diff --git a/bacnet-stack/abort.c b/bacnet-stack/abort.c index e69de29b..5d78ff3d 100644 --- a/bacnet-stack/abort.c +++ b/bacnet-stack/abort.c @@ -0,0 +1,195 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacdcode.h" +#include "bacdef.h" + +/* encode service */ +int abort_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + if (server) + apdu[0] = PDU_TYPE_ABORT | 1; + else + apdu[0] = PDU_TYPE_ABORT; + apdu[1] = invoke_id; + apdu[2] = abort_reason; + apdu_len = 3; + } + + return apdu_len; +} + +/* decode the service request only */ +int abort_decode_service_request(uint8_t * apdu, + unsigned apdu_len, uint8_t * invoke_id, uint8_t * abort_reason) +{ + int len = 0; + + if (apdu_len) { + if (invoke_id) + *invoke_id = apdu[0]; + if (abort_reason) + *abort_reason = apdu[1]; + } + + return len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +/* decode the whole APDU - mainly used for unit testing */ +int abort_decode_apdu(uint8_t * apdu, + unsigned apdu_len, uint8_t * invoke_id, uint8_t * abort_reason, + bool * server) +{ + int len = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu_len) { + if ((apdu[0] & 0xF0) != PDU_TYPE_ABORT) + return -1; + if (apdu[0] & 1) + *server = true; + else + *server = false; + if (apdu_len > 1) { + len = abort_decode_service_request(&apdu[1], + apdu_len - 1, invoke_id, abort_reason); + } + } + + return len; +} + +void testAbortAPDU(Test * pTest, + uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + uint8_t test_invoke_id = 0; + uint8_t test_abort_reason = 0; + bool test_server = false; + + len = abort_encode_apdu(&apdu[0], invoke_id, abort_reason, server); + apdu_len = len; + ct_test(pTest, len != 0); + len = abort_decode_apdu(&apdu[0], + apdu_len, &test_invoke_id, &test_abort_reason, &test_server); + ct_test(pTest, len != -1); + ct_test(pTest, test_invoke_id == invoke_id); + ct_test(pTest, test_abort_reason == abort_reason); + ct_test(pTest, test_server == server); + + return; +} + + +void testAbort(Test * pTest) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + uint8_t invoke_id = 0; + uint8_t test_invoke_id = 0; + uint8_t abort_reason = 0; + uint8_t test_abort_reason = 0; + bool server = false; + bool test_server = false; + + len = abort_encode_apdu(&apdu[0], invoke_id, abort_reason, server); + ct_test(pTest, len != 0); + apdu_len = len; + len = abort_decode_apdu(&apdu[0], + apdu_len, &test_invoke_id, &test_abort_reason, &test_server); + ct_test(pTest, len != -1); + ct_test(pTest, test_invoke_id == invoke_id); + ct_test(pTest, test_abort_reason == abort_reason); + ct_test(pTest, test_server == server); + + /* change type to get negative response */ + apdu[0] = PDU_TYPE_REJECT; + len = abort_decode_apdu(&apdu[0], + apdu_len, &test_invoke_id, &test_abort_reason, &test_server); + ct_test(pTest, len == -1); + + /* test NULL APDU */ + len = abort_decode_apdu(NULL, + apdu_len, &test_invoke_id, &test_abort_reason, &test_server); + ct_test(pTest, len == -1); + + /* force a zero length */ + len = abort_decode_apdu(&apdu[0], + 0, &test_invoke_id, &test_abort_reason, &test_server); + ct_test(pTest, len == 0); + + /* check them all... */ + for (invoke_id = 0; invoke_id < 255; invoke_id++) { + for (abort_reason = 0; abort_reason < 255; abort_reason++) { + testAbortAPDU(pTest, invoke_id, abort_reason, false); + testAbortAPDU(pTest, invoke_id, abort_reason, true); + } + } +} + +#ifdef TEST_ABORT +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Abort", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testAbort); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_ABORT */ +#endif /* TEST */ diff --git a/bacnet-stack/abort.h b/bacnet-stack/abort.h index e69de29b..6e3fd160 100644 --- a/bacnet-stack/abort.h +++ b/bacnet-stack/abort.h @@ -0,0 +1,62 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef ABORT_H +#define ABORT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + int abort_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, uint8_t abort_reason, bool server); + + int abort_decode_service_request(uint8_t * apdu, + unsigned apdu_len, uint8_t * invoke_id, uint8_t * abort_reason); + +#ifdef TEST +#include "ctest.h" + int abort_decode_apdu(uint8_t * apdu, + unsigned apdu_len, uint8_t * invoke_id, uint8_t * abort_reason, + bool * server); + + void testAbort(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/address.c b/bacnet-stack/address.c index e69de29b..e6755105 100644 --- a/bacnet-stack/address.c +++ b/bacnet-stack/address.c @@ -0,0 +1,366 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include +#include +#include +#include "config.h" +#include "address.h" +#include "bacdef.h" +#include "bacdcode.h" + +/* This module is used to handle the address binding that */ +/* occurs in BACnet. A device id is bound to a MAC address. */ +/* The normal method is using Who-Is, and using the data from I-Am */ + +static struct Address_Cache_Entry { + bool valid; + bool bind_request; + uint32_t device_id; + unsigned max_apdu; + BACNET_ADDRESS address; +} Address_Cache[MAX_ADDRESS_CACHE]; + +void address_copy(BACNET_ADDRESS * dest, BACNET_ADDRESS * src) +{ + unsigned i = 0; /* counter */ + + if (dest && src) { + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->mac[i] = src->mac[i]; + } + dest->mac_len = src->mac_len; + dest->net = src->net; + dest->len = src->len; + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = src->adr[i]; + } + } + + return; +} + +void address_remove_device(uint32_t device_id) +{ + unsigned i; + + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + if ((Address_Cache[i].valid || + Address_Cache[i].bind_request) && + (Address_Cache[i].device_id == device_id)) { + Address_Cache[i].valid = false; + break; + } + } + + return; +} + +void address_init(void) +{ + unsigned i; + + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + Address_Cache[i].valid = false; + Address_Cache[i].bind_request = false; + } + + return; +} + +bool address_get_by_device(uint32_t device_id, + unsigned *max_apdu, BACNET_ADDRESS * src) +{ + unsigned i; + bool found = false; /* return value */ + + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + if (Address_Cache[i].valid && + (Address_Cache[i].device_id == device_id)) { + address_copy(src, &Address_Cache[i].address); + *max_apdu = Address_Cache[i].max_apdu; + found = true; + break; + } + } + + return found; +} + +void address_add(uint32_t device_id, + unsigned max_apdu, BACNET_ADDRESS * src) +{ + unsigned i; + bool found = false; /* return value */ + + /* existing device - update address */ + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + if (Address_Cache[i].valid && + (Address_Cache[i].device_id == device_id)) { + address_copy(&Address_Cache[i].address, src); + Address_Cache[i].max_apdu = max_apdu; + found = true; + break; + } + } + /* new device */ + if (!found) { + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + if (!Address_Cache[i].valid) { + Address_Cache[i].valid = true; + Address_Cache[i].device_id = device_id; + Address_Cache[i].max_apdu = max_apdu; + address_copy(&Address_Cache[i].address, src); + break; + } + } + } + + return; +} + +/* returns true if device is already bound */ +/* also returns the address and max apdu if already bound */ +bool address_bind_request(uint32_t device_id, + unsigned *max_apdu, BACNET_ADDRESS * src) +{ + unsigned i; + bool found = false; /* return value */ + + /* existing device - update address */ + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + if (Address_Cache[i].valid && + (Address_Cache[i].device_id == device_id)) { + found = true; + address_copy(src, &Address_Cache[i].address); + *max_apdu = Address_Cache[i].max_apdu; + break; + } + /* already have a bind request active for this puppy */ + else if (Address_Cache[i].bind_request && + (Address_Cache[i].device_id == device_id)) { + return found; + } + } + + if (!found) { + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + if (!(Address_Cache[i].bind_request || Address_Cache[i].valid)) { + Address_Cache[i].bind_request = true; + Address_Cache[i].device_id = device_id; + /* now would be a good time to do a Who-Is request */ + break; + } + } + } + + return found; +} + +void address_add_binding(uint32_t device_id, + unsigned max_apdu, BACNET_ADDRESS * src) +{ + unsigned i; + bool found = false; /* return value */ + + /* existing device - update address */ + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + if (Address_Cache[i].valid && + (Address_Cache[i].device_id == device_id)) { + address_copy(&Address_Cache[i].address, src); + Address_Cache[i].max_apdu = max_apdu; + found = true; + break; + } + } + /* add new device - but only if bind requested */ + if (!found) { + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + if (!Address_Cache[i].valid && Address_Cache[i].bind_request) { + Address_Cache[i].valid = true; + Address_Cache[i].bind_request = false; + Address_Cache[i].device_id = device_id; + Address_Cache[i].max_apdu = max_apdu; + address_copy(&Address_Cache[i].address, src); + break; + } + } + } + + return; +} + +bool address_get_by_index(unsigned index, + uint32_t * device_id, unsigned *max_apdu, BACNET_ADDRESS * src) +{ + bool found = false; /* return value */ + + if (index < MAX_ADDRESS_CACHE) { + if (Address_Cache[index].valid) { + address_copy(src, &Address_Cache[index].address); + *device_id = Address_Cache[index].device_id; + *max_apdu = Address_Cache[index].max_apdu; + found = true; + } + } + + return found; +} + +unsigned address_count(void) +{ + unsigned i; + unsigned count = 0; /* return value */ + + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + if (Address_Cache[i].valid) + count++; + } + + return count; +} + +bool address_match(BACNET_ADDRESS * dest, BACNET_ADDRESS * src) +{ + unsigned i; + unsigned max_len; + bool match = true; /* return value */ + + if (dest->mac_len != src->mac_len) + match = false; + max_len = dest->mac_len; + if (max_len > MAX_MAC_LEN) + max_len = MAX_MAC_LEN; + for (i = 0; i < max_len; i++) { + if (dest->mac[i] != src->mac[i]) + match = false; + } + if (dest->net != src->net) + match = false; + if (dest->len != src->len) + match = false; + max_len = dest->len; + if (max_len > MAX_MAC_LEN) + max_len = MAX_MAC_LEN; + for (i = 0; i < max_len; i++) { + if (dest->adr[i] != src->adr[i]) + match = false; + } + + return match; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +static void set_address(unsigned index, BACNET_ADDRESS * dest) +{ + unsigned i; + + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->mac[i] = index; + } + dest->mac_len = MAX_MAC_LEN; + dest->net = 7; + dest->len = MAX_MAC_LEN; + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = index; + } +} + +void testAddress(Test * pTest) +{ + unsigned i, count; + BACNET_ADDRESS src; + uint32_t device_id = 0; + unsigned max_apdu = 480; + BACNET_ADDRESS test_address; + uint32_t test_device_id = 0; + unsigned test_max_apdu = 0; + + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + set_address(i, &src); + device_id = i * 255; + address_add(device_id, max_apdu, &src); + count = address_count(); + ct_test(pTest, count == (i + 1)); + } + + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + device_id = i * 255; + ct_test(pTest, address_get_by_device(device_id, &test_max_apdu, + &test_address)); + set_address(i, &src); + ct_test(pTest, test_max_apdu == max_apdu); + ct_test(pTest, address_match(&test_address, &src)); + ct_test(pTest, address_get_by_index(i, &test_device_id, + &test_max_apdu, &test_address)); + ct_test(pTest, test_device_id == device_id); + ct_test(pTest, test_max_apdu == max_apdu); + ct_test(pTest, address_match(&test_address, &src)); + ct_test(pTest, address_count() == MAX_ADDRESS_CACHE); + } + + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + device_id = i * 255; + address_remove_device(device_id); + ct_test(pTest, !address_get_by_device(device_id, &test_max_apdu, + &test_address)); + count = address_count(); + ct_test(pTest, count == (MAX_ADDRESS_CACHE - i - 1)); + } +} + +#ifdef TEST_ADDRESS +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Address", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testAddress); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_ADDRESS */ +#endif /* TEST */ diff --git a/bacnet-stack/address.h b/bacnet-stack/address.h index e69de29b..7c5fd3e4 100644 --- a/bacnet-stack/address.h +++ b/bacnet-stack/address.h @@ -0,0 +1,74 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef ADDRESS_H +#define ADDRESS_H + +#include +#include +#include +#include "bacdef.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + void address_init(void); + + void address_copy(BACNET_ADDRESS * dest, BACNET_ADDRESS * src); + + void address_add(uint32_t device_id, + unsigned max_apdu, BACNET_ADDRESS * src); + + void address_remove_device(uint32_t device_id); + + bool address_get_by_device(uint32_t device_id, + unsigned *max_apdu, BACNET_ADDRESS * src); + + bool address_get_by_index(unsigned index, + uint32_t * device_id, unsigned *max_apdu, BACNET_ADDRESS * src); + + unsigned address_count(void); + + bool address_match(BACNET_ADDRESS * dest, BACNET_ADDRESS * src); + + bool address_bind_request(uint32_t device_id, + unsigned *max_apdu, BACNET_ADDRESS * src); + + void address_add_binding(uint32_t device_id, + unsigned max_apdu, BACNET_ADDRESS * src); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/apdu.c b/bacnet-stack/apdu.c index e69de29b..1c8b2968 100644 --- a/bacnet-stack/apdu.c +++ b/bacnet-stack/apdu.c @@ -0,0 +1,455 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include +#include +#include "bits.h" +#include "apdu.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "tsm.h" +#include "dcc.h" +#include "iam.h" + +/* a simple table for crossing the services supported */ +static BACNET_SERVICES_SUPPORTED + confirmed_service_supported[MAX_BACNET_CONFIRMED_SERVICE] = { + SERVICE_SUPPORTED_ACKNOWLEDGE_ALARM, + SERVICE_SUPPORTED_CONFIRMED_COV_NOTIFICATION, + SERVICE_SUPPORTED_CONFIRMED_EVENT_NOTIFICATION, + SERVICE_SUPPORTED_GET_ALARM_SUMMARY, + SERVICE_SUPPORTED_GET_ENROLLMENT_SUMMARY, + SERVICE_SUPPORTED_SUBSCRIBE_COV, + SERVICE_SUPPORTED_ATOMIC_READ_FILE, + SERVICE_SUPPORTED_ATOMIC_WRITE_FILE, + SERVICE_SUPPORTED_ADD_LIST_ELEMENT, + SERVICE_SUPPORTED_REMOVE_LIST_ELEMENT, + SERVICE_SUPPORTED_CREATE_OBJECT, + SERVICE_SUPPORTED_DELETE_OBJECT, + SERVICE_SUPPORTED_READ_PROPERTY, + SERVICE_SUPPORTED_READ_PROPERTY_CONDITIONAL, + SERVICE_SUPPORTED_READ_PROPERTY_MULTIPLE, + SERVICE_SUPPORTED_WRITE_PROPERTY, + SERVICE_SUPPORTED_WRITE_PROPERTY_MULTIPLE, + SERVICE_SUPPORTED_DEVICE_COMMUNICATION_CONTROL, + SERVICE_SUPPORTED_PRIVATE_TRANSFER, + SERVICE_SUPPORTED_TEXT_MESSAGE, + SERVICE_SUPPORTED_REINITIALIZE_DEVICE, + SERVICE_SUPPORTED_VT_OPEN, + SERVICE_SUPPORTED_VT_CLOSE, + SERVICE_SUPPORTED_VT_DATA, + SERVICE_SUPPORTED_AUTHENTICATE, + SERVICE_SUPPORTED_REQUEST_KEY, + SERVICE_SUPPORTED_READ_RANGE, + SERVICE_SUPPORTED_LIFE_SAFETY_OPERATION, + SERVICE_SUPPORTED_SUBSCRIBE_COV_PROPERTY, + SERVICE_SUPPORTED_GET_EVENT_INFORMATION +}; + +/* a simple table for crossing the services supported */ +static BACNET_SERVICES_SUPPORTED + unconfirmed_service_supported[MAX_BACNET_UNCONFIRMED_SERVICE] = { + SERVICE_SUPPORTED_I_AM, + SERVICE_SUPPORTED_I_HAVE, + SERVICE_SUPPORTED_UNCONFIRMED_COV_NOTIFICATION, + SERVICE_SUPPORTED_UNCONFIRMED_EVENT_NOTIFICATION, + SERVICE_SUPPORTED_UNCONFIRMED_PRIVATE_TRANSFER, + SERVICE_SUPPORTED_UNCONFIRMED_TEXT_MESSAGE, + SERVICE_SUPPORTED_TIME_SYNCHRONIZATION, + SERVICE_SUPPORTED_WHO_HAS, + SERVICE_SUPPORTED_WHO_IS, + SERVICE_SUPPORTED_UTC_TIME_SYNCHRONIZATION +}; + +/* Confirmed Function Handlers */ +/* If they are not set, they are handled by a reject message */ +static confirmed_function Confirmed_Function[MAX_BACNET_CONFIRMED_SERVICE]; + +void apdu_set_confirmed_handler(BACNET_CONFIRMED_SERVICE service_choice, + confirmed_function pFunction) +{ + if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) + Confirmed_Function[service_choice] = pFunction; +} + +/* Allow the APDU handler to automatically reject */ +static confirmed_function Unrecognized_Service_Handler; + +void apdu_set_unrecognized_service_handler_handler(confirmed_function + pFunction) +{ + Unrecognized_Service_Handler = pFunction; +} + +/* Unconfirmed Function Handlers */ +/* If they are not set, they are not handled */ +static unconfirmed_function + Unconfirmed_Function[MAX_BACNET_UNCONFIRMED_SERVICE] = { + NULL +}; + +void apdu_set_unconfirmed_handler(BACNET_UNCONFIRMED_SERVICE + service_choice, unconfirmed_function pFunction) +{ + if (service_choice < MAX_BACNET_UNCONFIRMED_SERVICE) + Unconfirmed_Function[service_choice] = pFunction; +} + +bool apdu_service_supported(BACNET_SERVICES_SUPPORTED service_supported) +{ + int i = 0; + bool status = false; + bool found = false; + + if (service_supported < MAX_BACNET_SERVICES_SUPPORTED) { + /* is it a confirmed service? */ + for (i = 0; i < MAX_BACNET_CONFIRMED_SERVICE; i++) { + if (confirmed_service_supported[i] == service_supported) { + if (Confirmed_Function[i] != NULL) + status = true; + found = true; + break; + } + } + + if (!found) { + /* is it an unconfirmed service? */ + for (i = 0; i < MAX_BACNET_UNCONFIRMED_SERVICE; i++) { + if (unconfirmed_service_supported[i] == service_supported) { + if (Unconfirmed_Function[i] != NULL) + status = true; + break; + } + } + } + } + return status; +} + +/* Confirmed ACK Function Handlers */ +static void *Confirmed_ACK_Function[MAX_BACNET_CONFIRMED_SERVICE]; + +void apdu_set_confirmed_simple_ack_handler(BACNET_CONFIRMED_SERVICE + service_choice, confirmed_simple_ack_function pFunction) +{ + switch (service_choice) { + case SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM: + case SERVICE_CONFIRMED_COV_NOTIFICATION: + case SERVICE_CONFIRMED_EVENT_NOTIFICATION: + case SERVICE_CONFIRMED_SUBSCRIBE_COV: + case SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY: + case SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION: + /* Object Access Services */ + case SERVICE_CONFIRMED_ADD_LIST_ELEMENT: + case SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT: + case SERVICE_CONFIRMED_DELETE_OBJECT: + case SERVICE_CONFIRMED_WRITE_PROPERTY: + case SERVICE_CONFIRMED_WRITE_PROPERTY_MULTIPLE: + /* Remote Device Management Services */ + case SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL: + case SERVICE_CONFIRMED_TEXT_MESSAGE: + case SERVICE_CONFIRMED_REINITIALIZE_DEVICE: + /* Virtual Terminal Services */ + case SERVICE_CONFIRMED_VT_CLOSE: + /* Security Services */ + case SERVICE_CONFIRMED_REQUEST_KEY: + Confirmed_ACK_Function[service_choice] = (void *) pFunction; + break; + default: + break; + } +} + +void apdu_set_confirmed_ack_handler(BACNET_CONFIRMED_SERVICE + service_choice, confirmed_ack_function pFunction) +{ + switch (service_choice) { + case SERVICE_CONFIRMED_GET_ALARM_SUMMARY: + case SERVICE_CONFIRMED_GET_ENROLLMENT_SUMMARY: + case SERVICE_CONFIRMED_GET_EVENT_INFORMATION: + /* File Access Services */ + case SERVICE_CONFIRMED_ATOMIC_READ_FILE: + case SERVICE_CONFIRMED_ATOMIC_WRITE_FILE: + /* Object Access Services */ + case SERVICE_CONFIRMED_CREATE_OBJECT: + case SERVICE_CONFIRMED_READ_PROPERTY: + case SERVICE_CONFIRMED_READ_PROPERTY_CONDITIONAL: + case SERVICE_CONFIRMED_READ_PROPERTY_MULTIPLE: + case SERVICE_CONFIRMED_READ_RANGE: + /* Remote Device Management Services */ + case SERVICE_CONFIRMED_PRIVATE_TRANSFER: + /* Virtual Terminal Services */ + case SERVICE_CONFIRMED_VT_OPEN: + case SERVICE_CONFIRMED_VT_DATA: + /* Security Services */ + case SERVICE_CONFIRMED_AUTHENTICATE: + Confirmed_ACK_Function[service_choice] = (void *) pFunction; + break; + default: + break; + } +} + +static error_function Error_Function[MAX_BACNET_CONFIRMED_SERVICE]; + +void apdu_set_error_handler(BACNET_CONFIRMED_SERVICE service_choice, + error_function pFunction) +{ + if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) + Error_Function[service_choice] = pFunction; +} + +static abort_function Abort_Function; + +void apdu_set_abort_handler(abort_function pFunction) +{ + Abort_Function = pFunction; +} + +static reject_function Reject_Function; + +void apdu_set_reject_handler(reject_function pFunction) +{ + Reject_Function = pFunction; +} + +uint16_t apdu_decode_confirmed_service_request(uint8_t * apdu, /* APDU data */ + uint16_t apdu_len, + BACNET_CONFIRMED_SERVICE_DATA * service_data, + uint8_t * service_choice, + uint8_t ** service_request, uint16_t * service_request_len) +{ + uint16_t len = 0; /* counts where we are in PDU */ + + service_data->segmented_message = (apdu[0] & BIT3) ? true : false; + service_data->more_follows = (apdu[0] & BIT2) ? true : false; + service_data->segmented_response_accepted = + (apdu[0] & BIT1) ? true : false; + service_data->max_segs = decode_max_segs(apdu[1]); + service_data->max_resp = decode_max_apdu(apdu[1]); + service_data->invoke_id = apdu[2]; + len = 3; + if (service_data->segmented_message) { + service_data->sequence_number = apdu[len++]; + service_data->proposed_window_number = apdu[len++]; + } + *service_choice = apdu[len++]; + *service_request = &apdu[len]; + *service_request_len = apdu_len - len; + + return len; +} + +void apdu_handler(BACNET_ADDRESS * src, uint8_t * apdu, /* APDU data */ + uint16_t apdu_len) +{ + BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 }; + BACNET_CONFIRMED_SERVICE_ACK_DATA service_ack_data = { 0 }; + uint8_t invoke_id = 0; + uint8_t service_choice = 0; + uint8_t *service_request = NULL; + uint16_t service_request_len = 0; + uint16_t len = 0; /* counts where we are in PDU */ + uint8_t tag_number = 0; + uint32_t len_value = 0; + int error_code = 0; + int error_class = 0; + uint8_t reason = 0; + bool server = false; + + if (apdu) { + /* PDU Type */ + switch (apdu[0] & 0xF0) { + case PDU_TYPE_CONFIRMED_SERVICE_REQUEST: + len = apdu_decode_confirmed_service_request(&apdu[0], /* APDU data */ + apdu_len, + &service_data, + &service_choice, &service_request, &service_request_len); + /* When network communications are completely disabled, + only DeviceCommunicationControl and ReinitializeDevice APDUs + shall be processed and no messages shall be initiated. */ + if (dcc_communication_disabled() && + ((service_choice != + SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL) + && (service_choice != + SERVICE_CONFIRMED_REINITIALIZE_DEVICE))) + break; + if ((service_choice < MAX_BACNET_CONFIRMED_SERVICE) && + (Confirmed_Function[service_choice])) + Confirmed_Function[service_choice] (service_request, + service_request_len, src, &service_data); + else if (Unrecognized_Service_Handler) + Unrecognized_Service_Handler(service_request, + service_request_len, src, &service_data); + break; + case PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST: + if (dcc_communication_disabled()) + break; + service_choice = apdu[1]; + service_request = &apdu[2]; + service_request_len = apdu_len - 2; + if (service_choice < MAX_BACNET_UNCONFIRMED_SERVICE) { + if (Unconfirmed_Function[service_choice]) + Unconfirmed_Function[service_choice] (service_request, + service_request_len, src); + } + break; + case PDU_TYPE_SIMPLE_ACK: + invoke_id = apdu[1]; + service_choice = apdu[2]; + switch (service_choice) { + case SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM: + case SERVICE_CONFIRMED_COV_NOTIFICATION: + case SERVICE_CONFIRMED_EVENT_NOTIFICATION: + case SERVICE_CONFIRMED_SUBSCRIBE_COV: + case SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY: + case SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION: + /* Object Access Services */ + case SERVICE_CONFIRMED_ADD_LIST_ELEMENT: + case SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT: + case SERVICE_CONFIRMED_DELETE_OBJECT: + case SERVICE_CONFIRMED_WRITE_PROPERTY: + case SERVICE_CONFIRMED_WRITE_PROPERTY_MULTIPLE: + /* Remote Device Management Services */ + case SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL: + case SERVICE_CONFIRMED_REINITIALIZE_DEVICE: + case SERVICE_CONFIRMED_TEXT_MESSAGE: + /* Virtual Terminal Services */ + case SERVICE_CONFIRMED_VT_CLOSE: + /* Security Services */ + case SERVICE_CONFIRMED_REQUEST_KEY: + if (Confirmed_ACK_Function[service_choice]) { + ((confirmed_simple_ack_function) + Confirmed_ACK_Function[service_choice]) (src, + invoke_id); + } + tsm_free_invoke_id(invoke_id); + break; + default: + break; + } + break; + case PDU_TYPE_COMPLEX_ACK: + service_ack_data.segmented_message = + (apdu[0] & BIT3) ? true : false; + service_ack_data.more_follows = + (apdu[0] & BIT2) ? true : false; + invoke_id = service_ack_data.invoke_id = apdu[1]; + len = 2; + if (service_ack_data.segmented_message) { + service_ack_data.sequence_number = apdu[len++]; + service_ack_data.proposed_window_number = apdu[len++]; + } + service_choice = apdu[len++]; + service_request = &apdu[len]; + service_request_len = apdu_len - len; + switch (service_choice) { + case SERVICE_CONFIRMED_GET_ALARM_SUMMARY: + case SERVICE_CONFIRMED_GET_ENROLLMENT_SUMMARY: + case SERVICE_CONFIRMED_GET_EVENT_INFORMATION: + /* File Access Services */ + case SERVICE_CONFIRMED_ATOMIC_READ_FILE: + case SERVICE_CONFIRMED_ATOMIC_WRITE_FILE: + /* Object Access Services */ + case SERVICE_CONFIRMED_CREATE_OBJECT: + case SERVICE_CONFIRMED_READ_PROPERTY: + case SERVICE_CONFIRMED_READ_PROPERTY_CONDITIONAL: + case SERVICE_CONFIRMED_READ_PROPERTY_MULTIPLE: + case SERVICE_CONFIRMED_READ_RANGE: + case SERVICE_CONFIRMED_PRIVATE_TRANSFER: + /* Virtual Terminal Services */ + case SERVICE_CONFIRMED_VT_OPEN: + case SERVICE_CONFIRMED_VT_DATA: + /* Security Services */ + case SERVICE_CONFIRMED_AUTHENTICATE: + if (Confirmed_ACK_Function[service_choice]) { + ((confirmed_ack_function) + Confirmed_ACK_Function[service_choice]) + (service_request, service_request_len, src, + &service_ack_data); + } + tsm_free_invoke_id(invoke_id); + break; + default: + break; + } + break; + case PDU_TYPE_SEGMENT_ACK: + /* FIXME: what about a denial of service attack here? + we could check src to see if that matched the tsm */ + tsm_free_invoke_id(invoke_id); + break; + case PDU_TYPE_ERROR: + invoke_id = apdu[1]; + service_choice = apdu[2]; + len = 3; + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + /* FIXME: we could validate that the tag is enumerated... */ + len += decode_enumerated(&apdu[len], len_value, &error_class); + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + /* FIXME: we could validate that the tag is enumerated... */ + len += decode_enumerated(&apdu[len], len_value, &error_code); + if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) { + if (Error_Function[service_choice]) + Error_Function[service_choice] (src, + invoke_id, error_class, error_code); + } + tsm_free_invoke_id(invoke_id); + break; + case PDU_TYPE_REJECT: + invoke_id = apdu[1]; + reason = apdu[2]; + if (Reject_Function) + Reject_Function(src, invoke_id, reason); + tsm_free_invoke_id(invoke_id); + break; + case PDU_TYPE_ABORT: + server = apdu[0] & 0x01; + invoke_id = apdu[1]; + reason = apdu[2]; + if (Abort_Function) + Abort_Function(src, invoke_id, reason, server); + tsm_free_invoke_id(invoke_id); + break; + default: + break; + } + } + return; +} diff --git a/bacnet-stack/apdu.h b/bacnet-stack/apdu.h index e69de29b..b37d46a7 100644 --- a/bacnet-stack/apdu.h +++ b/bacnet-stack/apdu.h @@ -0,0 +1,158 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef APDU_H +#define APDU_H + +#include +#include +#include "bacdef.h" +#include "bacenum.h" + +typedef struct _confirmed_service_data { + bool segmented_message; + bool more_follows; + bool segmented_response_accepted; + int max_segs; + int max_resp; + uint8_t invoke_id; + uint8_t sequence_number; + uint8_t proposed_window_number; +} BACNET_CONFIRMED_SERVICE_DATA; + +typedef struct _confirmed_service_ack_data { + bool segmented_message; + bool more_follows; + uint8_t invoke_id; + uint8_t sequence_number; + uint8_t proposed_window_number; +} BACNET_CONFIRMED_SERVICE_ACK_DATA; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* generic unconfirmed function handler */ +/* Suitable to handle the following services: */ +/* I_Am, Who_Is, Unconfirmed_COV_Notification, I_Have, */ +/* Unconfirmed_Event_Notification, Unconfirmed_Private_Transfer, */ +/* Unconfirmed_Text_Message, Time_Synchronization, Who_Has, */ +/* UTC_Time_Synchronization */ + typedef void (*unconfirmed_function) (uint8_t * service_request, + uint16_t len, BACNET_ADDRESS * src); + +/* generic confirmed function handler */ +/* Suitable to handle the following services: */ +/* Acknowledge_Alarm, Confirmed_COV_Notification, */ +/* Confirmed_Event_Notification, Get_Alarm_Summary, */ +/* Get_Enrollment_Summary_Handler, Get_Event_Information, */ +/* Subscribe_COV_Handler, Subscribe_COV_Property, */ +/* Life_Safety_Operation, Atomic_Read_File, */ +/* Confirmed_Atomic_Write_File, Add_List_Element, */ +/* Remove_List_Element, Create_Object_Handler, */ +/* Delete_Object_Handler, Read_Property, */ +/* Read_Property_Conditional, Read_Property_Multiple, Read_Range, */ +/* Write_Property, Write_Property_Multiple, */ +/* Device_Communication_Control, Confirmed_Private_Transfer, */ +/* Confirmed_Text_Message, Reinitialize_Device, */ +/* VT_Open, VT_Close, VT_Data_Handler, */ +/* Authenticate, Request_Key */ + typedef void (*confirmed_function) (uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, + BACNET_CONFIRMED_SERVICE_DATA * service_data); + +/* generic confirmed simple ack function handler */ + typedef void (*confirmed_simple_ack_function) (BACNET_ADDRESS * src, + uint8_t invoke_id); + +/* generic confirmed ack function handler */ + typedef void (*confirmed_ack_function) (uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, + BACNET_CONFIRMED_SERVICE_ACK_DATA * service_data); + +/* generic error reply function */ + typedef void (*error_function) (BACNET_ADDRESS * src, + uint8_t invoke_id, + BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code); + +/* generic abort reply function */ + typedef void (*abort_function) (BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t abort_reason, bool server); + +/* generic reject reply function */ + typedef void (*reject_function) (BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t reject_reason); + + void apdu_set_confirmed_ack_handler(BACNET_CONFIRMED_SERVICE + service_choice, confirmed_ack_function pFunction); + + void apdu_set_confirmed_simple_ack_handler(BACNET_CONFIRMED_SERVICE + service_choice, confirmed_simple_ack_function pFunction); + +/* configure reject for confirmed services that are not supported */ + void apdu_set_unrecognized_service_handler_handler(confirmed_function + pFunction); + + void apdu_set_confirmed_handler(BACNET_CONFIRMED_SERVICE + service_choice, confirmed_function pFunction); + + void apdu_set_unconfirmed_handler(BACNET_UNCONFIRMED_SERVICE + service_choice, unconfirmed_function pFunction); + +/* returns true if the service is supported by a handler */ + bool apdu_service_supported(BACNET_SERVICES_SUPPORTED + service_supported); + + void apdu_set_error_handler(BACNET_CONFIRMED_SERVICE service_choice, + error_function pFunction); + + void apdu_set_abort_handler(abort_function pFunction); + + void apdu_set_reject_handler(reject_function pFunction); + + uint16_t apdu_decode_confirmed_service_request(uint8_t * apdu, /* APDU data */ + uint16_t apdu_len, + BACNET_CONFIRMED_SERVICE_DATA * service_data, + uint8_t * service_choice, + uint8_t ** service_request, uint16_t * service_request_len); + + void apdu_handler(BACNET_ADDRESS * src, /* source address */ + uint8_t * apdu, /* APDU data */ + uint16_t pdu_len); /* for confirmed messages */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/arcnet.h b/bacnet-stack/arcnet.h index e69de29b..324a0b77 100644 --- a/bacnet-stack/arcnet.h +++ b/bacnet-stack/arcnet.h @@ -0,0 +1,75 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef ARCNET_H +#define ARCNET_H + +#include +#include +#include +#include "bacdef.h" +#include "npdu.h" + +/* specific defines for ARCNET */ +#define MAX_HEADER (1+1+2+2+1+1+1+1) +#define MAX_MPDU (MAX_HEADER+MAX_PDU) + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + bool arcnet_valid(void); + void arcnet_cleanup(void); + bool arcnet_init(char *interface_name); + +/* function to send a packet out the 802.2 socket */ +/* returns zero on success, non-zero on failure */ + int arcnet_send_pdu(BACNET_ADDRESS * dest, /* destination address */ + BACNET_NPDU_DATA * npdu_data, /* network information */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len); /* number of bytes of data */ + +/* receives an framed packet */ +/* returns the number of octets in the PDU, or zero on failure */ + uint16_t arcnet_receive(BACNET_ADDRESS * src, /* source address */ + uint8_t * pdu, /* PDU data */ + uint16_t max_pdu, /* amount of space available in the PDU */ + unsigned timeout); /* milliseconds to wait for a packet */ + + void arcnet_get_my_address(BACNET_ADDRESS * my_address); + void arcnet_get_broadcast_address(BACNET_ADDRESS * dest); /* destination address */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/arf.c b/bacnet-stack/arf.c index e69de29b..7e3bcf33 100644 --- a/bacnet-stack/arf.c +++ b/bacnet-stack/arf.c @@ -0,0 +1,473 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacdcode.h" +#include "bacdef.h" +#include "device.h" +#include "arf.h" + +/* Atomic Read File */ + +/* encode service */ +int arf_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_ATOMIC_READ_FILE_DATA * data) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST; + apdu[1] = + encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); + apdu[2] = invoke_id; + apdu[3] = SERVICE_CONFIRMED_ATOMIC_READ_FILE; /* service choice */ + apdu_len = 4; + apdu_len += encode_tagged_object_id(&apdu[apdu_len], + data->object_type, data->object_instance); + switch (data->access) { + case FILE_STREAM_ACCESS: + apdu_len += encode_opening_tag(&apdu[apdu_len], 0); + apdu_len += encode_tagged_signed(&apdu[apdu_len], + data->type.stream.fileStartPosition); + apdu_len += encode_tagged_unsigned(&apdu[apdu_len], + data->type.stream.requestedOctetCount); + apdu_len += encode_closing_tag(&apdu[apdu_len], 0); + break; + case FILE_RECORD_ACCESS: + apdu_len += encode_opening_tag(&apdu[apdu_len], 1); + apdu_len += encode_tagged_signed(&apdu[apdu_len], + data->type.record.fileStartRecord); + apdu_len += encode_tagged_unsigned(&apdu[apdu_len], + data->type.record.RecordCount); + apdu_len += encode_closing_tag(&apdu[apdu_len], 1); + break; + default: + break; + } + } + + return apdu_len; +} + +/* decode the service request only */ +int arf_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_ATOMIC_READ_FILE_DATA * data) +{ + int len = 0; + int tag_len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + int type = 0; /* for decoding */ + + /* check for value pointers */ + if (apdu_len && data) { + len = + decode_tag_number_and_value(&apdu[0], &tag_number, + &len_value_type); + if (tag_number != BACNET_APPLICATION_TAG_OBJECT_ID) + return -1; + len += decode_object_id(&apdu[len], &type, &data->object_instance); + data->object_type = type; + if (decode_is_opening_tag_number(&apdu[len], 0)) { + data->access = FILE_STREAM_ACCESS; + /* a tag number is not extended so only one octet */ + len++; + /* fileStartPosition */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_SIGNED_INT) + return -1; + len += decode_signed(&apdu[len], + len_value_type, &data->type.stream.fileStartPosition); + /* requestedOctetCount */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_UNSIGNED_INT) + return -1; + len += decode_unsigned(&apdu[len], + len_value_type, &data->type.stream.requestedOctetCount); + if (!decode_is_closing_tag_number(&apdu[len], 0)) + return -1; + /* a tag number is not extended so only one octet */ + len++; + } else if (decode_is_opening_tag_number(&apdu[len], 1)) { + data->access = FILE_RECORD_ACCESS; + /* a tag number is not extended so only one octet */ + len++; + /* fileStartRecord */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_SIGNED_INT) + return -1; + len += decode_signed(&apdu[len], + len_value_type, &data->type.record.fileStartRecord); + /* RecordCount */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_UNSIGNED_INT) + return -1; + len += decode_unsigned(&apdu[len], + len_value_type, &data->type.record.RecordCount); + if (!decode_is_closing_tag_number(&apdu[len], 1)) + return -1; + /* a tag number is not extended so only one octet */ + len++; + } else + return -1; + } + + return len; +} + +int arf_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, BACNET_ATOMIC_READ_FILE_DATA * data) +{ + int len = 0; + unsigned offset = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) + return -1; + /* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */ + *invoke_id = apdu[2]; /* invoke id - filled in by net layer */ + if (apdu[3] != SERVICE_CONFIRMED_ATOMIC_READ_FILE) + return -1; + offset = 4; + + if (apdu_len > offset) { + len = arf_decode_service_request(&apdu[offset], + apdu_len - offset, data); + } + + return len; +} + +/* encode service */ +int arf_ack_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_ATOMIC_READ_FILE_DATA * data) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu[0] = PDU_TYPE_COMPLEX_ACK; + apdu[1] = invoke_id; + apdu[2] = SERVICE_CONFIRMED_ATOMIC_READ_FILE; /* service choice */ + apdu_len = 3; + /* endOfFile */ + apdu_len += + encode_tagged_boolean(&apdu[apdu_len], data->endOfFile); + switch (data->access) { + case FILE_STREAM_ACCESS: + apdu_len += encode_opening_tag(&apdu[apdu_len], 0); + apdu_len += encode_tagged_signed(&apdu[apdu_len], + data->type.stream.fileStartPosition); + apdu_len += encode_tagged_octet_string(&apdu[apdu_len], + &data->fileData); + apdu_len += encode_closing_tag(&apdu[apdu_len], 0); + break; + case FILE_RECORD_ACCESS: + apdu_len += encode_opening_tag(&apdu[apdu_len], 1); + apdu_len += encode_tagged_signed(&apdu[apdu_len], + data->type.record.fileStartRecord); + apdu_len += encode_tagged_unsigned(&apdu[apdu_len], + data->type.record.RecordCount); + apdu_len += encode_tagged_octet_string(&apdu[apdu_len], + &data->fileData); + apdu_len += encode_closing_tag(&apdu[apdu_len], 1); + break; + default: + break; + } + } + + return apdu_len; +} + +/* decode the service request only */ +int arf_ack_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_ATOMIC_READ_FILE_DATA * data) +{ + int len = 0; + int tag_len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + + /* check for value pointers */ + if (apdu_len && data) { + len = + decode_tag_number_and_value(&apdu[0], &tag_number, + &len_value_type); + if (tag_number != BACNET_APPLICATION_TAG_BOOLEAN) + return -1; + data->endOfFile = decode_boolean(len_value_type); + if (decode_is_opening_tag_number(&apdu[len], 0)) { + data->access = FILE_STREAM_ACCESS; + /* a tag number is not extended so only one octet */ + len++; + /* fileStartPosition */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_SIGNED_INT) + return -1; + len += decode_signed(&apdu[len], + len_value_type, &data->type.stream.fileStartPosition); + /* fileData */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_OCTET_STRING) + return -1; + len += decode_octet_string(&apdu[len], + len_value_type, &data->fileData); + if (!decode_is_closing_tag_number(&apdu[len], 0)) + return -1; + /* a tag number is not extended so only one octet */ + len++; + } else if (decode_is_opening_tag_number(&apdu[len], 1)) { + data->access = FILE_RECORD_ACCESS; + /* a tag number is not extended so only one octet */ + len++; + /* fileStartRecord */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_SIGNED_INT) + return -1; + len += decode_signed(&apdu[len], + len_value_type, &data->type.record.fileStartRecord); + /* returnedRecordCount */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_UNSIGNED_INT) + return -1; + len += decode_unsigned(&apdu[len], + len_value_type, &data->type.record.RecordCount); + /* fileData */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_OCTET_STRING) + return -1; + len += decode_octet_string(&apdu[len], + len_value_type, &data->fileData); + if (!decode_is_closing_tag_number(&apdu[len], 1)) + return -1; + /* a tag number is not extended so only one octet */ + len++; + } else + return -1; + } + + return len; +} + +int arf_ack_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, BACNET_ATOMIC_READ_FILE_DATA * data) +{ + int len = 0; + unsigned offset = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_COMPLEX_ACK) + return -1; + *invoke_id = apdu[1]; /* invoke id - filled in by net layer */ + if (apdu[2] != SERVICE_CONFIRMED_ATOMIC_READ_FILE) + return -1; + offset = 3; + + if (apdu_len > offset) { + len = arf_ack_decode_service_request(&apdu[offset], + apdu_len - offset, data); + } + + return len; +} + + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testAtomicReadFileAckAccess(Test * pTest, + BACNET_ATOMIC_READ_FILE_DATA * data) +{ + BACNET_ATOMIC_READ_FILE_DATA test_data = { 0 }; + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + uint8_t invoke_id = 128; + uint8_t test_invoke_id = 0; + + len = arf_ack_encode_apdu(&apdu[0], invoke_id, data); + ct_test(pTest, len != 0); + apdu_len = len; + + len = arf_ack_decode_apdu(&apdu[0], + apdu_len, &test_invoke_id, &test_data); + ct_test(pTest, len != -1); + ct_test(pTest, test_data.endOfFile == data->endOfFile); + ct_test(pTest, test_data.access == data->access); + if (test_data.access == FILE_STREAM_ACCESS) { + ct_test(pTest, test_data.type.stream.fileStartPosition == + data->type.stream.fileStartPosition); + } else if (test_data.access == FILE_RECORD_ACCESS) { + ct_test(pTest, test_data.type.record.fileStartRecord == + data->type.record.fileStartRecord); + ct_test(pTest, test_data.type.record.RecordCount == + data->type.record.RecordCount); + } + ct_test(pTest, octetstring_length(&test_data.fileData) == + octetstring_length(&data->fileData)); + ct_test(pTest, memcmp(octetstring_value(&test_data.fileData), + octetstring_value(&data->fileData), + octetstring_length(&test_data.fileData)) == 0); +} + +void testAtomicReadFileAck(Test * pTest) +{ + BACNET_ATOMIC_READ_FILE_DATA data = { 0 }; + uint8_t test_octet_string[32] = "Joshua-Mary-Anna-Christopher"; + + + data.endOfFile = true; + data.access = FILE_STREAM_ACCESS; + data.type.stream.fileStartPosition = 0; + octetstring_init(&data.fileData, + test_octet_string, sizeof(test_octet_string)); + testAtomicReadFileAckAccess(pTest, &data); + + data.endOfFile = false; + data.access = FILE_RECORD_ACCESS; + data.type.record.fileStartRecord = 1; + data.type.record.RecordCount = 2; + octetstring_init(&data.fileData, + test_octet_string, sizeof(test_octet_string)); + testAtomicReadFileAckAccess(pTest, &data); + + return; +} + +void testAtomicReadFileAccess(Test * pTest, + BACNET_ATOMIC_READ_FILE_DATA * data) +{ + BACNET_ATOMIC_READ_FILE_DATA test_data = { 0 }; + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + uint8_t invoke_id = 128; + uint8_t test_invoke_id = 0; + + len = arf_encode_apdu(&apdu[0], invoke_id, data); + ct_test(pTest, len != 0); + apdu_len = len; + + len = arf_decode_apdu(&apdu[0], apdu_len, &test_invoke_id, &test_data); + ct_test(pTest, len != -1); + ct_test(pTest, test_data.object_type == data->object_type); + ct_test(pTest, test_data.object_instance == data->object_instance); + ct_test(pTest, test_data.access == data->access); + if (test_data.access == FILE_STREAM_ACCESS) { + ct_test(pTest, test_data.type.stream.fileStartPosition == + data->type.stream.fileStartPosition); + ct_test(pTest, test_data.type.stream.requestedOctetCount == + data->type.stream.requestedOctetCount); + } else if (test_data.access == FILE_RECORD_ACCESS) { + ct_test(pTest, test_data.type.record.fileStartRecord == + data->type.record.fileStartRecord); + ct_test(pTest, test_data.type.record.RecordCount == + data->type.record.RecordCount); + } +} + +void testAtomicReadFile(Test * pTest) +{ + BACNET_ATOMIC_READ_FILE_DATA data = { 0 }; + + data.object_type = OBJECT_FILE; + data.object_instance = 1; + data.access = FILE_STREAM_ACCESS; + data.type.stream.fileStartPosition = 0; + data.type.stream.requestedOctetCount = 128; + testAtomicReadFileAccess(pTest, &data); + + data.object_type = OBJECT_FILE; + data.object_instance = 2; + data.access = FILE_RECORD_ACCESS; + data.type.record.fileStartRecord = 1; + data.type.record.RecordCount = 2; + testAtomicReadFileAccess(pTest, &data); + + return; +} + +#ifdef TEST_ATOMIC_READ_FILE +uint16_t Device_Max_APDU_Length_Accepted(void) +{ + return MAX_APDU; +} + +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet AtomicReadFile", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testAtomicReadFile); + assert(rc); + rc = ct_addTestFunction(pTest, testAtomicReadFileAck); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_xxx */ +#endif /* TEST */ diff --git a/bacnet-stack/arf.h b/bacnet-stack/arf.h index e69de29b..b693e737 100644 --- a/bacnet-stack/arf.h +++ b/bacnet-stack/arf.h @@ -0,0 +1,102 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef ATOMIC_READ_FILE_H +#define ATOMIC_READ_FILE_H + +#include +#include +#include "bacdcode.h" +#include "bacstr.h" + +typedef struct BACnet_Atomic_Read_File_Data { + BACNET_OBJECT_TYPE object_type; + uint32_t object_instance; + BACNET_FILE_ACCESS_METHOD access; + union { + struct { + int32_t fileStartPosition; + uint32_t requestedOctetCount; + } stream; + struct { + int32_t fileStartRecord; + /* requested or returned record count */ + uint32_t RecordCount; + } record; + } type; + BACNET_OCTET_STRING fileData; + bool endOfFile; +} BACNET_ATOMIC_READ_FILE_DATA; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Atomic Read File */ +/* encode service */ + int arf_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_ATOMIC_READ_FILE_DATA * data); + +/* decode the service request only */ + int arf_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_ATOMIC_READ_FILE_DATA * data); + + int arf_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, BACNET_ATOMIC_READ_FILE_DATA * data); + +/* Atomic Read File Ack */ + +/* encode service */ + int arf_ack_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_ATOMIC_READ_FILE_DATA * data); + +/* decode the service request only */ + int arf_ack_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_ATOMIC_READ_FILE_DATA * data); + + int arf_ack_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, BACNET_ATOMIC_READ_FILE_DATA * data); + +#ifdef TEST +#include "ctest.h" + + void test_AtomicReadFile(Test * pTest); + void test_AtomicReadFileAck(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/awf.c b/bacnet-stack/awf.c index e69de29b..7ab8ef9b 100644 --- a/bacnet-stack/awf.c +++ b/bacnet-stack/awf.c @@ -0,0 +1,408 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacdcode.h" +#include "bacdef.h" +#include "device.h" +#include "awf.h" + +/* Atomic Write File */ + +/* encode service */ +int awf_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_ATOMIC_WRITE_FILE_DATA * data) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST; + apdu[1] = + encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); + apdu[2] = invoke_id; + apdu[3] = SERVICE_CONFIRMED_ATOMIC_WRITE_FILE; /* service choice */ + apdu_len = 4; + apdu_len += encode_tagged_object_id(&apdu[apdu_len], + data->object_type, data->object_instance); + switch (data->access) { + case FILE_STREAM_ACCESS: + apdu_len += encode_opening_tag(&apdu[apdu_len], 0); + apdu_len += encode_tagged_signed(&apdu[apdu_len], + data->type.stream.fileStartPosition); + apdu_len += encode_tagged_octet_string(&apdu[apdu_len], + &data->fileData); + apdu_len += encode_closing_tag(&apdu[apdu_len], 0); + break; + case FILE_RECORD_ACCESS: + apdu_len += encode_opening_tag(&apdu[apdu_len], 1); + apdu_len += encode_tagged_signed(&apdu[apdu_len], + data->type.record.fileStartRecord); + apdu_len += encode_tagged_unsigned(&apdu[apdu_len], + data->type.record.returnedRecordCount); + apdu_len += encode_tagged_octet_string(&apdu[apdu_len], + &data->fileData); + apdu_len += encode_closing_tag(&apdu[apdu_len], 1); + break; + default: + break; + } + } + + return apdu_len; +} + +/* decode the service request only */ +int awf_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_ATOMIC_WRITE_FILE_DATA * data) +{ + int len = 0; + int tag_len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + int32_t signed_value = 0; + uint32_t unsigned_value = 0; + int type = 0; /* for decoding */ + + /* check for value pointers */ + if (apdu_len && data) { + len = + decode_tag_number_and_value(&apdu[0], &tag_number, + &len_value_type); + if (tag_number != BACNET_APPLICATION_TAG_OBJECT_ID) + return -1; + len += decode_object_id(&apdu[len], &type, &data->object_instance); + data->object_type = type; + if (decode_is_opening_tag_number(&apdu[len], 0)) { + data->access = FILE_STREAM_ACCESS; + /* a tag number of 2 is not extended so only one octet */ + len++; + /* fileStartPosition */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_SIGNED_INT) + return -1; + len += decode_signed(&apdu[len], len_value_type, &signed_value); + data->type.stream.fileStartPosition = signed_value; + /* fileData */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_OCTET_STRING) + return -1; + len += decode_octet_string(&apdu[len], + len_value_type, &data->fileData); + if (!decode_is_closing_tag_number(&apdu[len], 0)) + return -1; + /* a tag number is not extended so only one octet */ + len++; + } else if (decode_is_opening_tag_number(&apdu[len], 1)) { + data->access = FILE_RECORD_ACCESS; + /* a tag number is not extended so only one octet */ + len++; + /* fileStartRecord */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_SIGNED_INT) + return -1; + len += decode_signed(&apdu[len], len_value_type, &signed_value); + data->type.record.fileStartRecord = signed_value; + /* returnedRecordCount */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_UNSIGNED_INT) + return -1; + len += decode_unsigned(&apdu[len], len_value_type, + &unsigned_value); + data->type.record.returnedRecordCount = unsigned_value; + /* fileData */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += tag_len; + if (tag_number != BACNET_APPLICATION_TAG_OCTET_STRING) + return -1; + len += decode_octet_string(&apdu[len], + len_value_type, &data->fileData); + if (!decode_is_closing_tag_number(&apdu[len], 1)) + return -1; + /* a tag number is not extended so only one octet */ + len++; + } else + return -1; + } + + return len; +} + +int awf_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, BACNET_ATOMIC_WRITE_FILE_DATA * data) +{ + int len = 0; + unsigned offset = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) + return -1; + /* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */ + *invoke_id = apdu[2]; /* invoke id - filled in by net layer */ + if (apdu[3] != SERVICE_CONFIRMED_ATOMIC_WRITE_FILE) + return -1; + offset = 4; + + if (apdu_len > offset) { + len = awf_decode_service_request(&apdu[offset], + apdu_len - offset, data); + } + + return len; +} + +int awf_ack_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_ATOMIC_WRITE_FILE_DATA * data) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu[0] = PDU_TYPE_COMPLEX_ACK; + apdu[1] = invoke_id; + apdu[2] = SERVICE_CONFIRMED_ATOMIC_WRITE_FILE; /* service choice */ + apdu_len = 3; + switch (data->access) { + case FILE_STREAM_ACCESS: + apdu_len += encode_context_signed(&apdu[apdu_len], 0, + data->type.stream.fileStartPosition); + break; + case FILE_RECORD_ACCESS: + apdu_len += encode_context_signed(&apdu[apdu_len], 1, + data->type.record.fileStartRecord); + break; + default: + break; + } + } + + return apdu_len; +} + +/* decode the service request only */ +int awf_ack_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_ATOMIC_WRITE_FILE_DATA * data) +{ + int len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + + /* check for value pointers */ + if (apdu_len && data) { + len = + decode_tag_number_and_value(&apdu[0], &tag_number, + &len_value_type); + if (tag_number == 0) { + data->access = FILE_STREAM_ACCESS; + len += decode_signed(&apdu[len], + len_value_type, &data->type.stream.fileStartPosition); + } else if (tag_number == 1) { + data->access = FILE_RECORD_ACCESS; + len += decode_signed(&apdu[len], + len_value_type, &data->type.record.fileStartRecord); + } else + return -1; + } + + return len; +} + +int awf_ack_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, BACNET_ATOMIC_WRITE_FILE_DATA * data) +{ + int len = 0; + unsigned offset = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_COMPLEX_ACK) + return -1; + *invoke_id = apdu[1]; /* invoke id - filled in by net layer */ + if (apdu[2] != SERVICE_CONFIRMED_ATOMIC_WRITE_FILE) + return -1; + offset = 3; + + if (apdu_len > offset) { + len = awf_decode_service_request(&apdu[offset], + apdu_len - offset, data); + } + + return len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testAtomicWriteFileAccess(Test * pTest, + BACNET_ATOMIC_WRITE_FILE_DATA * data) +{ + BACNET_ATOMIC_WRITE_FILE_DATA test_data = { 0 }; + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + uint8_t invoke_id = 128; + uint8_t test_invoke_id = 0; + + len = awf_encode_apdu(&apdu[0], invoke_id, data); + ct_test(pTest, len != 0); + apdu_len = len; + + len = awf_decode_apdu(&apdu[0], apdu_len, &test_invoke_id, &test_data); + ct_test(pTest, len != -1); + ct_test(pTest, test_data.object_type == data->object_type); + ct_test(pTest, test_data.object_instance == data->object_instance); + ct_test(pTest, test_data.access == data->access); + if (test_data.access == FILE_STREAM_ACCESS) { + ct_test(pTest, test_data.type.stream.fileStartPosition == + data->type.stream.fileStartPosition); + } else if (test_data.access == FILE_RECORD_ACCESS) { + ct_test(pTest, test_data.type.record.fileStartRecord == + data->type.record.fileStartRecord); + ct_test(pTest, test_data.type.record.returnedRecordCount == + data->type.record.returnedRecordCount); + } + ct_test(pTest, octetstring_length(&test_data.fileData) == + octetstring_length(&data->fileData)); + ct_test(pTest, memcmp(octetstring_value(&test_data.fileData), + octetstring_value(&data->fileData), + octetstring_length(&test_data.fileData)) == 0); +} + +void testAtomicWriteFile(Test * pTest) +{ + BACNET_ATOMIC_WRITE_FILE_DATA data = { 0 }; + uint8_t test_octet_string[32] = "Joshua-Mary-Anna-Christopher"; + + data.object_type = OBJECT_FILE; + data.object_instance = 1; + data.access = FILE_STREAM_ACCESS; + data.type.stream.fileStartPosition = 0; + octetstring_init(&data.fileData, + test_octet_string, sizeof(test_octet_string)); + testAtomicWriteFileAccess(pTest, &data); + + data.object_type = OBJECT_FILE; + data.object_instance = 1; + data.access = FILE_RECORD_ACCESS; + data.type.record.fileStartRecord = 1; + data.type.record.returnedRecordCount = 2; + octetstring_init(&data.fileData, + test_octet_string, sizeof(test_octet_string)); + testAtomicWriteFileAccess(pTest, &data); + + return; +} + +void testAtomicWriteFileAckAccess(Test * pTest, + BACNET_ATOMIC_WRITE_FILE_DATA * data) +{ + BACNET_ATOMIC_WRITE_FILE_DATA test_data = { 0 }; + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + uint8_t invoke_id = 128; + uint8_t test_invoke_id = 0; + + len = awf_encode_apdu(&apdu[0], invoke_id, data); + ct_test(pTest, len != 0); + apdu_len = len; + + len = awf_decode_apdu(&apdu[0], apdu_len, &test_invoke_id, &test_data); + ct_test(pTest, len != -1); + ct_test(pTest, test_data.access == data->access); + if (test_data.access == FILE_STREAM_ACCESS) { + ct_test(pTest, test_data.type.stream.fileStartPosition == + data->type.stream.fileStartPosition); + } else if (test_data.access == FILE_RECORD_ACCESS) { + ct_test(pTest, test_data.type.record.fileStartRecord == + data->type.record.fileStartRecord); + } +} + +void testAtomicWriteFileAck(Test * pTest) +{ + BACNET_ATOMIC_WRITE_FILE_DATA data = { 0 }; + + data.access = FILE_STREAM_ACCESS; + data.type.stream.fileStartPosition = 42; + testAtomicWriteFileAckAccess(pTest, &data); + + data.access = FILE_RECORD_ACCESS; + data.type.record.fileStartRecord = 54; + testAtomicWriteFileAckAccess(pTest, &data); + + return; +} + +#ifdef TEST_ATOMIC_WRITE_FILE +uint16_t Device_Max_APDU_Length_Accepted(void) +{ + return MAX_APDU; +} + +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet AtomicWriteFile", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testAtomicWriteFile); + assert(rc); + rc = ct_addTestFunction(pTest, testAtomicWriteFileAck); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_WRITE_PROPERTY */ +#endif /* TEST */ diff --git a/bacnet-stack/awf.h b/bacnet-stack/awf.h index e69de29b..639a8c98 100644 --- a/bacnet-stack/awf.h +++ b/bacnet-stack/awf.h @@ -0,0 +1,97 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef ATOMIC_WRITE_FILE_H +#define ATOMIC_WRITE_FILE_H + +#include +#include +#include "bacdcode.h" + +typedef struct BACnet_Atomic_Write_File_Data { + BACNET_OBJECT_TYPE object_type; + uint32_t object_instance; + BACNET_FILE_ACCESS_METHOD access; + union { + struct { + int32_t fileStartPosition; + } stream; + struct { + int32_t fileStartRecord; + uint32_t returnedRecordCount; + } record; + } type; + BACNET_OCTET_STRING fileData; +} BACNET_ATOMIC_WRITE_FILE_DATA; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Atomic Write File */ +/* encode service */ + int awf_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_ATOMIC_WRITE_FILE_DATA * data); + +/* decode the service request only */ + int awf_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_ATOMIC_WRITE_FILE_DATA * data); + + int awf_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, BACNET_ATOMIC_WRITE_FILE_DATA * data); + +/* Atomic Write File Ack */ +/* encode service */ + int awf_ack_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_ATOMIC_WRITE_FILE_DATA * data); + +/* decode the service request only */ + int awf_ack_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_ATOMIC_WRITE_FILE_DATA * data); + + int awf_ack_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, BACNET_ATOMIC_WRITE_FILE_DATA * data); + +#ifdef TEST +#include "ctest.h" + + void test_AtomicWriteFile(Test * pTest); + void test_AtomicWriteFileAck(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/bacapp.c b/bacnet-stack/bacapp.c index e69de29b..bdd98aeb 100644 --- a/bacnet-stack/bacapp.c +++ b/bacnet-stack/bacapp.c @@ -0,0 +1,1195 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include +#include +#include +#include +#include +#include "bacenum.h" +#include "bacdcode.h" +#include "bacdef.h" +#include "bacapp.h" +#include "bactext.h" +#include "datetime.h" + +int bacapp_encode_application_data(uint8_t * apdu, + BACNET_APPLICATION_DATA_VALUE * value) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (value && apdu) { + switch (value->tag) { + case BACNET_APPLICATION_TAG_NULL: + apdu[0] = value->tag; + apdu_len++; + break; + case BACNET_APPLICATION_TAG_BOOLEAN: + apdu_len = encode_tagged_boolean(&apdu[0], + value->type.Boolean); + break; + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + apdu_len = encode_tagged_unsigned(&apdu[0], + value->type.Unsigned_Int); + break; + case BACNET_APPLICATION_TAG_SIGNED_INT: + apdu_len = encode_tagged_signed(&apdu[0], + value->type.Signed_Int); + break; + case BACNET_APPLICATION_TAG_REAL: + apdu_len = encode_tagged_real(&apdu[0], value->type.Real); + break; + case BACNET_APPLICATION_TAG_ENUMERATED: + apdu_len = encode_tagged_enumerated(&apdu[0], + value->type.Enumerated); + break; + case BACNET_APPLICATION_TAG_DATE: + apdu_len = encode_tagged_date(&apdu[0], &value->type.Date); + break; + case BACNET_APPLICATION_TAG_TIME: + apdu_len = encode_tagged_time(&apdu[0], &value->type.Time); + break; + case BACNET_APPLICATION_TAG_OBJECT_ID: + apdu_len = encode_tagged_object_id(&apdu[0], + value->type.Object_Id.type, + value->type.Object_Id.instance); + break; + case BACNET_APPLICATION_TAG_OCTET_STRING: + apdu_len = encode_tagged_octet_string(&apdu[0], + &value->type.Octet_String); + break; + case BACNET_APPLICATION_TAG_CHARACTER_STRING: + apdu_len = encode_tagged_character_string(&apdu[0], + &value->type.Character_String); + break; + case BACNET_APPLICATION_TAG_BIT_STRING: + apdu_len = encode_tagged_bitstring(&apdu[0], + &value->type.Bit_String); + break; + case BACNET_APPLICATION_TAG_DOUBLE: + /* FIXME: double is not implemented yet. + apdu_len = encode_tagged_double(&apdu[0], + value->type.Double); + */ + default: + break; + } + } + + return apdu_len; +} + +/* decode the data and store it into value. + Return the number of octets consumed. */ +int bacapp_decode_data(uint8_t * apdu, + uint8_t tag_data_type, + uint32_t len_value_type, BACNET_APPLICATION_DATA_VALUE * value) +{ + int len = 0; + int object_type = 0; + uint32_t instance = 0; + + if (apdu && value) { + switch (tag_data_type) { + case BACNET_APPLICATION_TAG_NULL: + /* nothing else to do */ + break; + case BACNET_APPLICATION_TAG_BOOLEAN: + value->type.Boolean = decode_boolean(len_value_type); + break; + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + len = decode_unsigned(&apdu[0], + len_value_type, &value->type.Unsigned_Int); + break; + case BACNET_APPLICATION_TAG_SIGNED_INT: + len = decode_signed(&apdu[0], + len_value_type, &value->type.Signed_Int); + break; + case BACNET_APPLICATION_TAG_REAL: + len = decode_real(&apdu[0], &(value->type.Real)); + break; +#if 0 + case BACNET_APPLICATION_TAG_DOUBLE: + len = decode_double(&apdu[0], &(value->type.Double)); + break; +#endif + case BACNET_APPLICATION_TAG_ENUMERATED: + len = decode_enumerated(&apdu[0], + len_value_type, &value->type.Enumerated); + break; + case BACNET_APPLICATION_TAG_DATE: + len = decode_date(&apdu[0], &value->type.Date); + break; + case BACNET_APPLICATION_TAG_TIME: + len = decode_bacnet_time(&apdu[0], &value->type.Time); + break; + case BACNET_APPLICATION_TAG_OBJECT_ID: + len = decode_object_id(&apdu[0], &object_type, &instance); + value->type.Object_Id.type = object_type; + value->type.Object_Id.instance = instance; + break; + case BACNET_APPLICATION_TAG_OCTET_STRING: + len = decode_octet_string(&apdu[0], + len_value_type, &value->type.Octet_String); + break; + case BACNET_APPLICATION_TAG_CHARACTER_STRING: + len = decode_character_string(&apdu[0], + len_value_type, &value->type.Character_String); + break; + case BACNET_APPLICATION_TAG_BIT_STRING: + len = decode_bitstring(&apdu[0], + len_value_type, &value->type.Bit_String); + break; + default: + break; + } + } + + return len; +} + +int bacapp_decode_application_data(uint8_t * apdu, + int max_apdu_len, BACNET_APPLICATION_DATA_VALUE * value) +{ + int len = 0; + int tag_len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + + /* FIXME: use max_apdu_len! */ + (void) max_apdu_len; + if (apdu && value && !decode_is_context_specific(apdu)) { + value->context_specific = false; + tag_len = decode_tag_number_and_value(&apdu[0], + &tag_number, &len_value_type); + if (tag_len) { + len += tag_len; + value->tag = tag_number; + len += bacapp_decode_data(&apdu[len], + tag_number, len_value_type, value); + } + value->next = NULL; + } + + return len; +} + +int bacapp_encode_context_data_value(uint8_t * apdu, + uint8_t context_tag_number, BACNET_APPLICATION_DATA_VALUE * value) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (value && apdu) { + switch (value->tag) { + case BACNET_APPLICATION_TAG_NULL: + apdu_len = encode_context_null(&apdu[0], context_tag_number); + break; + case BACNET_APPLICATION_TAG_BOOLEAN: + apdu_len = encode_context_boolean(&apdu[0], context_tag_number, + value->type.Boolean); + break; + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + apdu_len = + encode_context_unsigned(&apdu[0], context_tag_number, + value->type.Unsigned_Int); + break; + case BACNET_APPLICATION_TAG_SIGNED_INT: + apdu_len = encode_context_signed(&apdu[0], context_tag_number, + value->type.Signed_Int); + break; + case BACNET_APPLICATION_TAG_REAL: + apdu_len = encode_context_real(&apdu[0], context_tag_number, + value->type.Real); + break; + case BACNET_APPLICATION_TAG_ENUMERATED: + apdu_len = + encode_context_enumerated(&apdu[0], context_tag_number, + value->type.Enumerated); + break; + case BACNET_APPLICATION_TAG_DATE: + apdu_len = encode_context_date(&apdu[0], context_tag_number, + &value->type.Date); + break; + case BACNET_APPLICATION_TAG_TIME: + apdu_len = encode_context_time(&apdu[0], context_tag_number, + &value->type.Time); + break; + case BACNET_APPLICATION_TAG_OBJECT_ID: + apdu_len = + encode_context_object_id(&apdu[0], context_tag_number, + value->type.Object_Id.type, + value->type.Object_Id.instance); + break; + case BACNET_APPLICATION_TAG_OCTET_STRING: + apdu_len = + encode_context_octet_string(&apdu[0], context_tag_number, + &value->type.Octet_String); + break; + case BACNET_APPLICATION_TAG_CHARACTER_STRING: + apdu_len = + encode_context_character_string(&apdu[0], + context_tag_number, &value->type.Character_String); + break; + case BACNET_APPLICATION_TAG_BIT_STRING: + apdu_len = + encode_context_bitstring(&apdu[0], context_tag_number, + &value->type.Bit_String); + break; +#if 0 + case BACNET_APPLICATION_TAG_DOUBLE: + /* FIXME: double is not implemented yet. */ + apdu_len = encode_context_double(&apdu[0], context_tag_number, + value->type.Double); + break; +#endif + default: + break; + } + } + + return apdu_len; +} + +/* returns the fixed tag type for certain context tagged properties */ +BACNET_APPLICATION_TAG bacapp_context_tag_type(BACNET_PROPERTY_ID property, + uint8_t tag_number) +{ + BACNET_APPLICATION_TAG tag = MAX_BACNET_APPLICATION_TAG; + + switch (property) { + case PROP_REQUESTED_SHED_LEVEL: + switch (tag_number) { + case 0: + case 1: + tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; + break; + case 2: + tag = BACNET_APPLICATION_TAG_REAL; + break; + default: + break; + } + break; + case PROP_ACTION: + switch (tag_number) { + case 0: + case 1: + tag = BACNET_APPLICATION_TAG_OBJECT_ID; + break; + case 2: + tag = BACNET_APPLICATION_TAG_ENUMERATED; + break; + case 3: + case 5: + case 6: + tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; + break; + case 7: + case 8: + tag = BACNET_APPLICATION_TAG_BOOLEAN; + break; + case 4: /* propertyValue: abstract syntax */ + default: + break; + } + break; + case PROP_EXCEPTION_SCHEDULE: + switch (tag_number) { + case 1: + tag = BACNET_APPLICATION_TAG_OBJECT_ID; + break; + case 3: + tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; + break; + case 0: /* calendarEntry: abstract syntax + context */ + case 2: /* list of BACnetTimeValue: abstract syntax */ + default: + break; + } + break; + default: + break; + } + + return tag; +} + +int bacapp_encode_context_data(uint8_t * apdu, + BACNET_APPLICATION_DATA_VALUE * value, BACNET_PROPERTY_ID property) +{ + int apdu_len = 0; + BACNET_APPLICATION_TAG tag_data_type; + + if (value && apdu) { + tag_data_type = + bacapp_context_tag_type(property, value->context_tag); + if (tag_data_type < MAX_BACNET_APPLICATION_TAG) { + apdu_len = bacapp_encode_context_data_value(&apdu[0], + value->context_tag, value); + } else { + /* FIXME: what now? */ + apdu_len = 0; + } + value->next = NULL; + } + + return apdu_len; +} + +int bacapp_decode_context_data(uint8_t * apdu, + int max_apdu_len, BACNET_APPLICATION_DATA_VALUE * value, + BACNET_PROPERTY_ID property) +{ + int apdu_len = 0, len = 0; + int tag_len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + + /* FIXME: use max_apdu_len! */ + (void) max_apdu_len; + if (apdu && value && decode_is_context_specific(apdu)) { + value->context_specific = true; + tag_len = decode_tag_number_and_value(&apdu[0], + &tag_number, &len_value_type); + if (tag_len) { + apdu_len = tag_len; + value->context_tag = tag_number; + value->tag = bacapp_context_tag_type(property, tag_number); + if (value->tag < MAX_BACNET_APPLICATION_TAG) { + len = bacapp_decode_data(&apdu[apdu_len], + value->tag, len_value_type, value); + apdu_len += len; + } else { + /* FIXME: what now? */ + apdu_len = 0; + } + } + value->next = NULL; + } + + return apdu_len; +} + +int bacapp_encode_data(uint8_t * apdu, + BACNET_APPLICATION_DATA_VALUE * value) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (value && apdu) { + if (value->context_specific) { + apdu_len = bacapp_encode_context_data_value(&apdu[0], + value->context_tag, value); + } else { + apdu_len = bacapp_encode_application_data(&apdu[0], value); + } + } + + return apdu_len; +} + + +bool bacapp_copy(BACNET_APPLICATION_DATA_VALUE * dest_value, + BACNET_APPLICATION_DATA_VALUE * src_value) +{ + bool status = true; /*return value */ + + if (dest_value && src_value) { + dest_value->tag = src_value->tag; + switch (src_value->tag) { + case BACNET_APPLICATION_TAG_NULL: + break; + case BACNET_APPLICATION_TAG_BOOLEAN: + dest_value->type.Boolean = src_value->type.Boolean; + break; + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + dest_value->type.Unsigned_Int = src_value->type.Unsigned_Int; + break; + case BACNET_APPLICATION_TAG_SIGNED_INT: + dest_value->type.Signed_Int = src_value->type.Signed_Int; + break; + case BACNET_APPLICATION_TAG_REAL: + dest_value->type.Real = src_value->type.Real; + break; + case BACNET_APPLICATION_TAG_DOUBLE: + dest_value->type.Double = src_value->type.Double; + break; + case BACNET_APPLICATION_TAG_ENUMERATED: + dest_value->type.Enumerated = src_value->type.Enumerated; + break; + case BACNET_APPLICATION_TAG_DATE: + datetime_copy_date(&dest_value->type.Date, + &src_value->type.Date); + break; + case BACNET_APPLICATION_TAG_TIME: + datetime_copy_time(&dest_value->type.Time, + &src_value->type.Time); + break; + case BACNET_APPLICATION_TAG_OBJECT_ID: + dest_value->type.Object_Id.type = + src_value->type.Object_Id.type; + dest_value->type.Object_Id.instance = + src_value->type.Object_Id.instance; + break; + case BACNET_APPLICATION_TAG_OCTET_STRING: + octetstring_copy(&dest_value->type.Octet_String, + &src_value->type.Octet_String); + break; + case BACNET_APPLICATION_TAG_CHARACTER_STRING: + characterstring_copy(&dest_value->type.Character_String, + &src_value->type.Character_String); + break; + case BACNET_APPLICATION_TAG_BIT_STRING: + default: + status = false; + break; + } + dest_value->next = src_value->next; + } + + return status; +} + +/* returns the length of data between an opening tag and a closing tag. + Expects that the first octet contain the opening tag. + Include a value property identifier for context specific data + such as the value received in a WriteProperty request */ +int bacapp_data_len(uint8_t * apdu, int max_apdu_len, + BACNET_PROPERTY_ID property) +{ + int len = 0; + int total_len = 0; + int apdu_len = 0; + uint8_t tag_number = 0; + uint8_t opening_tag_number = 0; + uint8_t opening_tag_number_counter = 0; + uint32_t value = 0; + BACNET_APPLICATION_DATA_VALUE application_value; + + if (decode_is_opening_tag(&apdu[0])) { + len = decode_tag_number_and_value(&apdu[apdu_len], + &tag_number, &value); + apdu_len += len; + opening_tag_number = tag_number; + opening_tag_number_counter = 1; + while (opening_tag_number_counter) { + if (decode_is_opening_tag(&apdu[apdu_len])) { + len = decode_tag_number_and_value(&apdu[apdu_len], + &tag_number, &value); + if (tag_number == opening_tag_number) + opening_tag_number_counter++; + } else if (decode_is_closing_tag(&apdu[apdu_len])) { + len = decode_tag_number_and_value(&apdu[apdu_len], + &tag_number, &value); + if (tag_number == opening_tag_number) + opening_tag_number_counter--; + } else if (decode_is_context_specific(&apdu[apdu_len])) { + /* context-specific tagged data */ + len = bacapp_decode_context_data(&apdu[apdu_len], + max_apdu_len - apdu_len, &application_value, property); + } else { + /* application tagged data */ + len = bacapp_decode_application_data(&apdu[apdu_len], + max_apdu_len - apdu_len, &application_value); + } + apdu_len += len; + if (opening_tag_number_counter) + total_len += len; + /* ERROR! */ + if (apdu_len > max_apdu_len) { + total_len = -1; + break; + } + } + } + + return total_len; +} + +#ifdef BACAPP_PRINT_ENABLED +bool bacapp_print_value(FILE * stream, + BACNET_APPLICATION_DATA_VALUE * value, BACNET_PROPERTY_ID property) +{ + bool status = true; /*return value */ + size_t len = 0, i = 0; + char *char_str; + uint8_t *octet_str; + + if (value) { + switch (value->tag) { + case BACNET_APPLICATION_TAG_NULL: + fprintf(stream, "Null"); + break; + case BACNET_APPLICATION_TAG_BOOLEAN: + fprintf(stream, "%s", value->type.Boolean ? "TRUE" : "FALSE"); + break; + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + fprintf(stream, "%u", value->type.Unsigned_Int); + break; + case BACNET_APPLICATION_TAG_SIGNED_INT: + fprintf(stream, "%d", value->type.Signed_Int); + break; + case BACNET_APPLICATION_TAG_REAL: + fprintf(stream, "%f", (double) value->type.Real); + break; + case BACNET_APPLICATION_TAG_DOUBLE: + fprintf(stream, "%f", value->type.Double); + break; + case BACNET_APPLICATION_TAG_ENUMERATED: + switch (property) { + case PROP_OBJECT_TYPE: + fprintf(stream, "%s", + bactext_object_type_name(value->type.Enumerated)); + break; + case PROP_EVENT_STATE: + fprintf(stream, "%s", + bactext_event_state_name(value->type.Enumerated)); + break; + case PROP_UNITS: + fprintf(stream, "%s", + bactext_engineering_unit_name(value->type.Enumerated)); + break; + case PROP_PRESENT_VALUE: + fprintf(stream, "%s", + bactext_binary_present_value_name(value->type. + Enumerated)); + break; + case PROP_RELIABILITY: + fprintf(stream, "%s", + bactext_reliability_name(value->type.Enumerated)); + break; + case PROP_SYSTEM_STATUS: + fprintf(stream, "%s", + bactext_device_status_name(value->type.Enumerated)); + break; + case PROP_SEGMENTATION_SUPPORTED: + fprintf(stream, "%s", + bactext_segmentation_name(value->type.Enumerated)); + break; + default: + fprintf(stream, "%u", value->type.Enumerated); + break; + } + break; + case BACNET_APPLICATION_TAG_DATE: + fprintf(stream, "%s, %s %u, %u", + bactext_day_of_week_name(value->type.Date.wday), + bactext_month_name(value->type.Date.month), + (unsigned) value->type.Date.day, + (unsigned) value->type.Date.year); + break; + case BACNET_APPLICATION_TAG_TIME: + fprintf(stream, "%02u:%02u:%02u.%03u", + (unsigned) value->type.Time.hour, + (unsigned) value->type.Time.min, + (unsigned) value->type.Time.sec, + (unsigned) value->type.Time.hundredths); + break; + case BACNET_APPLICATION_TAG_OBJECT_ID: + fprintf(stream, "%s %u", + bactext_object_type_name(value->type.Object_Id.type), + value->type.Object_Id.instance); + break; + case BACNET_APPLICATION_TAG_OCTET_STRING: + len = octetstring_length(&value->type.Octet_String); + octet_str = octetstring_value(&value->type.Octet_String); + for (i = 0; i < len; i++) { + fprintf(stream, "%02X", *octet_str); + octet_str++; + } + break; + case BACNET_APPLICATION_TAG_CHARACTER_STRING: + len = characterstring_length(&value->type.Character_String); + char_str = + characterstring_value(&value->type.Character_String); + fprintf(stream, "\""); + for (i = 0; i < len; i++) { + if (isprint(*char_str)) + fprintf(stream, "%c", *char_str); + else + fprintf(stream, "."); + char_str++; + } + fprintf(stream, "\""); + break; + case BACNET_APPLICATION_TAG_BIT_STRING: + len = bitstring_bits_used(&value->type.Bit_String); + fprintf(stream, "{"); + for (i = 0; i < len; i++) { + fprintf(stream, "%s", + bitstring_bit(&value->type.Bit_String, + (uint8_t) i) ? "true" : "false"); + if (i < len - 1) + fprintf(stream, ","); + } + fprintf(stream, "}"); + break; + default: + status = false; + break; + } + } + + return status; +} + +/* used to load the app data struct with the proper data + converted from a command line argument */ +bool bacapp_parse_application_data(BACNET_APPLICATION_TAG tag_number, + const char *argv, BACNET_APPLICATION_DATA_VALUE * value) +{ + int hour, min, sec, hundredths; + int year, month, day, wday; + int object_type = 0; + uint32_t instance = 0; + bool status = false; + long long_value = 0; + unsigned long unsigned_long_value = 0; + double double_value = 0.0; + int count = 0; + + if (value && (tag_number < MAX_BACNET_APPLICATION_TAG)) { + status = true; + value->tag = tag_number; + if (tag_number == BACNET_APPLICATION_TAG_BOOLEAN) { + long_value = strtol(argv, NULL, 0); + if (long_value) + value->type.Boolean = true; + else + value->type.Boolean = false; + } else if (tag_number == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + unsigned_long_value = strtoul(argv, NULL, 0); + value->type.Unsigned_Int = unsigned_long_value; + } else if (tag_number == BACNET_APPLICATION_TAG_SIGNED_INT) { + long_value = strtol(argv, NULL, 0); + value->type.Signed_Int = long_value; + } else if (tag_number == BACNET_APPLICATION_TAG_REAL) { + double_value = strtod(argv, NULL); + value->type.Real = (float) double_value; + } else if (tag_number == BACNET_APPLICATION_TAG_DOUBLE) { + double_value = strtod(argv, NULL); + value->type.Double = double_value; + } else if (tag_number == BACNET_APPLICATION_TAG_CHARACTER_STRING) { + status = + characterstring_init_ansi(&value->type.Character_String, + (char *) argv); + } else if (tag_number == BACNET_APPLICATION_TAG_OCTET_STRING) { + status = octetstring_init(&value->type.Octet_String, + (uint8_t *) argv, strlen(argv)); + } else if (tag_number == BACNET_APPLICATION_TAG_ENUMERATED) { + unsigned_long_value = strtoul(argv, NULL, 0); + value->type.Enumerated = unsigned_long_value; + } else if (tag_number == BACNET_APPLICATION_TAG_DATE) { + count = + sscanf(argv, "%d/%d/%d:%d", &year, &month, &day, &wday); + if (count == 3) { + datetime_set_date(&value->type.Date, + (uint16_t)year, (uint8_t)month, (uint8_t)day); + } else if (count == 4) { + value->type.Date.year = year; + value->type.Date.month = month; + value->type.Date.day = day; + value->type.Date.wday = wday; + } else + status = false; + } else if (tag_number == BACNET_APPLICATION_TAG_TIME) { + count = + sscanf(argv, "%d:%d:%d.%d", &hour, &min, &sec, + &hundredths); + if (count == 4) { + value->type.Time.hour = hour; + value->type.Time.min = min; + value->type.Time.sec = sec; + value->type.Time.hundredths = hundredths; + } else if (count == 3) { + value->type.Time.hour = hour; + value->type.Time.min = min; + value->type.Time.sec = sec; + value->type.Time.hundredths = 0; + } else if (count == 2) { + value->type.Time.hour = hour; + value->type.Time.min = min; + value->type.Time.sec = 0; + value->type.Time.hundredths = 0; + } else + status = false; + } else if (tag_number == BACNET_APPLICATION_TAG_OBJECT_ID) { + count = sscanf(argv, "%d:%d", &object_type, &instance); + if (count == 2) { + value->type.Object_Id.type = object_type; + value->type.Object_Id.instance = instance; + } else + status = false; + } + value->next = NULL; + } + + return status; +} +#endif + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testBACnetApplicationDataLength(Test * pTest) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + int len = 0; /* total length of the apdu, return value */ + int test_len = 0; /* length of the data */ + uint8_t apdu[480] = { 0 }; + BACNET_TIME local_time; + BACNET_DATE local_date; + + /* create some constructed data */ + /* 1. zero elements */ + test_len = 0; + apdu_len = 0; + len = encode_opening_tag(&apdu[apdu_len], 3); + apdu_len += len; + len = encode_closing_tag(&apdu[apdu_len], 3); + apdu_len += len; + /* verify the length of the data inside the opening/closing tags */ + len = bacapp_data_len(&apdu[0], apdu_len, + PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES); + ct_test(pTest, test_len == len); + + /* 2. application tagged data, one element */ + test_len = 0; + apdu_len = 0; + len = encode_opening_tag(&apdu[apdu_len], 3); + apdu_len += len; + len = encode_tagged_unsigned(&apdu[apdu_len], 4194303); + test_len += len; + apdu_len += len; + len = encode_closing_tag(&apdu[apdu_len], 3); + apdu_len += len; + /* verify the length of the data inside the opening/closing tags */ + len = bacapp_data_len(&apdu[0], apdu_len, PROP_OBJECT_IDENTIFIER); + ct_test(pTest, test_len == len); + + /* 3. application tagged data, multiple elements */ + test_len = 0; + apdu_len = 0; + len = encode_opening_tag(&apdu[apdu_len], 3); + apdu_len += len; + len = encode_tagged_null(&apdu[apdu_len]); + test_len += len; + apdu_len += len; + len = encode_tagged_null(&apdu[apdu_len]); + test_len += len; + apdu_len += len; + len = encode_tagged_unsigned(&apdu[apdu_len], 1); + test_len += len; + apdu_len += len; + len = encode_tagged_unsigned(&apdu[apdu_len], 42); + test_len += len; + apdu_len += len; + len = encode_tagged_unsigned(&apdu[apdu_len], 91); + test_len += len; + apdu_len += len; + len = encode_tagged_null(&apdu[apdu_len]); + test_len += len; + apdu_len += len; + len = encode_tagged_null(&apdu[apdu_len]); + test_len += len; + apdu_len += len; + len = encode_tagged_null(&apdu[apdu_len]); + test_len += len; + apdu_len += len; + len = encode_tagged_null(&apdu[apdu_len]); + test_len += len; + apdu_len += len; + len = encode_tagged_null(&apdu[apdu_len]); + test_len += len; + apdu_len += len; + len = encode_tagged_null(&apdu[apdu_len]); + test_len += len; + apdu_len += len; + len = encode_tagged_null(&apdu[apdu_len]); + test_len += len; + apdu_len += len; + len = encode_tagged_null(&apdu[apdu_len]); + test_len += len; + apdu_len += len; + len = encode_tagged_null(&apdu[apdu_len]); + test_len += len; + apdu_len += len; + len = encode_tagged_null(&apdu[apdu_len]); + test_len += len; + apdu_len += len; + len = encode_tagged_null(&apdu[apdu_len]); + test_len += len; + apdu_len += len; + len = encode_closing_tag(&apdu[apdu_len], 3); + apdu_len += len; + /* verify the length of the data inside the opening/closing tags */ + len = bacapp_data_len(&apdu[0], apdu_len, PROP_PRIORITY_ARRAY); + ct_test(pTest, test_len == len); + + /* 4. complex datatype - one element */ + test_len = 0; + apdu_len = 0; + len = encode_opening_tag(&apdu[apdu_len], 3); + apdu_len += len; + len = encode_opening_tag(&apdu[apdu_len], 3); + test_len += len; + apdu_len += len; + local_date.year = 2006; /* AD */ + local_date.month = 4; /* 1=Jan */ + local_date.day = 1; /* 1..31 */ + local_date.wday = 6; /* 1=Monday */ + len = encode_tagged_date(&apdu[apdu_len], &local_date); + test_len += len; + apdu_len += len; + local_time.hour = 7; + local_time.min = 0; + local_time.sec = 3; + local_time.hundredths = 1; + len = encode_tagged_time(&apdu[apdu_len], &local_time); + test_len += len; + apdu_len += len; + len = encode_closing_tag(&apdu[apdu_len], 3); + test_len += len; + apdu_len += len; + len = encode_closing_tag(&apdu[apdu_len], 3); + apdu_len += len; + /* verify the length of the data inside the opening/closing tags */ + len = bacapp_data_len(&apdu[0], apdu_len, PROP_START_TIME); + ct_test(pTest, test_len == len); + + /* 5. complex datatype - multiple elements */ + + + + /* 6. context tagged data, one element */ + test_len = 0; + apdu_len = 0; + len = encode_opening_tag(&apdu[apdu_len], 3); + apdu_len += len; + len = encode_context_unsigned(&apdu[apdu_len], 1, 91); + test_len += len; + apdu_len += len; + len = encode_closing_tag(&apdu[apdu_len], 3); + apdu_len += len; + /* verify the length of the data inside the opening/closing tags */ + len = bacapp_data_len(&apdu[0], apdu_len, PROP_REQUESTED_SHED_LEVEL); + ct_test(pTest, test_len == len); +} + +/* generic - can be used by other unit tests + returns true if matching or same, false if different */ +bool bacapp_same_value(BACNET_APPLICATION_DATA_VALUE * value, + BACNET_APPLICATION_DATA_VALUE * test_value) +{ + bool status = true; /*return value */ + + /* does the tag match? */ + if (test_value->tag != value->tag) + status = false; + if (status) { + /* does the value match? */ + switch (test_value->tag) { + case BACNET_APPLICATION_TAG_NULL: + break; + case BACNET_APPLICATION_TAG_BOOLEAN: + if (test_value->type.Boolean != value->type.Boolean) + status = false; + break; + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + if (test_value->type.Unsigned_Int != value->type.Unsigned_Int) + status = false; + break; + case BACNET_APPLICATION_TAG_SIGNED_INT: + if (test_value->type.Signed_Int != value->type.Signed_Int) + status = false; + break; + case BACNET_APPLICATION_TAG_REAL: + if (test_value->type.Real != value->type.Real) + status = false; + break; + case BACNET_APPLICATION_TAG_DOUBLE: + if (test_value->type.Double != value->type.Double) + status = false; + break; + case BACNET_APPLICATION_TAG_ENUMERATED: + if (test_value->type.Enumerated != value->type.Enumerated) + status = false; + break; + case BACNET_APPLICATION_TAG_DATE: + if (datetime_compare_date(&test_value->type.Date, + &value->type.Date) != 0) + status = false; + break; + case BACNET_APPLICATION_TAG_TIME: + if (datetime_compare_time(&test_value->type.Time, + &value->type.Time) != 0) + status = false; + break; + case BACNET_APPLICATION_TAG_OBJECT_ID: + if (test_value->type.Object_Id.type != + value->type.Object_Id.type) + status = false; + if (test_value->type.Object_Id.instance != + value->type.Object_Id.instance) + status = false; + break; + case BACNET_APPLICATION_TAG_CHARACTER_STRING: + status = characterstring_same(&value->type.Character_String, + &test_value->type.Character_String); + break; + case BACNET_APPLICATION_TAG_BIT_STRING: + default: + status = false; + break; + } + } + return status; +} + +static bool testBACnetApplicationDataValue(BACNET_APPLICATION_DATA_VALUE * + value) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + BACNET_APPLICATION_DATA_VALUE test_value; + + apdu_len = bacapp_encode_application_data(&apdu[0], value); + len = bacapp_decode_application_data(&apdu[0], apdu_len, &test_value); + + return bacapp_same_value(value, &test_value); +} + +void testBACnetApplicationData(Test * pTest) +{ + BACNET_APPLICATION_DATA_VALUE value; + bool status = false; + + + status = bacapp_parse_application_data(BACNET_APPLICATION_TAG_NULL, + NULL, &value); + ct_test(pTest, status == true); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + + status = bacapp_parse_application_data(BACNET_APPLICATION_TAG_BOOLEAN, + "1", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Boolean == true); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + + status = bacapp_parse_application_data(BACNET_APPLICATION_TAG_BOOLEAN, + "0", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Boolean == false); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + + status = + bacapp_parse_application_data(BACNET_APPLICATION_TAG_UNSIGNED_INT, + "0", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Unsigned_Int == 0); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + status = + bacapp_parse_application_data(BACNET_APPLICATION_TAG_UNSIGNED_INT, + "0xFFFF", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Unsigned_Int == 0xFFFF); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + status = + bacapp_parse_application_data(BACNET_APPLICATION_TAG_UNSIGNED_INT, + "0xFFFFFFFF", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Unsigned_Int == 0xFFFFFFFF); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + + status = + bacapp_parse_application_data(BACNET_APPLICATION_TAG_SIGNED_INT, + "0", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Signed_Int == 0); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + status = + bacapp_parse_application_data(BACNET_APPLICATION_TAG_SIGNED_INT, + "-1", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Signed_Int == -1); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + status = + bacapp_parse_application_data(BACNET_APPLICATION_TAG_SIGNED_INT, + "32768", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Signed_Int == 32768); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + status = + bacapp_parse_application_data(BACNET_APPLICATION_TAG_SIGNED_INT, + "-32768", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Signed_Int == -32768); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + + status = bacapp_parse_application_data(BACNET_APPLICATION_TAG_REAL, + "0.0", &value); + ct_test(pTest, status == true); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + status = bacapp_parse_application_data(BACNET_APPLICATION_TAG_REAL, + "-1.0", &value); + ct_test(pTest, status == true); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + status = bacapp_parse_application_data(BACNET_APPLICATION_TAG_REAL, + "1.0", &value); + ct_test(pTest, status == true); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + status = bacapp_parse_application_data(BACNET_APPLICATION_TAG_REAL, + "3.14159", &value); + ct_test(pTest, status == true); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + status = bacapp_parse_application_data(BACNET_APPLICATION_TAG_REAL, + "-3.14159", &value); + ct_test(pTest, status == true); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + + status = + bacapp_parse_application_data(BACNET_APPLICATION_TAG_ENUMERATED, + "0", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Enumerated == 0); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + status = + bacapp_parse_application_data(BACNET_APPLICATION_TAG_ENUMERATED, + "0xFFFF", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Enumerated == 0xFFFF); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + status = + bacapp_parse_application_data(BACNET_APPLICATION_TAG_ENUMERATED, + "0xFFFFFFFF", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Enumerated == 0xFFFFFFFF); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + + status = bacapp_parse_application_data(BACNET_APPLICATION_TAG_DATE, + "2005/5/22:1", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Date.year == 2005); + ct_test(pTest, value.type.Date.month == 5); + ct_test(pTest, value.type.Date.day == 22); + ct_test(pTest, value.type.Date.wday == 1); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + + /* Happy Valentines Day! */ + status = bacapp_parse_application_data(BACNET_APPLICATION_TAG_DATE, + "2007/2/14", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Date.year == 2007); + ct_test(pTest, value.type.Date.month == 2); + ct_test(pTest, value.type.Date.day == 14); + ct_test(pTest, value.type.Date.wday == BACNET_WEEKDAY_WEDNESDAY); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + + /* Wildcard Values */ + status = bacapp_parse_application_data(BACNET_APPLICATION_TAG_DATE, + "2155/255/255:255", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Date.year == 2155); + ct_test(pTest, value.type.Date.month == 255); + ct_test(pTest, value.type.Date.day == 255); + ct_test(pTest, value.type.Date.wday == 255); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + + status = bacapp_parse_application_data(BACNET_APPLICATION_TAG_TIME, + "23:59:59.12", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Time.hour == 23); + ct_test(pTest, value.type.Time.min == 59); + ct_test(pTest, value.type.Time.sec == 59); + ct_test(pTest, value.type.Time.hundredths == 12); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + + status = bacapp_parse_application_data(BACNET_APPLICATION_TAG_TIME, + "23:59:59", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Time.hour == 23); + ct_test(pTest, value.type.Time.min == 59); + ct_test(pTest, value.type.Time.sec == 59); + ct_test(pTest, value.type.Time.hundredths == 0); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + + status = bacapp_parse_application_data(BACNET_APPLICATION_TAG_TIME, + "23:59", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Time.hour == 23); + ct_test(pTest, value.type.Time.min == 59); + ct_test(pTest, value.type.Time.sec == 0); + ct_test(pTest, value.type.Time.hundredths == 0); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + + status = + bacapp_parse_application_data(BACNET_APPLICATION_TAG_OBJECT_ID, + "0:100", &value); + ct_test(pTest, status == true); + ct_test(pTest, value.type.Object_Id.type == 0); + ct_test(pTest, value.type.Object_Id.instance == 100); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + + status = + bacapp_parse_application_data + (BACNET_APPLICATION_TAG_CHARACTER_STRING, "Karg!", &value); + ct_test(pTest, status == true); + ct_test(pTest, testBACnetApplicationDataValue(&value)); + + return; +} + +#ifdef TEST_BACNET_APPLICATION_DATA +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Application Data", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testBACnetApplicationData); + assert(rc); + rc = ct_addTestFunction(pTest, testBACnetApplicationDataLength); + assert(rc); + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_BACNET_APPLICATION_DATA */ +#endif /* TEST */ diff --git a/bacnet-stack/bacapp.h b/bacnet-stack/bacapp.h index e69de29b..c63410ea 100644 --- a/bacnet-stack/bacapp.h +++ b/bacnet-stack/bacapp.h @@ -0,0 +1,136 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef BACAPP_H +#define BACAPP_H + +#include +#include +#include +#include "bacdef.h" +#include "bacstr.h" +#include "datetime.h" + +struct BACnet_Application_Data_Value; +typedef struct BACnet_Application_Data_Value { + bool context_specific; /* true if context specific data */ + uint8_t context_tag; /* only used for context specific data */ + uint8_t tag; /* application tag data type */ + union { + /* NULL - not needed as it is encoded in the tag alone */ + bool Boolean; + uint32_t Unsigned_Int; + int32_t Signed_Int; + float Real; + double Double; + BACNET_OCTET_STRING Octet_String; + BACNET_CHARACTER_STRING Character_String; + BACNET_BIT_STRING Bit_String; + int Enumerated; + BACNET_DATE Date; + BACNET_TIME Time; + BACNET_OBJECT_ID Object_Id; + } type; + /* simple linked list if needed */ + struct BACnet_Application_Data_Value *next; +} BACNET_APPLICATION_DATA_VALUE; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + int bacapp_encode_data(uint8_t * apdu, + BACNET_APPLICATION_DATA_VALUE * value); + + int bacapp_decode_application_data(uint8_t * apdu, + int max_apdu_len, BACNET_APPLICATION_DATA_VALUE * value); + + int bacapp_encode_application_data(uint8_t * apdu, + BACNET_APPLICATION_DATA_VALUE * value); + + int bacapp_decode_context_data(uint8_t * apdu, + int max_apdu_len, BACNET_APPLICATION_DATA_VALUE * value, + BACNET_PROPERTY_ID property); + + int bacapp_encode_context_data(uint8_t * apdu, + BACNET_APPLICATION_DATA_VALUE * value, + BACNET_PROPERTY_ID property); + + int bacapp_encode_context_data_value(uint8_t * apdu, + uint8_t context_tag_number, BACNET_APPLICATION_DATA_VALUE * value); + + BACNET_APPLICATION_TAG bacapp_context_tag_type(BACNET_PROPERTY_ID + property, uint8_t tag_number); + + bool bacapp_copy(BACNET_APPLICATION_DATA_VALUE * dest_value, + BACNET_APPLICATION_DATA_VALUE * src_value); + + /* returns the length of data between an opening tag and a closing tag. + Expects that the first octet contain the opening tag. + Include a value property identifier for context specific data + such as the value received in a WriteProperty request */ + int bacapp_data_len(uint8_t * apdu, int max_apdu_len, + BACNET_PROPERTY_ID property); + +#if PRINT_ENABLED +#define BACAPP_PRINT_ENABLED +#else +#ifdef TEST +#define BACAPP_PRINT_ENABLED +#endif +#endif + +#ifdef BACAPP_PRINT_ENABLED + bool bacapp_parse_application_data(BACNET_APPLICATION_TAG tag_number, + const char *argv, BACNET_APPLICATION_DATA_VALUE * value); + bool bacapp_print_value(FILE * stream, + BACNET_APPLICATION_DATA_VALUE * value, + BACNET_PROPERTY_ID property); +#else +#define bacapp_parse_application_data(x,y,z) {(void)x;(void)y;(void)z;} +#define bacapp_print_value(x,y,z) {(void)x;(void)y;(void)z;} +#endif + +#ifdef TEST +#include "ctest.h" +#include "datetime.h" + bool bacapp_same_value(BACNET_APPLICATION_DATA_VALUE * value, + BACNET_APPLICATION_DATA_VALUE * test_value); + + void testBACnetApplicationDataLength(Test * pTest); + void testBACnetApplicationData(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/bacdcode.c b/bacnet-stack/bacdcode.c index e69de29b..080afc1b 100644 --- a/bacnet-stack/bacdcode.c +++ b/bacnet-stack/bacdcode.c @@ -0,0 +1,2162 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#include + +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bits.h" +#include "bacstr.h" + +/* NOTE: byte order plays a role in decoding multibyte values */ +/* http://www.unixpapa.com/incnote/byteorder.html */ +#ifndef BIG_ENDIAN + #error Define BIG_ENDIAN=0 or BIG_ENDIAN=1 for BACnet Stack in compiler settings +#endif + +/* max-segments-accepted + B'000' Unspecified number of segments accepted. + B'001' 2 segments accepted. + B'010' 4 segments accepted. + B'011' 8 segments accepted. + B'100' 16 segments accepted. + B'101' 32 segments accepted. + B'110' 64 segments accepted. + B'111' Greater than 64 segments accepted. +*/ + +/* max-APDU-length-accepted + B'0000' Up to MinimumMessageSize (50 octets) + B'0001' Up to 128 octets + B'0010' Up to 206 octets (fits in a LonTalk frame) + B'0011' Up to 480 octets (fits in an ARCNET frame) + B'0100' Up to 1024 octets + B'0101' Up to 1476 octets (fits in an ISO 8802-3 frame) + B'0110' reserved by ASHRAE + B'0111' reserved by ASHRAE + B'1000' reserved by ASHRAE + B'1001' reserved by ASHRAE + B'1010' reserved by ASHRAE + B'1011' reserved by ASHRAE + B'1100' reserved by ASHRAE + B'1101' reserved by ASHRAE + B'1110' reserved by ASHRAE + B'1111' reserved by ASHRAE +*/ +/* from clause 20.1.2.4 max-segments-accepted */ +/* and clause 20.1.2.5 max-APDU-length-accepted */ +/* returns the encoded octet */ +uint8_t encode_max_segs_max_apdu(int max_segs, int max_apdu) +{ + uint8_t octet = 0; + + if (max_segs < 2) + octet = 0; + else if (max_segs < 4) + octet = 0x10; + else if (max_segs < 8) + octet = 0x20; + else if (max_segs < 16) + octet = 0x30; + else if (max_segs < 32) + octet = 0x40; + else if (max_segs < 64) + octet = 0x50; + else if (max_segs == 64) + octet = 0x60; + else + octet = 0x70; + + /* max_apdu must be 50 octets minimum */ + if (max_apdu <= 50) + octet |= 0x00; + else if (max_apdu <= 128) + octet |= 0x01; + /*fits in a LonTalk frame */ + else if (max_apdu <= 206) + octet |= 0x02; + /*fits in an ARCNET or MS/TP frame */ + else if (max_apdu <= 480) + octet |= 0x03; + else if (max_apdu <= 1024) + octet |= 0x04; + /* fits in an ISO 8802-3 frame */ + else if (max_apdu <= 1476) + octet |= 0x05; + + return octet; +} + +/* from clause 20.1.2.4 max-segments-accepted */ +/* and clause 20.1.2.5 max-APDU-length-accepted */ +/* returns the encoded octet */ +int decode_max_segs(uint8_t octet) +{ + int max_segs = 0; + + switch (octet & 0xF0) { + case 0: + max_segs = 0; + break; + case 0x10: + max_segs = 2; + break; + case 0x20: + max_segs = 4; + break; + case 0x30: + max_segs = 8; + break; + case 0x40: + max_segs = 16; + break; + case 0x50: + max_segs = 32; + break; + case 0x60: + max_segs = 64; + break; + case 0x70: + max_segs = 65; + break; + default: + break; + } + + return max_segs; +} + +int decode_max_apdu(uint8_t octet) +{ + int max_apdu = 0; + + switch (octet & 0x0F) { + case 0: + max_apdu = 50; + break; + case 1: + max_apdu = 128; + break; + case 2: + max_apdu = 206; + break; + case 3: + max_apdu = 480; + break; + case 4: + max_apdu = 1024; + break; + case 5: + max_apdu = 1476; + break; + default: + break; + } + + return max_apdu; +} + +int encode_unsigned16(uint8_t * apdu, uint16_t value) +{ + union { + uint8_t byte[2]; + uint16_t value; + } short_data = { { + 0}}; + + short_data.value = value; +#if BIG_ENDIAN + apdu[0] = short_data.byte[0]; + apdu[1] = short_data.byte[1]; +#else + apdu[0] = short_data.byte[1]; + apdu[1] = short_data.byte[0]; +#endif + + return 2; +} + +int decode_unsigned16(uint8_t * apdu, uint16_t * value) +{ + union { + uint8_t byte[2]; + uint16_t value; + } short_data = { { + 0}}; + +#if BIG_ENDIAN + short_data.byte[0] = apdu[0]; + short_data.byte[1] = apdu[1]; +#else + short_data.byte[1] = apdu[0]; + short_data.byte[0] = apdu[1]; +#endif + if (value) + *value = short_data.value; + + return 2; +} + +int encode_unsigned24(uint8_t * apdu, uint32_t value) +{ + union { + uint8_t byte[4]; + uint32_t value; + } long_data = { { + 0}}; + + long_data.value = value; +#if BIG_ENDIAN + apdu[0] = long_data.byte[1]; + apdu[1] = long_data.byte[2]; + apdu[2] = long_data.byte[3]; +#else + apdu[0] = long_data.byte[2]; + apdu[1] = long_data.byte[1]; + apdu[2] = long_data.byte[0]; +#endif + + return 3; +} + +int decode_unsigned24(uint8_t * apdu, uint32_t * value) +{ + union { + uint8_t byte[4]; + uint32_t value; + } long_data = { { + 0}}; + +#if BIG_ENDIAN + long_data.byte[1] = apdu[0]; + long_data.byte[2] = apdu[1]; + long_data.byte[3] = apdu[2]; +#else + long_data.byte[2] = apdu[0]; + long_data.byte[1] = apdu[1]; + long_data.byte[0] = apdu[2]; +#endif + if (value) + *value = long_data.value; + + return 3; +} + +int encode_unsigned32(uint8_t * apdu, uint32_t value) +{ + union { + uint8_t byte[4]; + uint32_t value; + } long_data = { { + 0}}; + + long_data.value = value; +#if BIG_ENDIAN + apdu[0] = long_data.byte[0]; + apdu[1] = long_data.byte[1]; + apdu[2] = long_data.byte[2]; + apdu[3] = long_data.byte[3]; +#else + apdu[0] = long_data.byte[3]; + apdu[1] = long_data.byte[2]; + apdu[2] = long_data.byte[1]; + apdu[3] = long_data.byte[0]; +#endif + + return 4; +} + +int decode_unsigned32(uint8_t * apdu, uint32_t * value) +{ + union { + uint8_t byte[4]; + uint32_t value; + } long_data = { { + 0}}; + +#if BIG_ENDIAN + long_data.byte[0] = apdu[0]; + long_data.byte[1] = apdu[1]; + long_data.byte[2] = apdu[2]; + long_data.byte[3] = apdu[3]; +#else + long_data.byte[3] = apdu[0]; + long_data.byte[2] = apdu[1]; + long_data.byte[1] = apdu[2]; + long_data.byte[0] = apdu[3]; +#endif + if (value) + *value = long_data.value; + + return 4; +} + +/* returns the number of APDU octets used */ +int encode_signed8(uint8_t * apdu, int8_t value) +{ + union { + uint8_t byte; + int8_t value; + } byte_data = { + 0}; + + byte_data.value = value; + apdu[0] = byte_data.byte; + + return 1; +} + +/* returns the number of APDU octets consumed */ +int decode_signed8(uint8_t * apdu, int32_t * value) +{ + union { + uint8_t byte[4]; + int32_t value; + } long_data = { { + 0}}; + +#if BIG_ENDIAN + /* negative - bit 7 is set */ + if (apdu[0] & 0x80) { + long_data.byte[0] = 0xFF; + long_data.byte[1] = 0xFF; + long_data.byte[2] = 0xFF; + } + /* fill in the rest */ + long_data.byte[3] = apdu[0]; +#else + /* negative - bit 7 is set */ + if (apdu[0] & 0x80) { + long_data.byte[3] = 0xFF; + long_data.byte[2] = 0xFF; + long_data.byte[1] = 0xFF; + } + /* fill in the rest */ + long_data.byte[0] = apdu[0]; +#endif + if (value) + *value = long_data.value; + + return 1; +} + +/* returns the number of APDU octets used */ +int encode_signed16(uint8_t * apdu, int16_t value) +{ + union { + uint8_t byte[2]; + int16_t value; + } short_data = { { + 0}}; + + short_data.value = value; +#if BIG_ENDIAN + apdu[0] = short_data.byte[0]; + apdu[1] = short_data.byte[1]; +#else + apdu[0] = short_data.byte[1]; + apdu[1] = short_data.byte[0]; +#endif + + return 2; +} + +/* returns the number of APDU octets consumed */ +int decode_signed16(uint8_t * apdu, int32_t * value) +{ + union { + uint8_t byte[4]; + int32_t value; + } long_data = { { + 0}}; + +#if BIG_ENDIAN + /* negative - bit 7 is set */ + if (apdu[0] & 0x80) { + long_data.byte[0] = 0xFF; + long_data.byte[1] = 0xFF; + } + /* fill in the rest */ + long_data.byte[2] = apdu[0]; + long_data.byte[3] = apdu[1]; +#else + /* negative - bit 7 is set */ + if (apdu[0] & 0x80) { + long_data.byte[3] = 0xFF; + long_data.byte[2] = 0xFF; + } + /* fill in the rest */ + long_data.byte[1] = apdu[0]; + long_data.byte[0] = apdu[1]; +#endif + if (value) + *value = long_data.value; + + return 2; +} + +int encode_signed24(uint8_t * apdu, int32_t value) +{ + union { + uint8_t byte[4]; + int32_t value; + } long_data = { { + 0}}; + + long_data.value = value; +#if BIG_ENDIAN + apdu[0] = long_data.byte[1]; + apdu[1] = long_data.byte[2]; + apdu[2] = long_data.byte[3]; +#else + apdu[0] = long_data.byte[2]; + apdu[1] = long_data.byte[1]; + apdu[2] = long_data.byte[0]; +#endif + + return 3; +} + +int decode_signed24(uint8_t * apdu, int32_t * value) +{ + union { + uint8_t byte[4]; + int32_t value; + } long_data = { { + 0}}; + +#if BIG_ENDIAN + /* negative - bit 7 is set */ + if (apdu[0] & 0x80) + long_data.byte[0] = 0xFF; + /* fill in the rest */ + long_data.byte[1] = apdu[0]; + long_data.byte[2] = apdu[1]; + long_data.byte[3] = apdu[2]; +#else + /* negative - bit 7 is set */ + if (apdu[0] & 0x80) + long_data.byte[3] = 0xFF; + /* fill in the rest */ + long_data.byte[2] = apdu[0]; + long_data.byte[1] = apdu[1]; + long_data.byte[0] = apdu[2]; +#endif + if (value) + *value = long_data.value; + + return 3; +} + +int encode_signed32(uint8_t * apdu, int32_t value) +{ + union { + uint8_t byte[4]; + int32_t value; + } long_data = { { + 0}}; + + long_data.value = value; +#if BIG_ENDIAN + apdu[0] = long_data.byte[0]; + apdu[1] = long_data.byte[1]; + apdu[2] = long_data.byte[2]; + apdu[3] = long_data.byte[3]; +#else + apdu[0] = long_data.byte[3]; + apdu[1] = long_data.byte[2]; + apdu[2] = long_data.byte[1]; + apdu[3] = long_data.byte[0]; +#endif + + return 4; +} + +int decode_signed32(uint8_t * apdu, int32_t * value) +{ + union { + uint8_t byte[4]; + int32_t value; + } long_data = { { + 0}}; + +#if BIG_ENDIAN + long_data.byte[0] = apdu[0]; + long_data.byte[1] = apdu[1]; + long_data.byte[2] = apdu[2]; + long_data.byte[3] = apdu[3]; +#else + long_data.byte[3] = apdu[0]; + long_data.byte[2] = apdu[1]; + long_data.byte[1] = apdu[2]; + long_data.byte[0] = apdu[3]; +#endif + if (value) + *value = long_data.value; + + return 4; +} + +/* from clause 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_tag(uint8_t * apdu, uint8_t tag_number, bool context_specific, + uint32_t len_value_type) +{ + int len = 1; /* return value */ + + apdu[0] = 0; + if (context_specific) + apdu[0] = BIT3; + + /* additional tag byte after this byte */ + /* for extended tag byte */ + if (tag_number <= 14) + apdu[0] |= (tag_number << 4); + else { + apdu[0] |= 0xF0; + apdu[1] = tag_number; + len++; + } + + /* NOTE: additional len byte(s) after extended tag byte */ + /* if larger than 4 */ + if (len_value_type <= 4) + apdu[0] |= len_value_type; + else { + apdu[0] |= 5; + if (len_value_type <= 253) { + apdu[len++] = (uint8_t) len_value_type; + } else if (len_value_type <= 65535) { + apdu[len++] = 254; + len += + encode_unsigned16(&apdu[len], (uint16_t) len_value_type); + } else { + apdu[len++] = 255; + len += encode_unsigned32(&apdu[len], len_value_type); + } + } + + return len; +} + +/* from clause 20.2.1.3.2 Constructed Data */ +/* returns the number of apdu bytes consumed */ +int encode_opening_tag(uint8_t * apdu, uint8_t tag_number) +{ + int len = 1; + + /* set class field to context specific */ + apdu[0] = BIT3; + /* additional tag byte after this byte for extended tag byte */ + if (tag_number <= 14) + apdu[0] |= (tag_number << 4); + else { + apdu[0] |= 0xF0; + apdu[1] = tag_number; + len++; + } + /* set type field to opening tag */ + apdu[0] |= 6; + + return len; +} + +/* from clause 20.2.1.3.2 Constructed Data */ +/* returns the number of apdu bytes consumed */ +int encode_closing_tag(uint8_t * apdu, uint8_t tag_number) +{ + int len = 1; + + /* set class field to context specific */ + apdu[0] = BIT3; + /* additional tag byte after this byte for extended tag byte */ + if (tag_number <= 14) + apdu[0] |= (tag_number << 4); + else { + apdu[0] |= 0xF0; + apdu[1] = tag_number; + len++; + } + /* set type field to closing tag */ + apdu[0] |= 7; + + return len; +} + +/* from clause 20.2.1.3.2 Constructed Data */ +/* returns true if extended tag numbering is used */ +static bool decode_is_extended_tag_number(uint8_t * apdu) +{ + return ((apdu[0] & 0xF0) == 0xF0); +} + +/* from clause 20.2.1.3.2 Constructed Data */ +/* returns true if the extended value is used */ +static bool decode_is_extended_value(uint8_t * apdu) +{ + return ((apdu[0] & 0x07) == 5); +} + +/* from clause 20.2.1.3.2 Constructed Data */ +/* returns true if the tag is context specific */ +bool decode_is_context_specific(uint8_t * apdu) +{ + return ((apdu[0] & BIT3) == BIT3); +} + +int decode_tag_number(uint8_t * apdu, uint8_t * tag_number) +{ + int len = 1; /* return value */ + + /* decode the tag number first */ + if (decode_is_extended_tag_number(&apdu[0])) { + /* extended tag */ + if (tag_number) + *tag_number = apdu[1]; + len++; + } else { + if (tag_number) + *tag_number = (apdu[0] >> 4); + } + + return len; +} + +bool decode_is_opening_tag(uint8_t * apdu) +{ + return ((apdu[0] & 0x07) == 6); +} + +/* from clause 20.2.1.3.2 Constructed Data */ +/* returns the number of apdu bytes consumed */ +bool decode_is_closing_tag(uint8_t * apdu) +{ + return ((apdu[0] & 0x07) == 7); +} + +/* from clause 20.2.1.3.2 Constructed Data */ +/* returns the number of apdu bytes consumed */ +int decode_tag_number_and_value(uint8_t * apdu, + uint8_t * tag_number, uint32_t * value) +{ + int len = 1; + uint16_t value16; + uint32_t value32; + + len = decode_tag_number(&apdu[0], tag_number); + /* decode the value */ + if (decode_is_extended_value(&apdu[0])) { + /* tagged as uint32_t */ + if (apdu[len] == 255) { + len++; + len += decode_unsigned32(&apdu[len], &value32); + if (value) + *value = value32; + } + /* tagged as uint16_t */ + else if (apdu[len] == 254) { + len++; + len += decode_unsigned16(&apdu[len], &value16); + if (value) + *value = value16; + } + /* no tag - must be uint8_t */ + else { + if (value) + *value = apdu[len]; + len++; + } + } else if (decode_is_opening_tag(&apdu[0]) && value) + *value = 0; + /* closing tag */ + else if (decode_is_closing_tag(&apdu[0]) && value) + *value = 0; + /* small value */ + else if (value) + *value = apdu[0] & 0x07; + + return len; +} + +/* from clause 20.2.1.3.2 Constructed Data */ +/* returns true if the tag is context specific and matches */ +bool decode_is_context_tag(uint8_t * apdu, uint8_t tag_number) +{ + uint8_t my_tag_number = 0; + bool context_specific = false; + + context_specific = decode_is_context_specific(apdu); + decode_tag_number(apdu, &my_tag_number); + + return (context_specific && (my_tag_number == tag_number)); +} + +/* from clause 20.2.1.3.2 Constructed Data */ +/* returns the number of apdu bytes consumed */ +bool decode_is_opening_tag_number(uint8_t * apdu, uint8_t tag_number) +{ + uint8_t my_tag_number = 0; + bool opening_tag = false; + + opening_tag = ((apdu[0] & 0x07) == 6); + decode_tag_number(apdu, &my_tag_number); + + return (opening_tag && (my_tag_number == tag_number)); +} + +/* from clause 20.2.1.3.2 Constructed Data */ +/* returns the number of apdu bytes consumed */ +bool decode_is_closing_tag_number(uint8_t * apdu, uint8_t tag_number) +{ + uint8_t my_tag_number = 0; + bool closing_tag = false; + + closing_tag = ((apdu[0] & 0x07) == 7); + decode_tag_number(apdu, &my_tag_number); + + return (closing_tag && (my_tag_number == tag_number)); +} + +/* from clause 20.2.3 Encoding of a Boolean Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_tagged_boolean(uint8_t * apdu, bool boolean_value) +{ + int len = 0; + uint32_t len_value = 0; + + if (boolean_value) + len_value = 1; + + len = encode_tag(&apdu[0], BACNET_APPLICATION_TAG_BOOLEAN, false, + len_value); + + return len; +} + +/* context tagged is encoded differently */ +int encode_context_boolean(uint8_t * apdu, int tag_number, + bool boolean_value) +{ + int len = 0; /* return value */ + + len = encode_tag(&apdu[0], (uint8_t) tag_number, true, 1); + apdu[len] = boolean_value ? 1 : 0; + len++; + + return len; +} + +bool decode_context_boolean(uint8_t * apdu) +{ + bool boolean_value = false; + + if (apdu[0]) + boolean_value = true; + + return boolean_value; +} + +/* from clause 20.2.3 Encoding of a Boolean Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +bool decode_boolean(uint32_t len_value) +{ + bool boolean_value = false; + + if (len_value) + boolean_value = true; + + return boolean_value; +} + +/* from clause 20.2.2 Encoding of a Null Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_tagged_null(uint8_t * apdu) +{ + return encode_tag(&apdu[0], BACNET_APPLICATION_TAG_NULL, false, 0); +} + +int encode_context_null(uint8_t * apdu, int tag_number) +{ + return encode_tag(&apdu[0], (uint8_t)tag_number, true, 0); +} + +static uint8_t byte_reverse_bits(uint8_t in_byte) +{ + uint8_t out_byte = 0; + + if (in_byte & BIT0) + out_byte |= BIT7; + if (in_byte & BIT1) + out_byte |= BIT6; + if (in_byte & BIT2) + out_byte |= BIT5; + if (in_byte & BIT3) + out_byte |= BIT4; + if (in_byte & BIT4) + out_byte |= BIT3; + if (in_byte & BIT5) + out_byte |= BIT2; + if (in_byte & BIT6) + out_byte |= BIT1; + if (in_byte & BIT7) + out_byte |= BIT0; + + return out_byte; +} + +/* from clause 20.2.10 Encoding of a Bit String Value */ +/* returns the number of apdu bytes consumed */ +int decode_bitstring(uint8_t * apdu, uint32_t len_value, + BACNET_BIT_STRING * bit_string) +{ + int len = 0; + uint8_t unused_bits = 0; + uint32_t i = 0; + uint32_t bytes_used = 0; + + + bitstring_init(bit_string); + if (len_value) { + /* the first octet contains the unused bits */ + bytes_used = len_value - 1; + if (bytes_used <= MAX_BITSTRING_BYTES) { + len = 1; + for (i = 0; i < bytes_used; i++) { + bitstring_set_octet(bit_string, (uint8_t) i, + byte_reverse_bits(apdu[len++])); + } + unused_bits = apdu[0] & 0x07; + bitstring_set_bits_used(bit_string, + (uint8_t) bytes_used, unused_bits); + } + } + + return len; +} + +/* from clause 20.2.10 Encoding of a Bit String Value */ +/* returns the number of apdu bytes consumed */ +int encode_bitstring(uint8_t * apdu, BACNET_BIT_STRING * bit_string) +{ + int len = 0; + uint8_t remaining_used_bits = 0; + uint8_t used_bytes = 0; + uint8_t i = 0; + + /* if the bit string is empty, then the first octet shall be zero */ + if (bitstring_bits_used(bit_string) == 0) + apdu[len++] = 0; + else { + used_bytes = bitstring_bytes_used(bit_string); + remaining_used_bits = bitstring_bits_used(bit_string) - + ((used_bytes - 1) * 8); + /* number of unused bits in the subsequent final octet */ + apdu[len++] = 8 - remaining_used_bits; + for (i = 0; i < used_bytes; i++) { + apdu[len++] = + byte_reverse_bits(bitstring_octet(bit_string, i)); + } + } + + return len; +} + +int encode_tagged_bitstring(uint8_t * apdu, BACNET_BIT_STRING * bit_string) +{ + int len = 0; + int bit_string_encoded_length = 1; /* 1 for the bits remaining octet */ + + /* bit string may use more than 1 octet for the tag, so find out how many */ + bit_string_encoded_length += bitstring_bytes_used(bit_string); + len = encode_tag(&apdu[0], BACNET_APPLICATION_TAG_BIT_STRING, false, + bit_string_encoded_length); + len += encode_bitstring(&apdu[len], bit_string); + + return len; +} + +int encode_context_bitstring(uint8_t * apdu, int tag_number, + BACNET_BIT_STRING * bit_string) +{ + int len = 0; + int bit_string_encoded_length = 1; /* 1 for the bits remaining octet */ + + /* bit string may use more than 1 octet for the tag, so find out how many */ + bit_string_encoded_length += bitstring_bytes_used(bit_string); + len = + encode_tag(&apdu[0], (uint8_t)tag_number, true, bit_string_encoded_length); + len += encode_bitstring(&apdu[len], bit_string); + + return len; +} + +/* from clause 20.2.6 Encoding of a Real Number Value */ +/* returns the number of apdu bytes consumed */ +int decode_real(uint8_t * apdu, float *real_value) +{ + union { + uint8_t byte[4]; + float real_value; + } my_data; + + /* NOTE: assumes the compiler stores float as IEEE-754 float */ +#if BIG_ENDIAN + my_data.byte[0] = apdu[0]; + my_data.byte[1] = apdu[1]; + my_data.byte[2] = apdu[2]; + my_data.byte[3] = apdu[3]; +#else + my_data.byte[0] = apdu[3]; + my_data.byte[1] = apdu[2]; + my_data.byte[2] = apdu[1]; + my_data.byte[3] = apdu[0]; +#endif + + *real_value = my_data.real_value; + + return 4; +} + +/* from clause 20.2.6 Encoding of a Real Number Value */ +/* returns the number of apdu bytes consumed */ +int encode_bacnet_real(float value, uint8_t * apdu) +{ + union { + uint8_t byte[4]; + float real_value; + } my_data; + + /* NOTE: assumes the compiler stores float as IEEE-754 float */ + my_data.real_value = value; +#if BIG_ENDIAN + apdu[0] = my_data.byte[0]; + apdu[1] = my_data.byte[1]; + apdu[2] = my_data.byte[2]; + apdu[3] = my_data.byte[3]; +#else + apdu[0] = my_data.byte[3]; + apdu[1] = my_data.byte[2]; + apdu[2] = my_data.byte[1]; + apdu[3] = my_data.byte[0]; +#endif + + return 4; +} + +/* from clause 20.2.6 Encoding of a Real Number Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_tagged_real(uint8_t * apdu, float value) +{ + int len = 0; + + /* assumes that the tag only consumes 1 octet */ + len = encode_bacnet_real(value, &apdu[1]); + len += encode_tag(&apdu[0], BACNET_APPLICATION_TAG_REAL, false, len); + + return len; +} + +int encode_context_real(uint8_t * apdu, int tag_number, float value) +{ + int len = 0; + + /* assumes that the tag only consumes 1 octet */ + len = encode_bacnet_real(value, &apdu[1]); + /* we only reserved 1 byte for encoding the tag - check the limits */ + if (tag_number <= 14) + len += encode_tag(&apdu[0], (uint8_t) tag_number, true, len); + else + len = 0; + + return len; +} + +/* from clause 20.2.14 Encoding of an Object Identifier Value */ +/* returns the number of apdu bytes consumed */ +int decode_object_id(uint8_t * apdu, int *object_type, uint32_t * instance) +{ + uint32_t value = 0; + int len = 0; + + len = decode_unsigned32(apdu, &value); + *object_type = ((value >> BACNET_INSTANCE_BITS) & BACNET_MAX_OBJECT); + *instance = (value & BACNET_MAX_INSTANCE); + + return len; +} + +/* from clause 20.2.14 Encoding of an Object Identifier Value */ +/* returns the number of apdu bytes consumed */ +int encode_bacnet_object_id(uint8_t * apdu, + int object_type, uint32_t instance) +{ + uint32_t value = 0; + uint32_t type = 0; + int len = 0; + + type = object_type; + value = ((type & BACNET_MAX_OBJECT) << BACNET_INSTANCE_BITS) | + (instance & BACNET_MAX_INSTANCE); + len = encode_unsigned32(apdu, value); + + return len; +} + +/* from clause 20.2.14 Encoding of an Object Identifier Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_context_object_id(uint8_t * apdu, + int tag_number, int object_type, uint32_t instance) +{ + int len = 0; + + /* assumes that the tag only consumes 1 octet */ + len = encode_bacnet_object_id(&apdu[1], object_type, instance); + /* we only reserved 1 byte for encoding the tag - check the limits */ + if ((tag_number <= 14) && (len <= 4)) + len += encode_tag(&apdu[0], (uint8_t) tag_number, true, len); + else + len = 0; + + return len; +} + +/* from clause 20.2.14 Encoding of an Object Identifier Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_tagged_object_id(uint8_t * apdu, + int object_type, uint32_t instance) +{ + int len = 0; + + /* assumes that the tag only consumes 1 octet */ + len = encode_bacnet_object_id(&apdu[1], object_type, instance); + len += encode_tag(&apdu[0], BACNET_APPLICATION_TAG_OBJECT_ID, + false, len); + + return len; +} + +/* from clause 20.2.8 Encoding of an Octet String Value */ +/* returns the number of apdu bytes consumed */ +int encode_octet_string(uint8_t * apdu, BACNET_OCTET_STRING * octet_string) +{ + int len = 0; /* return value */ + uint8_t *value; + int i = 0; /* loop counter */ + + if (octet_string) { + /* FIXME: might need to pass in the length of the APDU + to bounds check since it might not be the only data chunk */ + len = octetstring_length(octet_string); + value = octetstring_value(octet_string); + for (i = 0; i < len; i++) { + apdu[i] = value[i]; + } + } + + return len; +} + +/* from clause 20.2.8 Encoding of an Octet String Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_tagged_octet_string(uint8_t * apdu, + BACNET_OCTET_STRING * octet_string) +{ + int apdu_len = 0; + + if (octet_string) { + apdu_len = + encode_tag(&apdu[0], BACNET_APPLICATION_TAG_OCTET_STRING, + false, octetstring_length(octet_string)); + /* FIXME: probably need to pass in the length of the APDU + to bounds check since it might not be the only data chunk */ + if ((apdu_len + octetstring_length(octet_string)) < MAX_APDU) + apdu_len += encode_octet_string(&apdu[apdu_len], octet_string); + else + apdu_len = 0; + } + + return apdu_len; +} + +/* from clause 20.2.8 Encoding of an Octet String Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_context_octet_string(uint8_t * apdu, + int tag_number, BACNET_OCTET_STRING * octet_string) +{ + int apdu_len = 0; + + if (apdu && octet_string) { + apdu_len = encode_tag(&apdu[0], (uint8_t) tag_number, + true, octetstring_length(octet_string)); + if ((apdu_len + octetstring_length(octet_string)) < MAX_APDU) + apdu_len += encode_octet_string(&apdu[apdu_len], octet_string); + else + apdu_len = 0; + } + + return apdu_len; +} + +/* from clause 20.2.8 Encoding of an Octet String Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int decode_octet_string(uint8_t * apdu, uint32_t len_value, + BACNET_OCTET_STRING * octet_string) +{ + int len = 0; /* return value */ + bool status = false; + + status = octetstring_init(octet_string, &apdu[0], len_value); + if (status) + len = len_value; + + return len; +} + +/* from clause 20.2.9 Encoding of a Character String Value */ +/* returns the number of apdu bytes consumed */ +int encode_bacnet_character_string(uint8_t * apdu, + BACNET_CHARACTER_STRING * char_string) +{ + int len, i; + char *pString; + + len = characterstring_length(char_string); + apdu[0] = characterstring_encoding(char_string); + pString = characterstring_value(char_string); + for (i = 0; i < len; i++) { + apdu[1 + i] = pString[i]; + } + + return len + 1 /* for encoding */ ; +} + +/* from clause 20.2.9 Encoding of a Character String Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_tagged_character_string(uint8_t * apdu, + BACNET_CHARACTER_STRING * char_string) +{ + int len = 0; + int string_len = 0; + + string_len = + characterstring_length(char_string) + 1 /* for encoding */ ; + len = + encode_tag(&apdu[0], BACNET_APPLICATION_TAG_CHARACTER_STRING, + false, string_len); + if ((len + string_len) < MAX_APDU) + len += encode_bacnet_character_string(&apdu[len], char_string); + else + len = 0; + + return len; +} + +int encode_context_character_string(uint8_t * apdu, int tag_number, + BACNET_CHARACTER_STRING * char_string) +{ + int len = 0; + int string_len = 0; + + string_len = + characterstring_length(char_string) + 1 /* for encoding */ ; + len += encode_tag(&apdu[0], (uint8_t) tag_number, true, string_len); + if ((len + string_len) < MAX_APDU) + len += encode_bacnet_character_string(&apdu[len], char_string); + else + len = 0; + + return len; +} + +/* from clause 20.2.9 Encoding of a Character String Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int decode_character_string(uint8_t * apdu, uint32_t len_value, + BACNET_CHARACTER_STRING * char_string) +{ + int len = 0; /* return value */ + bool status = false; + + status = characterstring_init(char_string, apdu[0], (char *) &apdu[1], + len_value - 1); + if (status) + len = len_value; + + return len; +} + +/* from clause 20.2.4 Encoding of an Unsigned Integer Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_bacnet_unsigned(uint8_t * apdu, uint32_t value) +{ + int len = 0; /* return value */ + + if (value < 0x100) { + apdu[0] = (uint8_t) value; + len = 1; + } else if (value < 0x10000) { + len = encode_unsigned16(&apdu[0], (uint16_t) value); + } else if (value < 0x1000000) { + len = encode_unsigned24(&apdu[0], value); + } else { + len = encode_unsigned32(&apdu[0], value); + } + + return len; +} + +/* from clause 20.2.4 Encoding of an Unsigned Integer Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_context_unsigned(uint8_t * apdu, int tag_number, uint32_t value) +{ + int len = 0; + + len = encode_bacnet_unsigned(&apdu[1], value); + /* we only reserved 1 byte for encoding the tag - check the limits */ + if ((tag_number <= 14) && (len <= 4)) + len += encode_tag(&apdu[0], (uint8_t) tag_number, true, len); + else + len = 0; + + return len; +} + +/* from clause 20.2.4 Encoding of an Unsigned Integer Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_tagged_unsigned(uint8_t * apdu, uint32_t value) +{ + int len = 0; + + len = encode_bacnet_unsigned(&apdu[1], value); + len += encode_tag(&apdu[0], BACNET_APPLICATION_TAG_UNSIGNED_INT, + false, len); + + return len; +} + +/* from clause 20.2.4 Encoding of an Unsigned Integer Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int decode_unsigned(uint8_t * apdu, uint32_t len_value, uint32_t * value) +{ + uint16_t unsigned16_value = 0; + + if (value) { + switch (len_value) { + case 1: + *value = apdu[0]; + break; + case 2: + decode_unsigned16(&apdu[0], &unsigned16_value); + *value = unsigned16_value; + break; + case 3: + decode_unsigned24(&apdu[0], value); + break; + case 4: + decode_unsigned32(&apdu[0], value); + break; + default: + *value = 0; + break; + } + } + + return len_value; +} + +/* from clause 20.2.11 Encoding of an Enumerated Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int decode_enumerated(uint8_t * apdu, uint32_t len_value, int *value) +{ + uint32_t unsigned_value = 0; + int len; + + len = decode_unsigned(apdu, len_value, &unsigned_value); + if (value) + *value = unsigned_value; + + return len; +} + +/* from clause 20.2.11 Encoding of an Enumerated Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_bacnet_enumerated(uint8_t * apdu, int value) +{ + return encode_bacnet_unsigned(apdu, value); +} + +/* from clause 20.2.11 Encoding of an Enumerated Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_tagged_enumerated(uint8_t * apdu, int value) +{ + int len = 0; /* return value */ + + /* assumes that the tag only consumes 1 octet */ + len = encode_bacnet_enumerated(&apdu[1], value); + len += encode_tag(&apdu[0], BACNET_APPLICATION_TAG_ENUMERATED, + false, len); + + return len; +} + +/* from clause 20.2.11 Encoding of an Enumerated Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_context_enumerated(uint8_t * apdu, int tag_number, int value) +{ + int len = 0; /* return value */ + + /* assumes that the tag only consumes 1 octet */ + len = encode_bacnet_enumerated(&apdu[1], value); + /* we only reserved 1 byte for encoding the tag - check the limits */ + if ((tag_number <= 14) && (len <= 4)) + len += encode_tag(&apdu[0], (uint8_t) tag_number, true, len); + else + len = 0; + + return len; +} + +/* from clause 20.2.5 Encoding of a Signed Integer Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int decode_signed(uint8_t * apdu, uint32_t len_value, int32_t * value) +{ + if (value) { + switch (len_value) { + case 1: + decode_signed8(&apdu[0], value); + break; + case 2: + decode_signed16(&apdu[0], value); + break; + case 3: + decode_signed24(&apdu[0], value); + break; + case 4: + decode_signed32(&apdu[0], value); + break; + default: + *value = 0; + break; + } + } + + return len_value; +} + +/* from clause 20.2.5 Encoding of a Signed Integer Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_bacnet_signed(uint8_t * apdu, int32_t value) +{ + int len = 0; /* return value */ + + /* don't encode the leading X'FF' or X'00' of the two's compliment. + That is, the first octet of any multi-octet encoded value shall + not be X'00' if the most significant bit (bit 7) of the second + octet is 0, and the first octet shall not be X'FF' if the most + significant bit of the second octet is 1. */ + if ((value >= -128) && (value < 128)) { + len = encode_signed8(&apdu[0], (int8_t) value); + } else if ((value >= -32768) && (value < 32768)) { + len = encode_signed16(&apdu[0], (int16_t) value); + } else if ((value > -8388608) && (value < 8388608)) { + len = encode_signed24(&apdu[0], value); + } else { + len = encode_signed32(&apdu[0], value); + } + + return len; +} + +/* from clause 20.2.5 Encoding of a Signed Integer Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_tagged_signed(uint8_t * apdu, int32_t value) +{ + int len = 0; /* return value */ + + /* assumes that the tag only consumes 1 octet */ + len = encode_bacnet_signed(&apdu[1], value); + len += encode_tag(&apdu[0], BACNET_APPLICATION_TAG_SIGNED_INT, + false, len); + + return len; +} + +/* from clause 20.2.5 Encoding of a Signed Integer Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_context_signed(uint8_t * apdu, int tag_number, int32_t value) +{ + int len = 0; /* return value */ + + /* assumes that the tag only consumes 1 octet */ + len = encode_bacnet_signed(&apdu[1], value); + /* we only reserved 1 byte for encoding the tag - check the limits */ + if ((tag_number <= 14) && (len <= 4)) + len += encode_tag(&apdu[0], (uint8_t) tag_number, true, len); + else + len = 0; + + return len; +} + +/* from clause 20.2.13 Encoding of a Time Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_bacnet_time(uint8_t * apdu, BACNET_TIME * btime) +{ + apdu[0] = btime->hour; + apdu[1] = btime->min; + apdu[2] = btime->sec; + apdu[3] = btime->hundredths; + + return 4; +} + +/* from clause 20.2.13 Encoding of a Time Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_tagged_time(uint8_t * apdu, BACNET_TIME * btime) +{ + int len = 0; + + /* assumes that the tag only consumes 1 octet */ + len = encode_bacnet_time(&apdu[1], btime); + len += encode_tag(&apdu[0], BACNET_APPLICATION_TAG_TIME, false, len); + + return len; +} + +int encode_context_time(uint8_t * apdu, int tag_number, + BACNET_TIME * btime) +{ + int len = 0; /* return value */ + + /* assumes that the tag only consumes 1 octet */ + len = encode_bacnet_time(&apdu[1], btime); + /* we only reserved 1 byte for encoding the tag - check the limits */ + if ((tag_number <= 14) && (len <= 4)) + len += encode_tag(&apdu[0], (uint8_t) tag_number, true, len); + else + len = 0; + + return len; +} + +/* from clause 20.2.13 Encoding of a Time Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int decode_bacnet_time(uint8_t * apdu, BACNET_TIME * btime) +{ + btime->hour = apdu[0]; + btime->min = apdu[1]; + btime->sec = apdu[2]; + btime->hundredths = apdu[3]; + + return 4; +} + +/* BACnet Date */ +/* year = years since 1900 */ +/* month 1=Jan */ +/* day = day of month */ +/* wday 1=Monday...7=Sunday */ + +/* from clause 20.2.12 Encoding of a Date Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_bacnet_date(uint8_t * apdu, BACNET_DATE * bdate) +{ + /* allow 2 digit years */ + if (bdate->year < 1900) { + if (bdate->year <= 38) + bdate->year += 2000; + else + bdate->year += 1900; + } + apdu[0] = bdate->year - 1900; + apdu[1] = bdate->month; + apdu[2] = bdate->day; + apdu[3] = bdate->wday; + + return 4; +} + +/* from clause 20.2.12 Encoding of a Date Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_tagged_date(uint8_t * apdu, BACNET_DATE * bdate) +{ + int len = 0; + + /* assumes that the tag only consumes 1 octet */ + len = encode_bacnet_date(&apdu[1], bdate); + len += encode_tag(&apdu[0], BACNET_APPLICATION_TAG_DATE, false, len); + + return len; + +} + +int encode_context_date(uint8_t * apdu, int tag_number, + BACNET_DATE * bdate) +{ + int len = 0; /* return value */ + + /* assumes that the tag only consumes 1 octet */ + len = encode_bacnet_date(&apdu[1], bdate); + /* we only reserved 1 byte for encoding the tag - check the limits */ + if ((tag_number <= 14) && (len <= 4)) + len += encode_tag(&apdu[0], (uint8_t) tag_number, true, len); + else + len = 0; + + return len; +} + +/* from clause 20.2.12 Encoding of a Date Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int decode_date(uint8_t * apdu, BACNET_DATE * bdate) +{ + bdate->year = apdu[0] + 1900; + bdate->month = apdu[1]; + bdate->day = apdu[2]; + bdate->wday = apdu[3]; + + return 4; +} + +/* returns the number of apdu bytes consumed */ +int encode_simple_ack(uint8_t * apdu, uint8_t invoke_id, + uint8_t service_choice) +{ + apdu[0] = PDU_TYPE_SIMPLE_ACK; + apdu[1] = invoke_id; + apdu[2] = service_choice; + + return 3; +} + +/* end of decoding_encoding.c */ +#ifdef TEST +#include +#include +#include +#include "ctest.h" + +static int get_apdu_len(bool extended_tag, uint32_t value) +{ + int test_len = 1; + + if (extended_tag) + test_len++; + if (value <= 4) + test_len += 0; /* do nothing... */ + else if (value <= 253) + test_len += 1; + else if (value <= 65535) + test_len += 3; + else + test_len += 5; + + return test_len; +} + +static void print_apdu(uint8_t * pBlock, uint32_t num) +{ + size_t lines = 0; /* number of lines to print */ + size_t line = 0; /* line of text counter */ + size_t last_line = 0; /* line on which the last text resided */ + unsigned long count = 0; /* address to print */ + unsigned int i = 0; /* counter */ + + if (pBlock && num) { + /* how many lines to print? */ + num--; /* adjust */ + lines = (num / 16) + 1; + last_line = num % 16; + + /* create the line */ + for (line = 0; line < lines; line++) { + /* start with the address */ + printf("%08lX: ", count); + /* hex representation */ + for (i = 0; i < 16; i++) { + if (((line == (lines - 1)) && (i <= last_line)) || + (line != (lines - 1))) { + printf("%02X ", (unsigned) (0x00FF & pBlock[i])); + } else + printf("-- "); + } + printf(" "); + /* print the characters if valid */ + for (i = 0; i < 16; i++) { + if (((line == (lines - 1)) && (i <= last_line)) || + (line != (lines - 1))) { + if (isprint(pBlock[i])) { + printf("%c", pBlock[i]); + } else + printf("."); + } else + printf("."); + } + printf("\r\n"); + pBlock += 16; + count += 16; + } + } + + return; +} + +void testBACDCodeTags(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + uint8_t tag_number = 0, test_tag_number = 0; + int len = 0, test_len = 0; + uint32_t value = 0, test_value = 0; + + for (tag_number = 0;; tag_number++) { + len = encode_opening_tag(&apdu[0], tag_number); + test_len = + get_apdu_len(decode_is_extended_tag_number(&apdu[0]), 0); + ct_test(pTest, len == test_len); + len = + decode_tag_number_and_value(&apdu[0], &test_tag_number, + &value); + ct_test(pTest, value == 0); + ct_test(pTest, len == test_len); + ct_test(pTest, tag_number == test_tag_number); + ct_test(pTest, decode_is_opening_tag(&apdu[0]) == true); + ct_test(pTest, decode_is_closing_tag(&apdu[0]) == false); + len = encode_closing_tag(&apdu[0], tag_number); + ct_test(pTest, len == test_len); + len = + decode_tag_number_and_value(&apdu[0], &test_tag_number, + &value); + ct_test(pTest, len == test_len); + ct_test(pTest, value == 0); + ct_test(pTest, tag_number == test_tag_number); + ct_test(pTest, decode_is_opening_tag(&apdu[0]) == false); + ct_test(pTest, decode_is_closing_tag(&apdu[0]) == true); + /* test the len-value-type portion */ + for (value = 1;; value = value << 1) { + len = encode_tag(&apdu[0], tag_number, false, value); + len = decode_tag_number_and_value(&apdu[0], &test_tag_number, + &test_value); + ct_test(pTest, tag_number == test_tag_number); + ct_test(pTest, value == test_value); + test_len = + get_apdu_len(decode_is_extended_tag_number(&apdu[0]), + value); + ct_test(pTest, len == test_len); + /* stop at the the last value */ + if (value & BIT31) + break; + } + /* stop after the last tag number */ + if (tag_number == 255) + break; + } + + return; +} + +void testBACDCodeReal(Test * pTest) +{ + uint8_t real_array[4] = { 0 }; + uint8_t encoded_array[4] = { 0 }; + float value = 42.123; + float decoded_value = 0; + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0, apdu_len = 0; + uint8_t tag_number = 0; + uint32_t long_value = 0; + + encode_bacnet_real(value, &real_array[0]); + decode_real(&real_array[0], &decoded_value); + ct_test(pTest, decoded_value == value); + encode_bacnet_real(value, &encoded_array[0]); + ct_test(pTest, memcmp(&real_array, &encoded_array, + sizeof(real_array)) == 0); + + /* a real will take up 4 octects plus a one octet tag */ + apdu_len = encode_tagged_real(&apdu[0], value); + ct_test(pTest, apdu_len == 5); + /* len tells us how many octets were used for encoding the value */ + len = decode_tag_number_and_value(&apdu[0], &tag_number, &long_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_REAL); + ct_test(pTest, decode_is_context_specific(&apdu[0]) == false); + ct_test(pTest, len == 1); + ct_test(pTest, long_value == 4); + decode_real(&apdu[len], &decoded_value); + ct_test(pTest, decoded_value == value); + + return; +} + +void testBACDCodeEnumerated(Test * pTest) +{ + uint8_t array[5] = { 0 }; + uint8_t encoded_array[5] = { 0 }; + int value = 1; + int decoded_value = 0; + int i = 0, apdu_len = 0; + int len = 0; + uint8_t apdu[MAX_APDU] = { 0 }; + uint8_t tag_number = 0; + uint32_t len_value = 0; + + for (i = 0; i < 31; i++) { + apdu_len = encode_tagged_enumerated(&array[0], value); + len = + decode_tag_number_and_value(&array[0], &tag_number, + &len_value); + len += decode_enumerated(&array[len], len_value, &decoded_value); + ct_test(pTest, decoded_value == value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_ENUMERATED); + ct_test(pTest, len == apdu_len); + /* encode back the value */ + encode_tagged_enumerated(&encoded_array[0], decoded_value); + ct_test(pTest, memcmp(&array[0], &encoded_array[0], + sizeof(array)) == 0); + /* an enumerated will take up to 4 octects */ + /* plus a one octet for the tag */ + apdu_len = encode_tagged_enumerated(&apdu[0], value); + len = decode_tag_number_and_value(&apdu[0], &tag_number, NULL); + ct_test(pTest, len == 1); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_ENUMERATED); + ct_test(pTest, decode_is_context_specific(&apdu[0]) == false); + /* context specific encoding */ + apdu_len = encode_context_enumerated(&apdu[0], 3, value); + ct_test(pTest, decode_is_context_specific(&apdu[0]) == true); + len = decode_tag_number_and_value(&apdu[0], &tag_number, NULL); + ct_test(pTest, len == 1); + ct_test(pTest, tag_number == 3); + /* test the interesting values */ + value = value << 1; + } + + return; +} + +void testBACDCodeUnsignedValue(Test * pTest, uint32_t value) +{ + uint8_t array[5] = { 0 }; + uint8_t encoded_array[5] = { 0 }; + uint32_t decoded_value = 0; + int len, apdu_len; + uint8_t apdu[MAX_APDU] = { 0 }; + uint8_t tag_number = 0; + uint32_t len_value = 0; + + len_value = encode_tagged_unsigned(&array[0], value); + len = decode_tag_number_and_value(&array[0], &tag_number, &len_value); + len = decode_unsigned(&array[len], len_value, &decoded_value); + ct_test(pTest, decoded_value == value); + if (decoded_value != value) { + printf("value=%u decoded_value=%u\n", value, decoded_value); + print_apdu(&array[0], sizeof(array)); + } + encode_tagged_unsigned(&encoded_array[0], decoded_value); + ct_test(pTest, memcmp(&array[0], &encoded_array[0], + sizeof(array)) == 0); + /* an unsigned will take up to 4 octects */ + /* plus a one octet for the tag */ + apdu_len = encode_tagged_unsigned(&apdu[0], value); + /* apdu_len varies... */ + /*ct_test(pTest, apdu_len == 5); */ + len = decode_tag_number_and_value(&apdu[0], &tag_number, NULL); + ct_test(pTest, len == 1); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_UNSIGNED_INT); + ct_test(pTest, decode_is_context_specific(&apdu[0]) == false); +} + +void testBACDCodeUnsigned(Test * pTest) +{ + uint32_t value = 1; + int i; + + for (i = 0; i < 32; i++) { + testBACDCodeUnsignedValue(pTest, value - 1); + testBACDCodeUnsignedValue(pTest, value); + testBACDCodeUnsignedValue(pTest, value + 1); + value = value << 1; + } + + return; +} + +void testBACDCodeSignedValue(Test * pTest, int32_t value) +{ + uint8_t array[5] = { 0 }; + uint8_t encoded_array[5] = { 0 }; + int32_t decoded_value = 0; + int len = 0, apdu_len = 0; + uint8_t apdu[MAX_APDU] = { 0 }; + uint8_t tag_number = 0; + uint32_t len_value = 0; + int diff = 0; + + len = encode_tagged_signed(&array[0], value); + len = decode_tag_number_and_value(&array[0], &tag_number, &len_value); + len = decode_signed(&array[len], len_value, &decoded_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_SIGNED_INT); + ct_test(pTest, decoded_value == value); + if (decoded_value != value) { + printf("value=%d decoded_value=%d\n", value, decoded_value); + print_apdu(&array[0], sizeof(array)); + } + encode_tagged_signed(&encoded_array[0], decoded_value); + diff = memcmp(&array[0], &encoded_array[0], sizeof(array)); + ct_test(pTest, diff == 0); + if (diff) { + printf("value=%d decoded_value=%d\n", value, decoded_value); + print_apdu(&array[0], sizeof(array)); + print_apdu(&encoded_array[0], sizeof(array)); + } + /* a signed int will take up to 4 octects */ + /* plus a one octet for the tag */ + apdu_len = encode_tagged_signed(&apdu[0], value); + len = decode_tag_number_and_value(&apdu[0], &tag_number, NULL); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_SIGNED_INT); + ct_test(pTest, decode_is_context_specific(&apdu[0]) == false); + + return; +} + +void testBACDCodeSigned(Test * pTest) +{ + int value = 1; + int i = 0; + + for (i = 0; i < 32; i++) { + testBACDCodeSignedValue(pTest, value - 1); + testBACDCodeSignedValue(pTest, value); + testBACDCodeSignedValue(pTest, value + 1); + value = value << 1; + } + + testBACDCodeSignedValue(pTest, -1); + value = -2; + for (i = 0; i < 32; i++) { + testBACDCodeSignedValue(pTest, value - 1); + testBACDCodeSignedValue(pTest, value); + testBACDCodeSignedValue(pTest, value + 1); + value = value << 1; + } + + return; +} + +void testBACDCodeOctetString(Test * pTest) +{ + uint8_t array[MAX_APDU] = { 0 }; + uint8_t encoded_array[MAX_APDU] = { 0 }; + BACNET_OCTET_STRING octet_string; + BACNET_OCTET_STRING test_octet_string; + uint8_t test_value[MAX_APDU] = { "" }; + int i; /* for loop counter */ + int apdu_len; + int len; + uint8_t tag_number = 0; + uint32_t len_value = 0; + bool status = false; + int diff = 0; /* for memcmp */ + + status = octetstring_init(&octet_string, NULL, 0); + ct_test(pTest, status == true); + apdu_len = encode_tagged_octet_string(&array[0], &octet_string); + len = decode_tag_number_and_value(&array[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OCTET_STRING); + len += decode_octet_string(&array[len], len_value, &test_octet_string); + ct_test(pTest, apdu_len == len); + diff = memcmp(octetstring_value(&octet_string), &test_value[0], + octetstring_length(&octet_string)); + ct_test(pTest, diff == 0); + + for (i = 0; i < (MAX_APDU - 6); i++) { + test_value[i] = '0' + (i % 10); + status = octetstring_init(&octet_string, test_value, i); + ct_test(pTest, status == true); + apdu_len = + encode_tagged_octet_string(&encoded_array[0], &octet_string); + len = + decode_tag_number_and_value(&encoded_array[0], &tag_number, + &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OCTET_STRING); + len += decode_octet_string(&encoded_array[len], len_value, + &test_octet_string); + if (apdu_len != len) { + printf("test octet string=#%d\n", i); + } + ct_test(pTest, apdu_len == len); + diff = memcmp(octetstring_value(&octet_string), &test_value[0], + octetstring_length(&octet_string)); + if (diff) { + printf("test octet string=#%d\n", i); + } + ct_test(pTest, diff == 0); + } + + return; +} + +void testBACDCodeCharacterString(Test * pTest) +{ + uint8_t array[MAX_APDU] = { 0 }; + uint8_t encoded_array[MAX_APDU] = { 0 }; + BACNET_CHARACTER_STRING char_string; + BACNET_CHARACTER_STRING test_char_string; + char test_value[MAX_APDU] = { "" }; + int i; /* for loop counter */ + int apdu_len; + int len; + uint8_t tag_number = 0; + uint32_t len_value = 0; + int diff = 0; /* for comparison */ + bool status = false; + + status = characterstring_init(&char_string, + CHARACTER_ANSI_X34, NULL, 0); + ct_test(pTest, status == true); + apdu_len = encode_tagged_character_string(&array[0], &char_string); + len = decode_tag_number_and_value(&array[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_CHARACTER_STRING); + len += + decode_character_string(&array[len], len_value, &test_char_string); + ct_test(pTest, apdu_len == len); + diff = memcmp(characterstring_value(&char_string), &test_value[0], + characterstring_length(&char_string)); + ct_test(pTest, diff == 0); + for (i = 0; i < MAX_CHARACTER_STRING_BYTES - 1; i++) { + test_value[i] = 'S'; + test_value[i + 1] = '\0'; + status = characterstring_init_ansi(&char_string, test_value); + ct_test(pTest, status == true); + apdu_len = + encode_tagged_character_string(&encoded_array[0], + &char_string); + len = + decode_tag_number_and_value(&encoded_array[0], &tag_number, + &len_value); + ct_test(pTest, + tag_number == BACNET_APPLICATION_TAG_CHARACTER_STRING); + len += + decode_character_string(&encoded_array[len], len_value, + &test_char_string); + if (apdu_len != len) { + printf("test string=#%d apdu_len=%d len=%d\n", i, apdu_len, + len); + } + ct_test(pTest, apdu_len == len); + diff = memcmp(characterstring_value(&char_string), &test_value[0], + characterstring_length(&char_string)); + if (diff) { + printf("test string=#%d\n", i); + } + ct_test(pTest, diff == 0); + } + + return; +} + +void testBACDCodeObject(Test * pTest) +{ + uint8_t object_array[4] = { + 0 + }; + uint8_t encoded_array[4] = { + 0 + }; + BACNET_OBJECT_TYPE type = OBJECT_BINARY_INPUT; + BACNET_OBJECT_TYPE decoded_type = OBJECT_ANALOG_OUTPUT; + uint32_t instance = 123; + uint32_t decoded_instance = 0; + + encode_bacnet_object_id(&encoded_array[0], type, instance); + decode_object_id(&encoded_array[0], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == type); + ct_test(pTest, decoded_instance == instance); + encode_bacnet_object_id(&object_array[0], type, instance); + ct_test(pTest, memcmp(&object_array[0], &encoded_array[0], + sizeof(object_array)) == 0); + for (type = 0; type < 1024; type++) { + for (instance = 0; instance <= BACNET_MAX_INSTANCE; + instance += 1024) { + encode_bacnet_object_id(&encoded_array[0], type, instance); + decode_object_id(&encoded_array[0], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == type); + ct_test(pTest, decoded_instance == instance); + encode_bacnet_object_id(&object_array[0], type, instance); + ct_test(pTest, memcmp(&object_array[0], + &encoded_array[0], sizeof(object_array)) == 0); + } + } + + return; +} + +void testBACDCodeMaxSegsApdu(Test * pTest) +{ + int max_segs[8] = { 0, 2, 4, 8, 16, 32, 64, 65 }; + int max_apdu[6] = { 50, 128, 206, 480, 1024, 1476 }; + int i = 0; + int j = 0; + uint8_t octet = 0; + + /* test */ + for (i = 0; i < 8; i++) { + for (j = 0; j < 6; j++) { + octet = encode_max_segs_max_apdu(max_segs[i], max_apdu[j]); + ct_test(pTest, max_segs[i] == decode_max_segs(octet)); + ct_test(pTest, max_apdu[j] == decode_max_apdu(octet)); + } + } +} + +void testBACDCodeBitString(Test * pTest) +{ + uint8_t bit = 0; + BACNET_BIT_STRING bit_string; + BACNET_BIT_STRING decoded_bit_string; + uint8_t apdu[MAX_APDU] = { 0 }; + uint32_t len_value = 0; + uint8_t tag_number = 0; + int len = 0; + + bitstring_init(&bit_string); + /* verify initialization */ + ct_test(pTest, bitstring_bits_used(&bit_string) == 0); + for (bit = 0; bit < (MAX_BITSTRING_BYTES * 8); bit++) { + ct_test(pTest, bitstring_bit(&bit_string, bit) == false); + } + /* test encode/decode -- true */ + for (bit = 0; bit < (MAX_BITSTRING_BYTES * 8); bit++) { + bitstring_set_bit(&bit_string, bit, true); + ct_test(pTest, bitstring_bits_used(&bit_string) == (bit + 1)); + ct_test(pTest, bitstring_bit(&bit_string, bit) == true); + /* encode */ + len = encode_tagged_bitstring(&apdu[0], &bit_string); + /* decode */ + len = + decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_BIT_STRING); + len += + decode_bitstring(&apdu[len], len_value, &decoded_bit_string); + ct_test(pTest, + bitstring_bits_used(&decoded_bit_string) == (bit + 1)); + ct_test(pTest, bitstring_bit(&decoded_bit_string, bit) == true); + } + /* test encode/decode -- false */ + bitstring_init(&bit_string); + for (bit = 0; bit < (MAX_BITSTRING_BYTES * 8); bit++) { + bitstring_set_bit(&bit_string, bit, false); + ct_test(pTest, bitstring_bits_used(&bit_string) == (bit + 1)); + ct_test(pTest, bitstring_bit(&bit_string, bit) == false); + /* encode */ + len = encode_tagged_bitstring(&apdu[0], &bit_string); + /* decode */ + len = + decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_BIT_STRING); + len += + decode_bitstring(&apdu[len], len_value, &decoded_bit_string); + ct_test(pTest, + bitstring_bits_used(&decoded_bit_string) == (bit + 1)); + ct_test(pTest, bitstring_bit(&decoded_bit_string, bit) == false); + } +} + +#ifdef TEST_DECODE +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACDCode", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testBACDCodeTags); + assert(rc); + rc = ct_addTestFunction(pTest, testBACDCodeReal); + assert(rc); + rc = ct_addTestFunction(pTest, testBACDCodeUnsigned); + assert(rc); + rc = ct_addTestFunction(pTest, testBACDCodeSigned); + assert(rc); + rc = ct_addTestFunction(pTest, testBACDCodeEnumerated); + assert(rc); + rc = ct_addTestFunction(pTest, testBACDCodeOctetString); + assert(rc); + rc = ct_addTestFunction(pTest, testBACDCodeCharacterString); + assert(rc); + rc = ct_addTestFunction(pTest, testBACDCodeObject); + assert(rc); + rc = ct_addTestFunction(pTest, testBACDCodeMaxSegsApdu); + assert(rc); + rc = ct_addTestFunction(pTest, testBACDCodeBitString); + assert(rc); + /* configure output */ + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_DECODE */ +#endif /* TEST */ diff --git a/bacnet-stack/bacdcode.h b/bacnet-stack/bacdcode.h index e69de29b..07bc3302 100644 --- a/bacnet-stack/bacdcode.h +++ b/bacnet-stack/bacdcode.h @@ -0,0 +1,213 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef BACDCODE_H +#define BACDCODE_H + +#include +#include +#include +#include "bacdef.h" +#include "datetime.h" +#include "bacstr.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* from clause 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ + int encode_tag(uint8_t * apdu, uint8_t tag_number, + bool context_specific, uint32_t len_value_type); + +/* from clause 20.2.1.3.2 Constructed Data */ +/* returns the number of apdu bytes consumed */ + int encode_opening_tag(uint8_t * apdu, uint8_t tag_number); + int encode_closing_tag(uint8_t * apdu, uint8_t tag_number); + int decode_tag_number(uint8_t * apdu, uint8_t * tag_number); + int decode_tag_number_and_value(uint8_t * apdu, uint8_t * tag_number, + uint32_t * value); +/* returns true if the tag is context specific */ + bool decode_is_context_specific(uint8_t * apdu); +/* returns true if the tag is an opening tag and matches */ + bool decode_is_opening_tag_number(uint8_t * apdu, uint8_t tag_number); +/* returns true if the tag is a closing tag and matches */ + bool decode_is_closing_tag_number(uint8_t * apdu, uint8_t tag_number); +/* returns true if the tag is context specific and matches */ + bool decode_is_context_tag(uint8_t * apdu, uint8_t tag_number); + /* returns true if the tag is an opening tag */ + bool decode_is_opening_tag(uint8_t * apdu); + /* returns true if the tag is a closing tag */ + bool decode_is_closing_tag(uint8_t * apdu); + +/* from clause 20.2.2 Encoding of a Null Value */ + int encode_tagged_null(uint8_t * apdu); + int encode_context_null(uint8_t * apdu, int tag_number); + +/* from clause 20.2.3 Encoding of a Boolean Value */ + int encode_tagged_boolean(uint8_t * apdu, bool boolean_value); + bool decode_boolean(uint32_t len_value); + int encode_context_boolean(uint8_t * apdu, int tag_number, + bool boolean_value); + bool decode_context_boolean(uint8_t * apdu); + +/* from clause 20.2.10 Encoding of a Bit String Value */ +/* returns the number of apdu bytes consumed */ + int decode_bitstring(uint8_t * apdu, uint32_t len_value, + BACNET_BIT_STRING * bit_string); +/* returns the number of apdu bytes consumed */ + int encode_bitstring(uint8_t * apdu, BACNET_BIT_STRING * bit_string); + int encode_tagged_bitstring(uint8_t * apdu, + BACNET_BIT_STRING * bit_string); + int encode_context_bitstring(uint8_t * apdu, int tag_number, + BACNET_BIT_STRING * bit_string); + +/* from clause 20.2.6 Encoding of a Real Number Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ + int decode_real(uint8_t * apdu, float *real_value); + int encode_bacnet_real(float value, uint8_t * apdu); + int encode_tagged_real(uint8_t * apdu, float value); + int encode_context_real(uint8_t * apdu, int tag_number, float value); + +/* from clause 20.2.14 Encoding of an Object Identifier Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ + int decode_object_id(uint8_t * apdu, int *object_type, + uint32_t * instance); + int encode_bacnet_object_id(uint8_t * apdu, int object_type, + uint32_t instance); + int encode_context_object_id(uint8_t * apdu, int tag_number, + int object_type, uint32_t instance); + int encode_tagged_object_id(uint8_t * apdu, int object_type, + uint32_t instance); + +/* from clause 20.2.8 Encoding of an Octet String Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ + int encode_octet_string(uint8_t * apdu, + BACNET_OCTET_STRING * octet_string); + int encode_tagged_octet_string(uint8_t * apdu, + BACNET_OCTET_STRING * octet_string); + int encode_context_octet_string(uint8_t * apdu, + int tag_number, BACNET_OCTET_STRING * octet_string); + int decode_octet_string(uint8_t * apdu, uint32_t len_value, + BACNET_OCTET_STRING * octet_string); + + +/* from clause 20.2.9 Encoding of a Character String Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ + int encode_bacnet_character_string(uint8_t * apdu, + BACNET_CHARACTER_STRING * char_string); + int encode_tagged_character_string(uint8_t * apdu, + BACNET_CHARACTER_STRING * char_string); + int encode_context_character_string(uint8_t * apdu, int tag_number, + BACNET_CHARACTER_STRING * char_string); + int decode_character_string(uint8_t * apdu, uint32_t len_value, + BACNET_CHARACTER_STRING * char_string); + +/* from clause 20.2.4 Encoding of an Unsigned Integer Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ + int encode_bacnet_unsigned(uint8_t * apdu, uint32_t value); + int encode_context_unsigned(uint8_t * apdu, int tag_number, + uint32_t value); + int encode_tagged_unsigned(uint8_t * apdu, uint32_t value); + int decode_unsigned(uint8_t * apdu, uint32_t len_value, + uint32_t * value); + +/* from clause 20.2.5 Encoding of a Signed Integer Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ + int encode_bacnet_signed(uint8_t * apdu, int32_t value); + int encode_tagged_signed(uint8_t * apdu, int32_t value); + int encode_context_signed(uint8_t * apdu, int tag_number, + int32_t value); + int decode_signed(uint8_t * apdu, uint32_t len_value, int32_t * value); + +/* from clause 20.2.11 Encoding of an Enumerated Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ + int decode_enumerated(uint8_t * apdu, uint32_t len_value, int *value); + int encode_bacnet_enumerated(uint8_t * apdu, int value); + int encode_tagged_enumerated(uint8_t * apdu, int value); + int encode_context_enumerated(uint8_t * apdu, int tag_number, + int value); + +/* from clause 20.2.13 Encoding of a Time Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ + int encode_bacnet_time(uint8_t * apdu, BACNET_TIME * btime); + int encode_tagged_time(uint8_t * apdu, BACNET_TIME * btime); + int decode_bacnet_time(uint8_t * apdu, BACNET_TIME * btime); + int encode_context_time(uint8_t * apdu, int tag_number, + BACNET_TIME * btime); + +/* BACnet Date */ +/* year = years since 1900 */ +/* month 1=Jan */ +/* day = day of month */ +/* wday 1=Monday...7=Sunday */ + +/* from clause 20.2.12 Encoding of a Date Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ + int encode_bacnet_date(uint8_t * apdu, BACNET_DATE * bdate); + int encode_tagged_date(uint8_t * apdu, BACNET_DATE * bdate); + int encode_context_date(uint8_t * apdu, int tag_number, + BACNET_DATE * bdate); + int decode_date(uint8_t * apdu, BACNET_DATE * bdate); + +/* two octet unsigned16 */ + int encode_unsigned16(uint8_t * apdu, uint16_t value); + int decode_unsigned16(uint8_t * apdu, uint16_t * value); +/* four octet unsigned32 */ + int encode_unsigned32(uint8_t * apdu, uint32_t value); + int decode_unsigned32(uint8_t * apdu, uint32_t * value); + +/* from clause 20.1.2.4 max-segments-accepted */ +/* and clause 20.1.2.5 max-APDU-length-accepted */ +/* returns the encoded octet */ + uint8_t encode_max_segs_max_apdu(int max_segs, int max_apdu); + int decode_max_segs(uint8_t octet); + int decode_max_apdu(uint8_t octet); + +/* returns the number of apdu bytes consumed */ + int encode_simple_ack(uint8_t * apdu, uint8_t invoke_id, + uint8_t service_choice); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/bacdef.h b/bacnet-stack/bacdef.h index e69de29b..dc7efe04 100644 --- a/bacnet-stack/bacdef.h +++ b/bacnet-stack/bacdef.h @@ -0,0 +1,99 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef BACDEF_H +#define BACDEF_H + +#include +#include +#include "bacenum.h" +#include "config.h" + +/* This stack implements this version of BACnet */ +#define BACNET_PROTOCOL_VERSION 1 +#define BACNET_PROTOCOL_REVISION 5 + +/* largest BACnet Instance Number */ +/* Also used as a device instance number wildcard address */ +#define BACNET_MAX_INSTANCE (0x3FFFFF) +#define BACNET_INSTANCE_BITS 22 +/* large BACnet Object Type */ +#define BACNET_MAX_OBJECT (0x3FF) +/* Array index 0=size of array, n=array element n, MAX=all array elements */ +#define BACNET_ARRAY_ALL (~0) +/* Priority Array for commandable objects */ +#define BACNET_NO_PRIORITY 0 +#define BACNET_MIN_PRIORITY 1 +#define BACNET_MAX_PRIORITY 16 + +/* embedded systems need fixed name sizes */ +#define MAX_OBJECT_NAME 10 +/* common object properties */ +typedef struct BACnet_Object_Data { + uint32_t Object_Identifier; + char Object_Name[MAX_OBJECT_NAME]; + BACNET_OBJECT_TYPE Object_Type; +} BACNET_OBJECT_DATA; + +#define BACNET_BROADCAST_NETWORK 0xFFFF +/* IPv6 (16 octets) coupled with port number (2 octets) */ +#define MAX_MAC_LEN 18 +struct BACnet_Device_Address { + /* mac_len = 0 if global address */ + int mac_len; + /* note: MAC for IP addresses uses 4 bytes for addr, 2 bytes for port */ + /* use de/encode_unsigned32/16 for re/storing the IP address */ + uint8_t mac[MAX_MAC_LEN]; + /* DNET,DLEN,DADR or SNET,SLEN,SADR */ + /* the following are used if the device is behind a router */ + /* net = 0 indicates local */ + uint16_t net; /* BACnet network number */ + /* LEN = 0 denotes broadcast MAC ADR and ADR field is absent */ + /* LEN > 0 specifies length of ADR field */ + int len; /* length of MAC address */ + uint8_t adr[MAX_MAC_LEN]; /* hwaddr (MAC) address */ +}; +typedef struct BACnet_Device_Address BACNET_ADDRESS; + +/* note: with microprocessors having lots more code space than memory, + it might be better to have a packed encoding with a library to + easily access the data. */ +typedef struct BACnet_Object_Id { + uint16_t type; + uint32_t instance; +} BACNET_OBJECT_ID; + +#define MAX_NPDU (1+1+2+1+MAX_MAC_LEN+2+1+MAX_MAC_LEN+1+1+2) +#define MAX_PDU (MAX_APDU + MAX_NPDU) + +#endif diff --git a/bacnet-stack/bacenum.h b/bacnet-stack/bacenum.h index e69de29b..43df30ee 100644 --- a/bacnet-stack/bacenum.h +++ b/bacnet-stack/bacenum.h @@ -0,0 +1,1240 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2003 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef BACENUM_H +#define BACENUM_H + +typedef enum { + PROP_ACKED_TRANSITIONS = 0, + PROP_ACK_REQUIRED = 1, + PROP_ACTION = 2, + PROP_ACTION_TEXT = 3, + PROP_ACTIVE_TEXT = 4, + PROP_ACTIVE_VT_SESSIONS = 5, + PROP_ALARM_VALUE = 6, + PROP_ALARM_VALUES = 7, + PROP_ALL = 8, + PROP_ALL_WRITES_SUCCESSFUL = 9, + PROP_APDU_SEGMENT_TIMEOUT = 10, + PROP_APDU_TIMEOUT = 11, + PROP_APPLICATION_SOFTWARE_VERSION = 12, + PROP_ARCHIVE = 13, + PROP_BIAS = 14, + PROP_CHANGE_OF_STATE_COUNT = 15, + PROP_CHANGE_OF_STATE_TIME = 16, + PROP_NOTIFICATION_CLASS = 17, + PROP_BLANK_1 = 18, + PROP_CONTROLLED_VARIABLE_REFERENCE = 19, + PROP_CONTROLLED_VARIABLE_UNITS = 20, + PROP_CONTROLLED_VARIABLE_VALUE = 21, + PROP_COV_INCREMENT = 22, + PROP_DATE_LIST = 23, + PROP_DAYLIGHT_SAVINGS_STATUS = 24, + PROP_DEADBAND = 25, + PROP_DERIVATIVE_CONSTANT = 26, + PROP_DERIVATIVE_CONSTANT_UNITS = 27, + PROP_DESCRIPTION = 28, + PROP_DESCRIPTION_OF_HALT = 29, + PROP_DEVICE_ADDRESS_BINDING = 30, + PROP_DEVICE_TYPE = 31, + PROP_EFFECTIVE_PERIOD = 32, + PROP_ELAPSED_ACTIVE_TIME = 33, + PROP_ERROR_LIMIT = 34, + PROP_EVENT_ENABLE = 35, + PROP_EVENT_STATE = 36, + PROP_EVENT_TYPE = 37, + PROP_EXCEPTION_SCHEDULE = 38, + PROP_FAULT_VALUES = 39, + PROP_FEEDBACK_VALUE = 40, + PROP_FILE_ACCESS_METHOD = 41, + PROP_FILE_SIZE = 42, + PROP_FILE_TYPE = 43, + PROP_FIRMWARE_REVISION = 44, + PROP_HIGH_LIMIT = 45, + PROP_INACTIVE_TEXT = 46, + PROP_IN_PROCESS = 47, + PROP_INSTANCE_OF = 48, + PROP_INTEGRAL_CONSTANT = 49, + PROP_INTEGRAL_CONSTANT_UNITS = 50, + PROP_ISSUE_CONFIRMED_NOTIFICATIONS = 51, + PROP_LIMIT_ENABLE = 52, + PROP_LIST_OF_GROUP_MEMBERS = 53, + PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES = 54, + PROP_LIST_OF_SESSION_KEYS = 55, + PROP_LOCAL_DATE = 56, + PROP_LOCAL_TIME = 57, + PROP_LOCATION = 58, + PROP_LOW_LIMIT = 59, + PROP_MANIPULATED_VARIABLE_REFERENCE = 60, + PROP_MAXIMUM_OUTPUT = 61, + PROP_MAX_APDU_LENGTH_ACCEPTED = 62, + PROP_MAX_INFO_FRAMES = 63, + PROP_MAX_MASTER = 64, + PROP_MAX_PRES_VALUE = 65, + PROP_MINIMUM_OFF_TIME = 66, + PROP_MINIMUM_ON_TIME = 67, + PROP_MINIMUM_OUTPUT = 68, + PROP_MIN_PRES_VALUE = 69, + PROP_MODEL_NAME = 70, + PROP_MODIFICATION_DATE = 71, + PROP_NOTIFY_TYPE = 72, + PROP_NUMBER_OF_APDU_RETRIES = 73, + PROP_NUMBER_OF_STATES = 74, + PROP_OBJECT_IDENTIFIER = 75, + PROP_OBJECT_LIST = 76, + PROP_OBJECT_NAME = 77, + PROP_OBJECT_PROPERTY_REFERENCE = 78, + PROP_OBJECT_TYPE = 79, + PROP_OPTIONAL = 80, + PROP_OUT_OF_SERVICE = 81, + PROP_OUTPUT_UNITS = 82, + PROP_EVENT_PARAMETERS = 83, + PROP_POLARITY = 84, + PROP_PRESENT_VALUE = 85, + PROP_PRIORITY = 86, + PROP_PRIORITY_ARRAY = 87, + PROP_PRIORITY_FOR_WRITING = 88, + PROP_PROCESS_IDENTIFIER = 89, + PROP_PROGRAM_CHANGE = 90, + PROP_PROGRAM_LOCATION = 91, + PROP_PROGRAM_STATE = 92, + PROP_PROPORTIONAL_CONSTANT = 93, + PROP_PROPORTIONAL_CONSTANT_UNITS = 94, + PROP_PROTOCOL_CONFORMANCE_CLASS = 95, /* deleted in version 1 revision 2 */ + PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED = 96, + PROP_PROTOCOL_SERVICES_SUPPORTED = 97, + PROP_PROTOCOL_VERSION = 98, + PROP_READ_ONLY = 99, + PROP_REASON_FOR_HALT = 100, + PROP_RECIPIENT = 101, + PROP_RECIPIENT_LIST = 102, + PROP_RELIABILITY = 103, + PROP_RELINQUISH_DEFAULT = 104, + PROP_REQUIRED = 105, + PROP_RESOLUTION = 106, + PROP_SEGMENTATION_SUPPORTED = 107, + PROP_SETPOINT = 108, + PROP_SETPOINT_REFERENCE = 109, + PROP_STATE_TEXT = 110, + PROP_STATUS_FLAGS = 111, + PROP_SYSTEM_STATUS = 112, + PROP_TIME_DELAY = 113, + PROP_TIME_OF_ACTIVE_TIME_RESET = 114, + PROP_TIME_OF_STATE_COUNT_RESET = 115, + PROP_TIME_SYNCHRONIZATION_RECIPIENTS = 116, + PROP_UNITS = 117, + PROP_UPDATE_INTERVAL = 118, + PROP_UTC_OFFSET = 119, + PROP_VENDOR_IDENTIFIER = 120, + PROP_VENDOR_NAME = 121, + PROP_VT_CLASSES_SUPPORTED = 122, + PROP_WEEKLY_SCHEDULE = 123, + PROP_ATTEMPTED_SAMPLES = 124, + PROP_AVERAGE_VALUE = 125, + PROP_BUFFER_SIZE = 126, + PROP_CLIENT_COV_INCREMENT = 127, + PROP_COV_RESUBSCRIPTION_INTERVAL = 128, + PROP_CURRENT_NOTIFY_TIME = 129, + PROP_EVENT_TIME_STAMPS = 130, + PROP_LOG_BUFFER = 131, + PROP_LOG_DEVICE_OBJECT = 132, + /* The enable property is renamed from log-enable in + Addendum b to ANSI/ASHRAE 135-2004(135b-2) */ + PROP_ENABLE = 133, + PROP_LOG_INTERVAL = 134, + PROP_MAXIMUM_VALUE = 135, + PROP_MINIMUM_VALUE = 136, + PROP_NOTIFICATION_THRESHOLD = 137, + PROP_PREVIOUS_NOTIFY_TIME = 138, + PROP_PROTOCOL_REVISION = 139, + PROP_RECORDS_SINCE_NOTIFICATION = 140, + PROP_RECORD_COUNT = 141, + PROP_START_TIME = 142, + PROP_STOP_TIME = 143, + PROP_STOP_WHEN_FULL = 144, + PROP_TOTAL_RECORD_COUNT = 145, + PROP_VALID_SAMPLES = 146, + PROP_WINDOW_INTERVAL = 147, + PROP_WINDOW_SAMPLES = 148, + PROP_MAXIMUM_VALUE_TIMESTAMP = 149, + PROP_MINIMUM_VALUE_TIMESTAMP = 150, + PROP_VARIANCE_VALUE = 151, + PROP_ACTIVE_COV_SUBSCRIPTIONS = 152, + PROP_BACKUP_FAILURE_TIMEOUT = 153, + PROP_CONFIGURATION_FILES = 154, + PROP_DATABASE_REVISION = 155, + PROP_DIRECT_READING = 156, + PROP_LAST_RESTORE_TIME = 157, + PROP_MAINTENANCE_REQUIRED = 158, + PROP_MEMBER_OF = 159, + PROP_MODE = 160, + PROP_OPERATION_EXPECTED = 161, + PROP_SETTING = 162, + PROP_SILENCED = 163, + PROP_TRACKING_VALUE = 164, + PROP_ZONE_MEMBERS = 165, + PROP_LIFE_SAFETY_ALARM_VALUES = 166, + PROP_MAX_SEGMENTS_ACCEPTED = 167, + PROP_PROFILE_NAME = 168, + PROP_AUTO_SLAVE_DISCOVERY = 169, + PROP_MANUAL_SLAVE_ADDRESS_BINDING = 170, + PROP_SLAVE_ADDRESS_BINDING = 171, + PROP_SLAVE_PROXY_ENABLE = 172, + PROP_LAST_NOTIFY_TIME = 173, + PROP_SCHEDULE_DEFAULT = 174, + PROP_ACCEPTED_MODES = 175, + PROP_ADJUST_VALUE = 176, + PROP_COUNT = 177, + PROP_COUNT_BEFORE_CHANGE = 178, + PROP_COUNT_CHANGE_TIME = 179, + PROP_COV_PERIOD = 180, + PROP_INPUT_REFERENCE = 181, + PROP_LIMIT_MONITORING_INTERVAL = 182, + PROP_LOGGING_DEVICE = 183, + PROP_LOGGING_RECORD = 184, + PROP_PRESCALE = 185, + PROP_PULSE_RATE = 186, + PROP_SCALE = 187, + PROP_SCALE_FACTOR = 188, + PROP_UPDATE_TIME = 189, + PROP_VALUE_BEFORE_CHANGE = 190, + PROP_VALUE_SET = 191, + PROP_VALUE_CHANGE_TIME = 192, + /* enumerations 193-206 are new */ + PROP_ALIGN_INTERVALS = 193, + PROP_GROUP_MEMBER_NAMES = 194, + PROP_INTERVAL_OFFSET = 195, + PROP_LAST_RESTART_REASON = 196, + PROP_LOGGING_TYPE = 197, + PROP_MEMBER_STATUS_FLAGS = 198, + PROP_NOTIFICATION_PERIOD = 199, + PROP_PREVIOUS_NOTIFY_RECORD = 200, + PROP_REQUESTED_UPDATE_INTERVAL = 201, + PROP_RESTART_NOTIFICATION_RECIPIENTS = 202, + PROP_TIME_OF_DEVICE_RESTART = 203, + PROP_TIME_SYNCHRONIZATION_INTERVAL = 204, + PROP_TRIGGER = 205, + PROP_UTC_TIME_SYNCHRONIZATION_RECIPIENTS = 206, + /* enumerations 207-211 are used in Addendum d to ANSI/ASHRAE 135-2004 */ + PROP_NODE_SUBTYPE = 207, + PROP_NODE_TYPE = 208, + PROP_STRUCTURED_OBJECT_LIST = 209, + PROP_SUBORDINATE_ANNOTATIONS = 210, + PROP_SUBORDINATE_LIST = 211, + /* enumerations 212-225 are used in Addendum e to ANSI/ASHRAE 135-2004 */ + PROP_ACTUAL_SHED_LEVEL = 212, + PROP_DUTY_WINDOW = 213, + PROP_EXPECTED_SHED_LEVEL = 214, + PROP_FULL_DUTY_BASELINE = 215, + /* enumerations 216-217 are used in Addendum i to ANSI/ASHRAE 135-2004 */ + PROP_BLINK_PRIORITY_THRESHOLD = 216, + PROP_BLINK_TIME = 217, + /* enumerations 212-225 are used in Addendum e to ANSI/ASHRAE 135-2004 */ + PROP_REQUESTED_SHED_LEVEL = 218, + PROP_SHED_DURATION = 219, + PROP_SHED_LEVEL_DESCRIPTIONS = 220, + PROP_SHED_LEVELS = 221, + PROP_STATE_DESCRIPTION = 222, + /* enumerations 223-225 are used in Addendum i to ANSI/ASHRAE 135-2004 */ + PROP_FADE_TIME = 223, + PROP_LIGHTING_COMMAND = 224, + PROP_LIGHTING_COMMAND_PRIORITY = 225, + /* enumerations 226-235 are used in Addendum f to ANSI/ASHRAE 135-2004 */ + /* enumerations 236-243 are used in Addendum i to ANSI/ASHRAE 135-2004 */ + PROP_OFF_DELAY = 236, + PROP_ON_DELAY = 237, + PROP_POWER = 238, + PROP_POWER_ON_VALUE = 239, + PROP_PROGRESS_VALUE = 240, + PROP_RAMP_RATE = 241, + PROP_STEP_INCREMENT = 242, + PROP_SYSTEM_FAILURE_VALUE = 243, + /* The special property identifiers all, optional, and required */ + /* are reserved for use in the ReadPropertyConditional and */ + /* ReadPropertyMultiple services or services not defined in this standard. */ + /* Enumerated values 0-511 are reserved for definition by ASHRAE. */ + /* Enumerated values 512-4194303 may be used by others subject to the */ + /* procedures and constraints described in Clause 23. */ + MAX_BACNET_PROPERTY_ID = 4194303 +} BACNET_PROPERTY_ID; + +typedef enum { + ACTION_DIRECT = 0, + ACTION_REVERSE = 1 +} BACNET_ACTION; + +typedef enum { + MIN_BINARY_PV = 0, /* for validating incoming values */ + BINARY_INACTIVE = 0, + BINARY_ACTIVE = 1, + MAX_BINARY_PV = 1, /* for validating incoming values */ + BINARY_NULL = 2 /* our homemade way of storing this info */ +} BACNET_BINARY_PV; + +typedef enum { + ACTION_BINARY_PV, + ACTION_UNSIGNED, + ACTION_FLOAT +} BACNET_ACTION_VALUE_TYPE; + +typedef enum { + EVENT_STATE_NORMAL = 0, + EVENT_STATE_FAULT = 1, + EVENT_STATE_OFFNORMAL = 2, + EVENT_STATE_HIGH_LIMIT = 3, + EVENT_STATE_LOW_LIMIT = 4 +} BACNET_EVENT_STATE; + +typedef enum { + STATUS_OPERATIONAL = 0, + STATUS_OPERATIONAL_READ_ONLY = 1, + STATUS_DOWNLOAD_REQUIRED = 2, + STATUS_DOWNLOAD_IN_PROGRESS = 3, + STATUS_NON_OPERATIONAL = 4, + MAX_DEVICE_STATUS = 5 +} BACNET_DEVICE_STATUS; + +typedef enum { + /* Acceleration */ + UNITS_METERS_PER_SECOND_PER_SECOND = 166, + /* Area */ + UNITS_SQUARE_METERS = 0, + UNITS_SQUARE_CENTIMETERS = 116, + UNITS_SQUARE_FEET = 1, + UNITS_SQUARE_INCHES = 115, + /* Currency */ + UNITS_CURRENCY1 = 105, + UNITS_CURRENCY2 = 106, + UNITS_CURRENCY3 = 107, + UNITS_CURRENCY4 = 108, + UNITS_CURRENCY5 = 109, + UNITS_CURRENCY6 = 110, + UNITS_CURRENCY7 = 111, + UNITS_CURRENCY8 = 112, + UNITS_CURRENCY9 = 113, + UNITS_CURRENCY10 = 114, + /* Electrical */ + UNITS_MILLIAMPERES = 2, + UNITS_AMPERES = 3, + UNITS_AMPERES_PER_METER = 167, + UNITS_AMPERES_PER_SQUARE_METER = 168, + UNITS_AMPERE_SQUARE_METERS = 169, + UNITS_FARADS = 170, + UNITS_HENRYS = 171, + UNITS_OHMS = 4, + UNITS_OHM_METERS = 172, + UNITS_MILLIOHMS = 145, + UNITS_KILOHMS = 122, + UNITS_MEGOHMS = 123, + UNITS_SIEMENS = 173, /* 1 mho equals 1 siemens */ + UNITS_SIEMENS_PER_METER = 174, + UNITS_TESLAS = 175, + UNITS_VOLTS = 5, + UNITS_MILLIVOLTS = 124, + UNITS_KILOVOLTS = 6, + UNITS_MEGAVOLTS = 7, + UNITS_VOLT_AMPERES = 8, + UNITS_KILOVOLT_AMPERES = 9, + UNITS_MEGAVOLT_AMPERES = 10, + UNITS_VOLT_AMPERES_REACTIVE = 11, + UNITS_KILOVOLT_AMPERES_REACTIVE = 12, + UNITS_MEGAVOLT_AMPERES_REACTIVE = 13, + UNITS_VOLTS_PER_DEGREE_KELVIN = 176, + UNITS_VOLTS_PER_METER = 177, + UNITS_DEGREES_PHASE = 14, + UNITS_POWER_FACTOR = 15, + UNITS_WEBERS = 178, + /* Energy */ + UNITS_JOULES = 16, + UNITS_KILOJOULES = 17, + UNITS_KILOJOULES_PER_KILOGRAM = 125, + UNITS_MEGAJOULES = 126, + UNITS_WATT_HOURS = 18, + UNITS_KILOWATT_HOURS = 19, + UNITS_MEGAWATT_HOURS = 146, + UNITS_BTUS = 20, + UNITS_KILO_BTUS = 147, + UNITS_MEGA_BTUS = 148, + UNITS_THERMS = 21, + UNITS_TON_HOURS = 22, + /* Enthalpy */ + UNITS_JOULES_PER_KILOGRAM_DRY_AIR = 23, + UNITS_KILOJOULES_PER_KILOGRAM_DRY_AIR = 149, + UNITS_MEGAJOULES_PER_KILOGRAM_DRY_AIR = 150, + UNITS_BTUS_PER_POUND_DRY_AIR = 24, + UNITS_BTUS_PER_POUND = 117, + /* Entropy */ + UNITS_JOULES_PER_DEGREE_KELVIN = 127, + UNITS_KILOJOULES_PER_DEGREE_KELVIN = 151, + UNITS_MEGAJOULES_PER_DEGREE_KELVIN = 152, + UNITS_JOULES_PER_KILOGRAM_DEGREE_KELVIN = 128, + /* Force */ + UNITS_NEWTON = 153, + /* Frequency */ + UNITS_CYCLES_PER_HOUR = 25, + UNITS_CYCLES_PER_MINUTE = 26, + UNITS_HERTZ = 27, + UNITS_KILOHERTZ = 129, + UNITS_MEGAHERTZ = 130, + UNITS_PER_HOUR = 131, + /* Humidity */ + UNITS_GRAMS_OF_WATER_PER_KILOGRAM_DRY_AIR = 28, + UNITS_PERCENT_RELATIVE_HUMIDITY = 29, + /* Length */ + UNITS_MILLIMETERS = 30, + UNITS_CENTIMETERS = 118, + UNITS_METERS = 31, + UNITS_INCHES = 32, + UNITS_FEET = 33, + /* Light */ + UNITS_CANDELAS = 179, + UNITS_CANDELAS_PER_SQUARE_METER = 180, + UNITS_WATTS_PER_SQUARE_FOOT = 34, + UNITS_WATTS_PER_SQUARE_METER = 35, + UNITS_LUMENS = 36, + UNITS_LUXES = 37, + UNITS_FOOT_CANDLES = 38, + /* Mass */ + UNITS_KILOGRAMS = 39, + UNITS_POUNDS_MASS = 40, + UNITS_TONS = 41, + /* Mass Flow */ + UNITS_GRAMS_PER_SECOND = 154, + UNITS_GRAMS_PER_MINUTE = 155, + UNITS_KILOGRAMS_PER_SECOND = 42, + UNITS_KILOGRAMS_PER_MINUTE = 43, + UNITS_KILOGRAMS_PER_HOUR = 44, + UNITS_POUNDS_MASS_PER_SECOND = 119, + UNITS_POUNDS_MASS_PER_MINUTE = 45, + UNITS_POUNDS_MASS_PER_HOUR = 46, + UNITS_TONS_PER_HOUR = 156, + /* Power */ + UNITS_MILLIWATTS = 132, + UNITS_WATTS = 47, + UNITS_KILOWATTS = 48, + UNITS_MEGAWATTS = 49, + UNITS_BTUS_PER_HOUR = 50, + UNITS_KILO_BTUS_PER_HOUR = 157, + UNITS_HORSEPOWER = 51, + UNITS_TONS_REFRIGERATION = 52, + /* Pressure */ + UNITS_PASCALS = 53, + UNITS_HECTOPASCALS = 133, + UNITS_KILOPASCALS = 54, + UNITS_MILLIBARS = 134, + UNITS_BARS = 55, + UNITS_POUNDS_FORCE_PER_SQUARE_INCH = 56, + UNITS_CENTIMETERS_OF_WATER = 57, + UNITS_INCHES_OF_WATER = 58, + UNITS_MILLIMETERS_OF_MERCURY = 59, + UNITS_CENTIMETERS_OF_MERCURY = 60, + UNITS_INCHES_OF_MERCURY = 61, + /* Temperature */ + UNITS_DEGREES_CELSIUS = 62, + UNITS_DEGREES_KELVIN = 63, + UNITS_DEGREES_KELVIN_PER_HOUR = 181, + UNITS_DEGREES_KELVIN_PER_MINUTE = 182, + UNITS_DEGREES_FAHRENHEIT = 64, + UNITS_DEGREE_DAYS_CELSIUS = 65, + UNITS_DEGREE_DAYS_FAHRENHEIT = 66, + UNITS_DELTA_DEGREES_FAHRENHEIT = 120, + UNITS_DELTA_DEGREES_KELVIN = 121, + /* Time */ + UNITS_YEARS = 67, + UNITS_MONTHS = 68, + UNITS_WEEKS = 69, + UNITS_DAYS = 70, + UNITS_HOURS = 71, + UNITS_MINUTES = 72, + UNITS_SECONDS = 73, + UNITS_HUNDREDTHS_SECONDS = 158, + UNITS_MILLISECONDS = 159, + /* Torque */ + UNITS_NEWTON_METERS = 160, + /* Velocity */ + UNITS_MILLIMETERS_PER_SECOND = 161, + UNITS_MILLIMETERS_PER_MINUTE = 162, + UNITS_METERS_PER_SECOND = 74, + UNITS_METERS_PER_MINUTE = 163, + UNITS_METERS_PER_HOUR = 164, + UNITS_KILOMETERS_PER_HOUR = 75, + UNITS_FEET_PER_SECOND = 76, + UNITS_FEET_PER_MINUTE = 77, + UNITS_MILES_PER_HOUR = 78, + /* Volume */ + UNITS_CUBIC_FEET = 79, + UNITS_CUBIC_METERS = 80, + UNITS_IMPERIAL_GALLONS = 81, + UNITS_LITERS = 82, + UNITS_US_GALLONS = 83, + /* Volumetric Flow */ + UNITS_CUBIC_FEET_PER_SECOND = 142, + UNITS_CUBIC_FEET_PER_MINUTE = 84, + UNITS_CUBIC_METERS_PER_SECOND = 85, + UNITS_CUBIC_METERS_PER_MINUTE = 165, + UNITS_CUBIC_METERS_PER_HOUR = 135, + UNITS_IMPERIAL_GALLONS_PER_MINUTE = 86, + UNITS_LITERS_PER_SECOND = 87, + UNITS_LITERS_PER_MINUTE = 88, + UNITS_LITERS_PER_HOUR = 136, + UNITS_US_GALLONS_PER_MINUTE = 89, + /* Other */ + UNITS_DEGREES_ANGULAR = 90, + UNITS_DEGREES_CELSIUS_PER_HOUR = 91, + UNITS_DEGREES_CELSIUS_PER_MINUTE = 92, + UNITS_DEGREES_FAHRENHEIT_PER_HOUR = 93, + UNITS_DEGREES_FAHRENHEIT_PER_MINUTE = 94, + UNITS_JOULE_SECONDS = 183, + UNITS_KILOGRAMS_PER_CUBIC_METER = 186, + UNITS_KILOWATT_HOURS_PER_SQUARE_METER = 137, + UNITS_KILOWATT_HOURS_PER_SQUARE_FOOT = 138, + UNITS_MEGAJOULES_PER_SQUARE_METER = 139, + UNITS_MEGAJOULES_PER_SQUARE_FOOT = 140, + UNITS_NO_UNITS = 95, + UNITS_NEWTON_SECONDS = 187, + UNITS_NEWTONS_PER_METER = 188, + UNITS_PARTS_PER_MILLION = 96, + UNITS_PARTS_PER_BILLION = 97, + UNITS_PERCENT = 98, + UNITS_PERCENT_OBSCURATION_PER_FOOT = 143, + UNITS_PERCENT_OBSCURATION_PER_METER = 144, + UNITS_PERCENT_PER_SECOND = 99, + UNITS_PER_MINUTE = 100, + UNITS_PER_SECOND = 101, + UNITS_PSI_PER_DEGREE_FAHRENHEIT = 102, + UNITS_RADIANS = 103, + UNITS_RADIANS_PER_SECOND = 184, + UNITS_REVOLUTIONS_PER_MINUTE = 104, + UNITS_SQUARE_METERS_PER_NEWTON = 185, + UNITS_WATTS_PER_METER_PER_DEGREE_KELVIN = 189, + UNITS_WATTS_PER_SQUARE_METER_DEGREE_KELVIN = 141 + /* Enumerated values 0-255 are reserved for definition by ASHRAE. */ + /* Enumerated values 256-65535 may be used by others subject to */ + /* the procedures and constraints described in Clause 23. */ + /* The last enumeration used in this version is 189. */ +} BACNET_ENGINEERING_UNITS; + +typedef enum { + POLARITY_NORMAL = 0, + POLARITY_REVERSE = 1 +} BACNET_POLARITY; + +typedef enum { + PROGRAM_REQUEST_READY = 0, + PROGRAM_REQUEST_LOAD = 1, + PROGRAM_REQUEST_RUN = 2, + PROGRAM_REQUEST_HALT = 3, + PROGRAM_REQUEST_RESTART = 4, + PROGRAM_REQUEST_UNLOAD = 5 +} BACNET_PROGRAM_REQUEST; + +typedef enum { + PROGRAM_STATE_IDLE = 0, + PROGRAM_STATE_LOADING = 1, + PROGRAM_STATE_RUNNING = 2, + PROGRAM_STATE_WAITING = 3, + PROGRAM_STATE_HALTED = 4, + PROGRAM_STATE_UNLOADING = 5 +} BACNET_PROGRAM_STATE; + +typedef enum { + PROGRAM_ERROR_NORMAL = 0, + PROGRAM_ERROR_LOAD_FAILED = 1, + PROGRAM_ERROR_INTERNAL = 2, + PROGRAM_ERROR_PROGRAM = 3, + PROGRAM_ERROR_OTHER = 4 + /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ + /* Enumerated values 64-65535 may be used by others subject to */ + /* the procedures and constraints described in Clause 23. */ +} BACNET_PROGRAM_ERROR; + +typedef enum { + RELIABILITY_NO_FAULT_DETECTED = 0, + RELIABILITY_NO_SENSOR = 1, + RELIABILITY_OVER_RANGE = 2, + RELIABILITY_UNDER_RANGE = 3, + RELIABILITY_OPEN_LOOP = 4, + RELIABILITY_SHORTED_LOOP = 5, + RELIABILITY_NO_OUTPUT = 6, + RELIABILITY_UNRELIABLE_OTHER = 7, + RELIABILITY_PROCESS_ERROR = 8, + RELIABILITY_MULTI_STATE_FAULT = 9, + RELIABILITY_CONFIGURATION_ERROR = 10, + RELIABILITY_COMMUNICATION_FAILURE = 12, + RELIABILITY_TRIPPED = 13 + /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ + /* Enumerated values 64-65535 may be used by others subject to */ + /* the procedures and constraints described in Clause 23. */ +} BACNET_RELIABILITY; + +typedef enum { + EVENT_CHANGE_OF_BITSTRING = 0, + EVENT_CHANGE_OF_STATE = 1, + EVENT_CHANGE_OF_VALUE = 2, + EVENT_COMMAND_FAILURE = 3, + EVENT_FLOATING_LIMIT = 4, + EVENT_OUT_OF_RANGE = 5, + /* complex-event-type (6), -- see comment below */ + /* event-buffer-ready (7), -- context tag 7 is deprecated */ + EVENT_CHANGE_OF_LIFE_SAFETY = 8, + EVENT_EXTENDED = 9, + EVENT_BUFFER_READY = 10, + EVENT_UNSIGNED_RANGE = 11 + /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ + /* Enumerated values 64-65535 may be used by others subject to */ + /* the procedures and constraints described in Clause 23. */ + /* It is expected that these enumerated values will correspond to */ + /* the use of the complex-event-type CHOICE [6] of the */ + /* BACnetNotificationParameters production. */ + /* The last enumeration used in this version is 11. */ +} BACNET_EVENT_TYPE; + +typedef enum { + FILE_RECORD_ACCESS = 0, + FILE_STREAM_ACCESS = 1, + FILE_RECORD_AND_STREAM_ACCESS = 2 +} BACNET_FILE_ACCESS_METHOD; + +typedef enum { + MIN_LIFE_SAFETY_MODE = 0, + LIFE_SAFETY_MODE_OFF = 0, + LIFE_SAFETY_MODE_ON = 1, + LIFE_SAFETY_MODE_TEST = 2, + LIFE_SAFETY_MODE_MANNED = 3, + LIFE_SAFETY_MODE_UNMANNED = 4, + LIFE_SAFETY_MODE_ARMED = 5, + LIFE_SAFETY_MODE_DISARMED = 6, + LIFE_SAFETY_MODE_PREARMED = 7, + LIFE_SAFETY_MODE_SLOW = 8, + LIFE_SAFETY_MODE_FAST = 9, + LIFE_SAFETY_MODE_DISCONNECTED = 10, + LIFE_SAFETY_MODE_ENABLED = 11, + LIFE_SAFETY_MODE_DISABLED = 12, + LIFE_SAFETY_MODE_AUTOMATIC_RELEASE_DISABLED = 13, + LIFE_SAFETY_MODE_DEFAULT = 14, + MAX_LIFE_SAFETY_MODE = 14 + /* Enumerated values 0-255 are reserved for definition by ASHRAE. */ + /* Enumerated values 256-65535 may be used by others subject to */ + /* procedures and constraints described in Clause 23. */ +} BACNET_LIFE_SAFETY_MODE; + +typedef enum { + LIFE_SAFETY_OPERATION_NONE = 0, + LIFE_SAFETY_OPERATION_SILENCE = 1, + LIFE_SAFETY_OPERATION_SILENCE_AUDIBLE = 2, + LIFE_SAFETY_OPERATION_SILENCE_VISUAL = 3, + LIFE_SAFETY_OPERATION_RESET = 4, + LIFE_SAFETY_OPERATION_RESET_ALARM = 5, + LIFE_SAFETY_OPERATION_RESET_FAULT = 6, + LIFE_SAFETY_OPERATION_UNSILENCE = 7, + LIFE_SAFETY_OPERATION_UNSILENCE_AUDIBLE = 8, + LIFE_SAFETY_OPERATION_UNSILENCE_VISUAL = 9 + /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ + /* Enumerated values 64-65535 may be used by others subject to */ + /* procedures and constraints described in Clause 23. */ +} BACNET_LIFE_SAFETY_OPERATION; + +typedef enum { + MIN_LIFE_SAFETY_STATE = 0, + LIFE_SAFETY_STATE_QUIET = 0, + LIFE_SAFETY_STATE_PRE_ALARM = 1, + LIFE_SAFETY_STATE_ALARM = 2, + LIFE_SAFETY_STATE_FAULT = 3, + LIFE_SAFETY_STATE_FAULT_PRE_ALARM = 4, + LIFE_SAFETY_STATE_FAULT_ALARM = 5, + LIFE_SAFETY_STATE_NOT_READY = 6, + LIFE_SAFETY_STATE_ACTIVE = 7, + LIFE_SAFETY_STATE_TAMPER = 8, + LIFE_SAFETY_STATE_TEST_ALARM = 9, + LIFE_SAFETY_STATE_TEST_ACTIVE = 10, + LIFE_SAFETY_STATE_TEST_FAULT = 11, + LIFE_SAFETY_STATE_TEST_FAULT_ALARM = 12, + LIFE_SAFETY_STATE_HOLDUP = 13, + LIFE_SAFETY_STATE_DURESS = 14, + LIFE_SAFETY_STATE_TAMPER_ALARM = 15, + LIFE_SAFETY_STATE_ABNORMAL = 16, + LIFE_SAFETY_STATE_EMERGENCY_POWER = 17, + LIFE_SAFETY_STATE_DELAYED = 18, + LIFE_SAFETY_STATE_BLOCKED = 19, + LIFE_SAFETY_STATE_LOCAL_ALARM = 20, + LIFE_SAFETY_STATE_GENERAL_ALARM = 21, + LIFE_SAFETY_STATE_SUPERVISORY = 22, + LIFE_SAFETY_STATE_TEST_SUPERVISORY = 23, + MAX_LIFE_SAFETY_STATE = 0 + /* Enumerated values 0-255 are reserved for definition by ASHRAE. */ + /* Enumerated values 256-65535 may be used by others subject to */ + /* procedures and constraints described in Clause 23. */ +} BACNET_LIFE_SAFETY_STATE; + +typedef enum { + SILENCED_STATE_UNSILENCED = 0, + SILENCED_STATE_AUDIBLE_SILENCED = 1, + SILENCED_STATE_VISIBLE_SILENCED = 2, + SILENCED_STATE_ALL_SILENCED = 3 + /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ + /* Enumerated values 64-65535 may be used by others subject to */ + /* procedures and constraints described in Clause 23. */ +} BACNET_SILENCED_STATE; + +typedef enum { + MAINTENANCE_NONE = 0, + MAINTENANCE_PERIODIC_TEST = 1, + AINTENANCE_NEED_SERVICE_OPERATIONAL = 2, + MAINTENANCE_NEED_SERVICE_INOPERATIVE = 3 + /* Enumerated values 0-255 are reserved for definition by ASHRAE. */ + /* Enumerated values 256-65535 may be used by others subject to */ + /* procedures and constraints described in Clause 23. */ +} BACNET_MAINTENANCE; + +typedef enum { + NOTIFY_ALARM = 0, + NOTIFY_EVENT = 1, + NOTIFY_ACK_NOTIFICATION = 2 +} BACNET_NOTIFY_TYPE; + +typedef enum { + OBJECT_ANALOG_INPUT = 0, + OBJECT_ANALOG_OUTPUT = 1, + OBJECT_ANALOG_VALUE = 2, + OBJECT_BINARY_INPUT = 3, + OBJECT_BINARY_OUTPUT = 4, + OBJECT_BINARY_VALUE = 5, + OBJECT_CALENDAR = 6, + OBJECT_COMMAND = 7, + OBJECT_DEVICE = 8, + OBJECT_EVENT_ENROLLMENT = 9, + OBJECT_FILE = 10, + OBJECT_GROUP = 11, + OBJECT_LOOP = 12, + OBJECT_MULTI_STATE_INPUT = 13, + OBJECT_MULTI_STATE_OUTPUT = 14, + OBJECT_NOTIFICATION_CLASS = 15, + OBJECT_PROGRAM = 16, + OBJECT_SCHEDULE = 17, + OBJECT_AVERAGING = 18, + OBJECT_MULTI_STATE_VALUE = 19, + OBJECT_TRENDLOG = 20, + OBJECT_LIFE_SAFETY_POINT = 21, + OBJECT_LIFE_SAFETY_ZONE = 22, + OBJECT_ACCUMULATOR = 23, + OBJECT_PULSE_CONVERTER = 24, + OBJECT_EVENT_LOG = 25, + OBJECT_GLOBAL_GROUP = 26, + OBJECT_TREND_LOG_MULTIPLE = 27, + OBJECT_LOAD_CONTROL = 28, + OBJECT_STRUCTURED_VIEW = 29, + /* what is object type 30? */ + OBJECT_LIGHTING_OUTPUT = 31, + /* Enumerated values 0-127 are reserved for definition by ASHRAE. */ + /* Enumerated values 128-1023 may be used by others subject to */ + /* the procedures and constraints described in Clause 23. */ + MAX_ASHRAE_OBJECT_TYPE = 32, /* used for bit string loop */ + MAX_BACNET_OBJECT_TYPE = 1023 +} BACNET_OBJECT_TYPE; + +typedef enum { + SEGMENTATION_BOTH = 0, + SEGMENTATION_TRANSMIT = 1, + SEGMENTATION_RECEIVE = 2, + SEGMENTATION_NONE = 3, + MAX_BACNET_SEGMENTATION = 4 +} BACNET_SEGMENTATION; + +typedef enum { + VT_CLASS_DEFAULT = 0, + VT_CLASS_ANSI_X34 = 1, /* real name is ANSI X3.64 */ + VT_CLASS_DEC_VT52 = 2, + VT_CLASS_DEC_VT100 = 3, + VT_CLASS_DEC_VT220 = 4, + VT_CLASS_HP_700_94 = 5, /* real name is HP 700/94 */ + VT_CLASS_IBM_3130 = 6 + /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ + /* Enumerated values 64-65535 may be used by others subject to */ + /* the procedures and constraints described in Clause 23. */ +} BACNET_VT_CLASS; + +typedef enum { + CHARACTER_ANSI_X34 = 0, + CHARACTER_MS_DBCS = 1, + CHARACTER_JISC_6226 = 2, + CHARACTER_UCS4 = 3, + CHARACTER_UCS2 = 4, + CHARACTER_ISO8859 = 5 +} BACNET_CHARACTER_STRING_ENCODING; + +typedef enum { + BACNET_APPLICATION_TAG_NULL = 0, + BACNET_APPLICATION_TAG_BOOLEAN = 1, + BACNET_APPLICATION_TAG_UNSIGNED_INT = 2, + BACNET_APPLICATION_TAG_SIGNED_INT = 3, + BACNET_APPLICATION_TAG_REAL = 4, + BACNET_APPLICATION_TAG_DOUBLE = 5, + BACNET_APPLICATION_TAG_OCTET_STRING = 6, + BACNET_APPLICATION_TAG_CHARACTER_STRING = 7, + BACNET_APPLICATION_TAG_BIT_STRING = 8, + BACNET_APPLICATION_TAG_ENUMERATED = 9, + BACNET_APPLICATION_TAG_DATE = 10, + BACNET_APPLICATION_TAG_TIME = 11, + BACNET_APPLICATION_TAG_OBJECT_ID = 12, + BACNET_APPLICATION_TAG_RESERVED1 = 13, + BACNET_APPLICATION_TAG_RESERVED2 = 14, + BACNET_APPLICATION_TAG_RESERVED3 = 15, + MAX_BACNET_APPLICATION_TAG = 16 +} BACNET_APPLICATION_TAG; + +/* note: these are not the real values, */ +/* but are shifted left for easy encoding */ +typedef enum { + PDU_TYPE_CONFIRMED_SERVICE_REQUEST = 0, + PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST = 0x10, + PDU_TYPE_SIMPLE_ACK = 0x20, + PDU_TYPE_COMPLEX_ACK = 0x30, + PDU_TYPE_SEGMENT_ACK = 0x40, + PDU_TYPE_ERROR = 0x50, + PDU_TYPE_REJECT = 0x60, + PDU_TYPE_ABORT = 0x70 +} BACNET_PDU_TYPE; + +typedef enum { + /* Alarm and Event Services */ + SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM = 0, + SERVICE_CONFIRMED_COV_NOTIFICATION = 1, + SERVICE_CONFIRMED_EVENT_NOTIFICATION = 2, + SERVICE_CONFIRMED_GET_ALARM_SUMMARY = 3, + SERVICE_CONFIRMED_GET_ENROLLMENT_SUMMARY = 4, + SERVICE_CONFIRMED_GET_EVENT_INFORMATION = 29, + SERVICE_CONFIRMED_SUBSCRIBE_COV = 5, + SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY = 28, + SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION = 27, + /* File Access Services */ + SERVICE_CONFIRMED_ATOMIC_READ_FILE = 6, + SERVICE_CONFIRMED_ATOMIC_WRITE_FILE = 7, + /* Object Access Services */ + SERVICE_CONFIRMED_ADD_LIST_ELEMENT = 8, + SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT = 9, + SERVICE_CONFIRMED_CREATE_OBJECT = 10, + SERVICE_CONFIRMED_DELETE_OBJECT = 11, + SERVICE_CONFIRMED_READ_PROPERTY = 12, + SERVICE_CONFIRMED_READ_PROPERTY_CONDITIONAL = 13, + SERVICE_CONFIRMED_READ_PROPERTY_MULTIPLE = 14, + SERVICE_CONFIRMED_READ_RANGE = 26, + SERVICE_CONFIRMED_WRITE_PROPERTY = 15, + SERVICE_CONFIRMED_WRITE_PROPERTY_MULTIPLE = 16, + /* Remote Device Management Services */ + SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL = 17, + SERVICE_CONFIRMED_PRIVATE_TRANSFER = 18, + SERVICE_CONFIRMED_TEXT_MESSAGE = 19, + SERVICE_CONFIRMED_REINITIALIZE_DEVICE = 20, + /* Virtual Terminal Services */ + SERVICE_CONFIRMED_VT_OPEN = 21, + SERVICE_CONFIRMED_VT_CLOSE = 22, + SERVICE_CONFIRMED_VT_DATA = 23, + /* Security Services */ + SERVICE_CONFIRMED_AUTHENTICATE = 24, + SERVICE_CONFIRMED_REQUEST_KEY = 25, + /* Services added after 1995 */ + /* readRange (26) see Object Access Services */ + /* lifeSafetyOperation (27) see Alarm and Event Services */ + /* subscribeCOVProperty (28) see Alarm and Event Services */ + /* getEventInformation (29) see Alarm and Event Services */ + MAX_BACNET_CONFIRMED_SERVICE = 30 +} BACNET_CONFIRMED_SERVICE; + +typedef enum { + SERVICE_UNCONFIRMED_I_AM = 0, + SERVICE_UNCONFIRMED_I_HAVE = 1, + SERVICE_UNCONFIRMED_COV_NOTIFICATION = 2, + SERVICE_UNCONFIRMED_EVENT_NOTIFICATION = 3, + SERVICE_UNCONFIRMED_PRIVATE_TRANSFER = 4, + SERVICE_UNCONFIRMED_TEXT_MESSAGE = 5, + SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION = 6, + SERVICE_UNCONFIRMED_WHO_HAS = 7, + SERVICE_UNCONFIRMED_WHO_IS = 8, + SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION = 9, + /* Other services to be added as they are defined. */ + /* All choice values in this production are reserved */ + /* for definition by ASHRAE. */ + /* Proprietary extensions are made by using the */ + /* UnconfirmedPrivateTransfer service. See Clause 23. */ + MAX_BACNET_UNCONFIRMED_SERVICE = 10 +} BACNET_UNCONFIRMED_SERVICE; + +/* Bit String Enumerations */ +typedef enum { + /* Alarm and Event Services */ + SERVICE_SUPPORTED_ACKNOWLEDGE_ALARM = 0, + SERVICE_SUPPORTED_CONFIRMED_COV_NOTIFICATION = 1, + SERVICE_SUPPORTED_CONFIRMED_EVENT_NOTIFICATION = 2, + SERVICE_SUPPORTED_GET_ALARM_SUMMARY = 3, + SERVICE_SUPPORTED_GET_ENROLLMENT_SUMMARY = 4, + SERVICE_SUPPORTED_GET_EVENT_INFORMATION = 39, + SERVICE_SUPPORTED_SUBSCRIBE_COV = 5, + SERVICE_SUPPORTED_SUBSCRIBE_COV_PROPERTY = 38, + SERVICE_SUPPORTED_LIFE_SAFETY_OPERATION = 37, + /* File Access Services */ + SERVICE_SUPPORTED_ATOMIC_READ_FILE = 6, + SERVICE_SUPPORTED_ATOMIC_WRITE_FILE = 7, + /* Object Access Services */ + SERVICE_SUPPORTED_ADD_LIST_ELEMENT = 8, + SERVICE_SUPPORTED_REMOVE_LIST_ELEMENT = 9, + SERVICE_SUPPORTED_CREATE_OBJECT = 10, + SERVICE_SUPPORTED_DELETE_OBJECT = 11, + SERVICE_SUPPORTED_READ_PROPERTY = 12, + SERVICE_SUPPORTED_READ_PROPERTY_CONDITIONAL = 13, + SERVICE_SUPPORTED_READ_PROPERTY_MULTIPLE = 14, + SERVICE_SUPPORTED_READ_RANGE = 35, + SERVICE_SUPPORTED_WRITE_PROPERTY = 15, + SERVICE_SUPPORTED_WRITE_PROPERTY_MULTIPLE = 16, + /* Remote Device Management Services */ + SERVICE_SUPPORTED_DEVICE_COMMUNICATION_CONTROL = 17, + SERVICE_SUPPORTED_PRIVATE_TRANSFER = 18, + SERVICE_SUPPORTED_TEXT_MESSAGE = 19, + SERVICE_SUPPORTED_REINITIALIZE_DEVICE = 20, + /* Virtual Terminal Services */ + SERVICE_SUPPORTED_VT_OPEN = 21, + SERVICE_SUPPORTED_VT_CLOSE = 22, + SERVICE_SUPPORTED_VT_DATA = 23, + /* Security Services */ + SERVICE_SUPPORTED_AUTHENTICATE = 24, + SERVICE_SUPPORTED_REQUEST_KEY = 25, + SERVICE_SUPPORTED_I_AM = 26, + SERVICE_SUPPORTED_I_HAVE = 27, + SERVICE_SUPPORTED_UNCONFIRMED_COV_NOTIFICATION = 28, + SERVICE_SUPPORTED_UNCONFIRMED_EVENT_NOTIFICATION = 29, + SERVICE_SUPPORTED_UNCONFIRMED_PRIVATE_TRANSFER = 30, + SERVICE_SUPPORTED_UNCONFIRMED_TEXT_MESSAGE = 31, + SERVICE_SUPPORTED_TIME_SYNCHRONIZATION = 32, + SERVICE_SUPPORTED_UTC_TIME_SYNCHRONIZATION = 36, + SERVICE_SUPPORTED_WHO_HAS = 33, + SERVICE_SUPPORTED_WHO_IS = 34, + /* Other services to be added as they are defined. */ + /* All values in this production are reserved */ + /* for definition by ASHRAE. */ + MAX_BACNET_SERVICES_SUPPORTED = 40 +} BACNET_SERVICES_SUPPORTED; + +typedef enum { + BVLC_RESULT = 0, + BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE = 1, + BVLC_READ_BROADCAST_DISTRIBUTION_TABLE = 2, + BVLC_READ_BROADCAST_DISTRIBUTION_TABLE_ACK = 3, + BVLC_FORWARDED_NPDU = 4, + BVLC_REGISTER_FOREIGN_DEVICE = 5, + BVLC_READ_FOREIGN_DEVICE_TABLE = 6, + BVLC_READ_FOREIGN_DEVICE_TABLE_ACK = 7, + BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY = 8, + BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK = 9, + BVLC_ORIGINAL_UNICAST_NPDU = 10, + BVLC_ORIGINAL_BROADCAST_NPDU = 11, + MAX_BVLC_FUNCTION = 12 +} BACNET_BVLC_FUNCTION; + +typedef enum { + BVLC_RESULT_SUCCESSFUL_COMPLETION = 0x0000, + BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK = 0x0010, + BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK = 0x0020, + BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK = 0X0030, + BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK = 0x0040, + BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK = 0x0050, + BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK = 0x0060 +} BACNET_BVLC_RESULT; + +/* Bit String Enumerations */ +typedef enum { + STATUS_FLAG_IN_ALARM = 0, + STATUS_FLAG_FAULT = 1, + STATUS_FLAG_OVERRIDDEN = 2, + STATUS_FLAG_OUT_OF_SERVICE = 3 +} BACNET_STATUS_FLAGS; + +typedef enum { + ACKNOWLEDGMENT_FILTER_ALL = 0, + ACKNOWLEDGMENT_FILTER_ACKED = 1, + ACKNOWLEDGMENT_FILTER_NOT_ACKED = 2 +} BACNET_ACKNOWLEDGMENT_FILTER; + +typedef enum { + EVENT_STATE_FILTER_OFFNORMAL = 0, + EVENT_STATE_FILTER_FAULT = 1, + EVENT_STATE_FILTER_NORMAL = 2, + EVENT_STATE_FILTER_ALL = 3, + EVENT_STATE_FILTER_ACTIVE = 4 +} BACNET_EVENT_STATE_FILTER; + +typedef enum { + SELECTION_LOGIC_AND = 0, + SELECTION_LOGIC_OR = 1, + SELECTION_LOGIC_ALL = 2 +} BACNET_SELECTION_LOGIC; + +typedef enum { + RELATION_SPECIFIER_EQUAL = 0, + RELATION_SPECIFIER_NOT_EQUAL = 1, + RELATION_SPECIFIER_LESS_THAN = 2, + RELATION_SPECIFIER_GREATER_THAN = 3, + RELATION_SPECIFIER_LESS_THAN_OR_EQUAL = 4, + RELATION_SPECIFIER_GREATER_THAN_OR_EQUAL = 5 +} BACNET_RELATION_SPECIFIER; + +typedef enum { + COMMUNICATION_ENABLE = 0, + COMMUNICATION_DISABLE = 1, + COMMUNICATION_DISABLE_INITIATION = 2, + MAX_BACNET_COMMUNICATION_ENABLE_DISABLE = 3 +} BACNET_COMMUNICATION_ENABLE_DISABLE; + +typedef enum { + MESSAGE_PRIORITY_NORMAL = 0, + MESSAGE_PRIORITY_URGENT = 1, + MESSAGE_PRIORITY_CRITICAL_EQUIPMENT = 2, + MESSAGE_PRIORITY_LIFE_SAFETY = 3 +} BACNET_MESSAGE_PRIORITY; + +/*Network Layer Message Type */ +/*If Bit 7 of the control octet described in 6.2.2 is 1, */ +/* a message type octet shall be present as shown in Figure 6-1. */ +/* The following message types are indicated: */ +typedef enum { + NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK = 0, + NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK = 1, + NETWORK_MESSAGE_I_COULD_BE_ROUTER_TO_NETWORK = 2, + NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK = 3, + NETWORK_MESSAGE_ROUTER_BUSY_TO_NETWORK = 4, + NETWORK_MESSAGE_ROUTER_AVAILABLE_TO_NETWORK = 5, + NETWORK_MESSAGE_INITIALIZE_ROUTING_TABLE = 6, + NETWORK_MESSAGE_INITIALIZE_ROUTING_TABLE_ACK = 7, + NETWORK_MESSAGE_ESTABLISH_CONNECTION_TO_NETWORK = 8, + NETWORK_MESSAGE_DISCONNECT_CONNECTION_TO_NETWORK = 9 + /* X'0A' to X'7F': Reserved for use by ASHRAE, */ + /* X'80' to X'FF': Available for vendor proprietary messages */ +} BACNET_NETWORK_MESSAGE_TYPE; + + +typedef enum { + REINITIALIZED_STATE_COLD_START = 0, + REINITIALIZED_STATE_WARM_START = 1, + REINITIALIZED_STATE_START_BACKUP = 2, + REINITIALIZED_STATE_END_BACKUP = 3, + REINITIALIZED_STATE_START_RESTORE = 4, + REINITIALIZED_STATE_END_RESTORE = 5, + REINITIALIZED_STATE_ABORT_RESTORE = 6, + REINITIALIZED_STATE_IDLE = 255 +} BACNET_REINITIALIZED_STATE_OF_DEVICE; + +typedef enum { + ABORT_REASON_OTHER = 0, + ABORT_REASON_BUFFER_OVERFLOW = 1, + ABORT_REASON_INVALID_APDU_IN_THIS_STATE = 2, + ABORT_REASON_PREEMPTED_BY_HIGHER_PRIORITY_TASK = 3, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED = 4, + /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ + /* Enumerated values 64-65535 may be used by others subject to */ + /* the procedures and constraints described in Clause 23. */ + MAX_BACNET_ABORT_REASON = 5, + FIRST_PROPRIETARY_ABORT_REASON = 64, + LAST_PROPRIETARY_ABORT_REASON = 65535 +} BACNET_ABORT_REASON; + +typedef enum { + REJECT_REASON_OTHER = 0, + REJECT_REASON_BUFFER_OVERFLOW = 1, + REJECT_REASON_INCONSISTENT_PARAMETERS = 2, + REJECT_REASON_INVALID_PARAMETER_DATA_TYPE = 3, + REJECT_REASON_INVALID_TAG = 4, + REJECT_REASON_MISSING_REQUIRED_PARAMETER = 5, + REJECT_REASON_PARAMETER_OUT_OF_RANGE = 6, + REJECT_REASON_TOO_MANY_ARGUMENTS = 7, + REJECT_REASON_UNDEFINED_ENUMERATION = 8, + REJECT_REASON_UNRECOGNIZED_SERVICE = 9, + /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ + /* Enumerated values 64-65535 may be used by others subject to */ + /* the procedures and constraints described in Clause 23. */ + MAX_BACNET_REJECT_REASON = 10, + FIRST_PROPRIETARY_REJECT_REASON = 64, + LAST_PROPRIETARY_REJECT_REASON = 65535 +} BACNET_BACNET_REJECT_REASON; + +typedef enum { + ERROR_CLASS_DEVICE = 0, + ERROR_CLASS_OBJECT = 1, + ERROR_CLASS_PROPERTY = 2, + ERROR_CLASS_RESOURCES = 3, + ERROR_CLASS_SECURITY = 4, + ERROR_CLASS_SERVICES = 5, + ERROR_CLASS_VT = 6, + /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ + /* Enumerated values 64-65535 may be used by others subject to */ + /* the procedures and constraints described in Clause 23. */ + MAX_BACNET_ERROR_CLASS = 7, + FIRST_PROPRIETARY_ERROR_CLASS = 64, + LAST_PROPRIETARY_ERROR_CLASS = 65535 +} BACNET_ERROR_CLASS; + +/* These are sorted in the order given in + Clause 18. ERROR, REJECT AND ABORT CODES + The Class and Code pairings are required + to be used in accordance with Clause 18. */ +typedef enum { + /* valid for all classes */ + ERROR_CODE_OTHER = 0, + + /* Error Class - Device */ + ERROR_CODE_DEVICE_BUSY = 3, + ERROR_CODE_CONFIGURATION_IN_PROGRESS = 2, + ERROR_CODE_OPERATIONAL_PROBLEM = 25, + + /* Error Class - Object */ + ERROR_CODE_DYNAMIC_CREATION_NOT_SUPPORTED = 4, + ERROR_CODE_NO_OBJECTS_OF_SPECIFIED_TYPE = 17, + ERROR_CODE_OBJECT_DELETION_NOT_PERMITTED = 23, + ERROR_CODE_OBJECT_IDENTIFIER_ALREADY_EXISTS = 24, + ERROR_CODE_READ_ACCESS_DENIED = 27, + ERROR_CODE_UNKNOWN_OBJECT = 31, + ERROR_CODE_UNSUPPORTED_OBJECT_TYPE = 36, + + /* Error Class - Property */ + ERROR_CODE_CHARACTER_SET_NOT_SUPPORTED = 41, + ERROR_CODE_DATATYPE_NOT_SUPPORTED = 47, + ERROR_CODE_INCONSISTENT_SELECTION_CRITERION = 8, + ERROR_CODE_INVALID_ARRAY_INDEX = 42, + ERROR_CODE_INVALID_DATA_TYPE = 9, + ERROR_CODE_NOT_COV_PROPERTY = 44, + ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED = 45, + ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY = 50, + /* ERROR_CODE_READ_ACCESS_DENIED = 27, */ + ERROR_CODE_UNKNOWN_PROPERTY = 32, + ERROR_CODE_VALUE_OUT_OF_RANGE = 37, + ERROR_CODE_WRITE_ACCESS_DENIED = 40, + + /* Error Class - Resources */ + ERROR_CODE_NO_SPACE_FOR_OBJECT = 18, + ERROR_CODE_NO_SPACE_TO_ADD_LIST_ELEMENT = 19, + ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY = 20, + + /* Error Class - Security */ + ERROR_CODE_AUTHENTICATION_FAILED = 1, + /* ERROR_CODE_CHARACTER_SET_NOT_SUPPORTED = 41, */ + ERROR_CODE_INCOMPATIBLE_SECURITY_LEVELS = 6, + ERROR_CODE_INVALID_OPERATOR_NAME = 12, + ERROR_CODE_KEY_GENERATION_ERROR = 15, + ERROR_CODE_PASSWORD_FAILURE = 26, + ERROR_CODE_SECURITY_NOT_SUPPORTED = 28, + ERROR_CODE_TIMEOUT = 30, + + /* Error Class - Services */ + /* ERROR_CODE_CHARACTER_SET_NOT_SUPPORTED = 41, */ + ERROR_CODE_COV_SUBSCRIPTION_FAILED = 43, + ERROR_CODE_DUPLICATE_NAME = 48, + ERROR_CODE_DUPLICATE_OBJECT_ID = 49, + ERROR_CODE_FILE_ACCESS_DENIED = 5, + ERROR_CODE_INCONSISTENT_PARAMETERS = 7, + ERROR_CODE_INVALID_CONFIGURATION_DATA = 46, + ERROR_CODE_INVALID_FILE_ACCESS_METHOD = 10, + ERROR_CODE_ERROR_CODE_INVALID_FILE_START_POSITION = 11, + ERROR_CODE_INVALID_PARAMETER_DATA_TYPE = 13, + ERROR_CODE_INVALID_TIME_STAMP = 14, + ERROR_CODE_MISSING_REQUIRED_PARAMETER = 16, + /* ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED = 45, */ + ERROR_CODE_PROPERTY_IS_NOT_A_LIST = 22, + ERROR_CODE_SERVICE_REQUEST_DENIED = 29, + + /* Error Class - VT */ + ERROR_CODE_UNKNOWN_VT_CLASS = 34, + ERROR_CODE_UNKNOWN_VT_SESSION = 35, + ERROR_CODE_NO_VT_SESSIONS_AVAILABLE = 21, + ERROR_CODE_VT_SESSION_ALREADY_CLOSED = 38, + ERROR_CODE_VT_SESSION_TERMINATION_FAILURE = 39, + + /* unused */ + ERROR_CODE_RESERVED1 = 33, + /* Enumerated values 0-255 are reserved for definition by ASHRAE. */ + /* Enumerated values 256-65535 may be used by others subject to */ + /* the procedures and constraints described in Clause 23. */ + /* The last enumeration used in this version is 50. */ + MAX_BACNET_ERROR_CODE = 51, + FIRST_PROPRIETARY_ERROR_CODE = 256, + LAST_PROPRIETARY_ERROR_CODE = 65535 +} BACNET_ERROR_CODE; + +typedef enum { + BACNET_REINIT_COLDSTART = 0, + BACNET_REINIT_WARMSTART = 1, + BACNET_REINIT_STARTBACKUP = 2, + BACNET_REINIT_ENDBACKUP = 3, + BACNET_REINIT_STARTRESTORE = 4, + BACNET_REINIT_ENDRESTORE = 5, + BACNET_REINIT_ABORTRESTORE = 6, + MAX_BACNET_REINITIALIZED_STATE = 7 +} BACNET_REINITIALIZED_STATE; + +typedef enum BACnetNodeType { + BACNET_NODE_UNKNOWN = 0, + BACNET_NODE_SYSTEM = 1, + BACNET_NODE_NETWORK = 2, + BACNET_NODE_DEVICE = 3, + BACNET_NODE_ORGANIZATIONAL = 4, + BACNET_NODE_AREA = 5, + BACNET_NODE_EQUIPMENT = 6, + BACNET_NODE_POINT = 7, + BACNET_NODE_COLLECTION = 8, + BACNET_NODE_PROPERTY = 9, + BACNET_NODE_FUNCTIONAL = 10, + BACNET_NODE_OTHER = 11 +} BACNET_NODE_TYPE; + +typedef enum BACnetShedState { + BACNET_SHED_INACTIVE = 0, + BACNET_SHED_REQUEST_PENDING = 1, + BACNET_SHED_COMPLIANT = 2, + BACNET_SHED_NON_COMPLIANT = 3 +} BACNET_SHED_STATE; + +typedef enum BACnetLightingOperation { + BACNET_LIGHTS_STOP = 0, + BACNET_LIGHTS_FADE_TO = 1, + BACNET_LIGHTS_FADE_TO_OVER = 2, + BACNET_LIGHTS_RAMP_TO = 3, + BACNET_LIGHTS_RAMP_TO_AT_RATE = 4, + BACNET_LIGHTS_RAMP_UP = 5, + BACNET_LIGHTS_RAMP_UP_AT_RATE = 6, + BACNET_LIGHTS_RAMP_DOWN = 7, + BACNET_LIGHTS_RAMP_DOWN_AT_RATE = 8, + BACNET_LIGHTS_STEP_UP = 9, + BACNET_LIGHTS_STEP_DOWN = 10, + BACNET_LIGHTS_STEP_UP_BY = 11, + BACNET_LIGHTS_STEP_DOWN_BY = 12, + BACNET_LIGHTS_GOTO_LEVEL = 13, + BACNET_LIGHTS_RELINQUISH = 14 +} BACNET_LIGHTING_OPERATION; + +#endif /* end of BACENUM_H */ diff --git a/bacnet-stack/bacerror.c b/bacnet-stack/bacerror.c index e69de29b..2e8110d6 100644 --- a/bacnet-stack/bacerror.c +++ b/bacnet-stack/bacerror.c @@ -0,0 +1,259 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacdcode.h" +#include "bacdef.h" + +/* encode service */ +int bacerror_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, + BACNET_CONFIRMED_SERVICE service, + BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu[0] = PDU_TYPE_ERROR; + apdu[1] = invoke_id; + apdu[2] = service; + apdu_len = 3; + /* service parameters */ + apdu_len += encode_tagged_enumerated(&apdu[apdu_len], error_class); + apdu_len += encode_tagged_enumerated(&apdu[apdu_len], error_code); + } + + return apdu_len; +} + +/* decode the application class and code */ +int bacerror_decode_error_class_and_code(uint8_t * apdu, + unsigned apdu_len, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + int decoded_value = 0; + + if (apdu_len) { + /* error class */ + len += decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + if (tag_number != BACNET_APPLICATION_TAG_ENUMERATED) + return 0; + len += + decode_enumerated(&apdu[len], len_value_type, &decoded_value); + if (error_class) + *error_class = decoded_value; + /* error code */ + len += decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + if (tag_number != BACNET_APPLICATION_TAG_ENUMERATED) + return 0; + len += + decode_enumerated(&apdu[len], len_value_type, &decoded_value); + if (error_code) + *error_code = decoded_value; + } + + return len; +} + +/* decode the service request only */ +int bacerror_decode_service_request(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, + BACNET_CONFIRMED_SERVICE * service, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int len = 0; + + if (apdu_len > 2) { + if (invoke_id) + *invoke_id = apdu[0]; + if (service) + *service = apdu[1]; + /* decode the application class and code */ + len = bacerror_decode_error_class_and_code(&apdu[2], + apdu_len - 2, error_class, error_code); + } + + return len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +/* decode the whole APDU - mainly used for unit testing */ +int bacerror_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, + BACNET_CONFIRMED_SERVICE * service, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int len = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu_len) { + if (apdu[0] != PDU_TYPE_ERROR) + return -1; + if (apdu_len > 1) { + len = bacerror_decode_service_request(&apdu[1], + apdu_len - 1, invoke_id, service, error_class, error_code); + } + } + + return len; +} + +void testBACError(Test * pTest) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + uint8_t invoke_id = 0; + BACNET_CONFIRMED_SERVICE service = 0; + BACNET_ERROR_CLASS error_class = 0; + BACNET_ERROR_CODE error_code = 0; + uint8_t test_invoke_id = 0; + BACNET_CONFIRMED_SERVICE test_service = 0; + BACNET_ERROR_CLASS test_error_class = 0; + BACNET_ERROR_CODE test_error_code = 0; + + len = bacerror_encode_apdu(&apdu[0], + invoke_id, service, error_class, error_code); + ct_test(pTest, len != 0); + apdu_len = len; + + len = bacerror_decode_apdu(&apdu[0], + apdu_len, + &test_invoke_id, + &test_service, &test_error_class, &test_error_code); + ct_test(pTest, len != -1); + ct_test(pTest, test_invoke_id == invoke_id); + ct_test(pTest, test_service == service); + ct_test(pTest, test_error_class == error_class); + ct_test(pTest, test_error_code == error_code); + + /* change type to get negative response */ + apdu[0] = PDU_TYPE_ABORT; + len = bacerror_decode_apdu(&apdu[0], + apdu_len, + &test_invoke_id, + &test_service, &test_error_class, &test_error_code); + ct_test(pTest, len == -1); + + /* test NULL APDU */ + len = bacerror_decode_apdu(NULL, + apdu_len, + &test_invoke_id, + &test_service, &test_error_class, &test_error_code); + ct_test(pTest, len == -1); + + /* force a zero length */ + len = bacerror_decode_apdu(&apdu[0], + 0, + &test_invoke_id, + &test_service, &test_error_class, &test_error_code); + ct_test(pTest, len == 0); + + + /* check them all... */ + for (service = 0; service < MAX_BACNET_CONFIRMED_SERVICE; service++) { + for (error_class = 0; + error_class < MAX_BACNET_ERROR_CLASS; error_class++) { + for (error_code = 0; + error_code < MAX_BACNET_ERROR_CODE; error_code++) { + len = bacerror_encode_apdu(&apdu[0], + invoke_id, service, error_class, error_code); + apdu_len = len; + ct_test(pTest, len != 0); + len = bacerror_decode_apdu(&apdu[0], + apdu_len, + &test_invoke_id, + &test_service, &test_error_class, &test_error_code); + ct_test(pTest, len != -1); + ct_test(pTest, test_invoke_id == invoke_id); + ct_test(pTest, test_service == service); + ct_test(pTest, test_error_class == error_class); + ct_test(pTest, test_error_code == error_code); + } + } + } + + /* max boundaries */ + service = 255; + error_class = LAST_PROPRIETARY_ERROR_CLASS; + error_code = LAST_PROPRIETARY_ERROR_CODE; + len = bacerror_encode_apdu(&apdu[0], + invoke_id, service, error_class, error_code); + apdu_len = len; + ct_test(pTest, len != 0); + len = bacerror_decode_apdu(&apdu[0], + apdu_len, + &test_invoke_id, + &test_service, &test_error_class, &test_error_code); + ct_test(pTest, len != -1); + ct_test(pTest, test_invoke_id == invoke_id); + ct_test(pTest, test_service == service); + ct_test(pTest, test_error_class == error_class); + ct_test(pTest, test_error_code == error_code); + +} + +#ifdef TEST_BACERROR +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Error", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testBACError); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_ERROR */ +#endif /* TEST */ diff --git a/bacnet-stack/bacerror.h b/bacnet-stack/bacerror.h index e69de29b..60b3c883 100644 --- a/bacnet-stack/bacerror.h +++ b/bacnet-stack/bacerror.h @@ -0,0 +1,74 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef BACERROR_H +#define BACERROR_H + +#include +#include +#include "bacenum.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + int bacerror_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, + BACNET_CONFIRMED_SERVICE service, + BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code); + + int bacerror_decode_service_request(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, + BACNET_CONFIRMED_SERVICE * service, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + + int bacerror_decode_error_class_and_code(uint8_t * apdu, + unsigned apdu_len, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + +#ifdef TEST +#include "ctest.h" + int bacerror_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, + BACNET_CONFIRMED_SERVICE * service, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + + void testBACError(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/bacint.c b/bacnet-stack/bacint.c index e69de29b..d9fd23c0 100644 --- a/bacnet-stack/bacint.c +++ b/bacnet-stack/bacint.c @@ -0,0 +1,580 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +/* BACnet Integer encoding and decoding */ + +#include +#include + +int encode_unsigned16(uint8_t * apdu, uint16_t value) +{ +#if BIG_ENDIAN + apdu[0] = (uint8_t)(value & 0x00ff); + apdu[1] = (uint8_t)((value & 0xff00) >> 8); +#else + apdu[0] = (uint8_t)((value & 0xff00) >> 8); + apdu[1] = (uint8_t)(value & 0x00ff); +#endif + + return 2; +} + +int decode_unsigned16(uint8_t * apdu, uint16_t * value) +{ + if (value) { +#if BIG_ENDIAN + *value = (uint16_t)(apdu[0] & 0x00ff); + *value |= ((uint16_t)((apdu[1] << 8) & 0xff00)); +#else + *value = (uint16_t)((apdu[0] << 8) & 0xff00); + *value |= ((uint16_t)(apdu[1] & 0x00ff)); +#endif + } + + return 2; +} + +int encode_unsigned24(uint8_t * apdu, uint32_t value) +{ +#if BIG_ENDIAN + apdu[0] = (uint8_t)(value & 0x0000ff); + apdu[1] = (uint8_t)((value & 0x00ff00) >> 8); + apdu[2] = (uint8_t)((value & 0xff0000) >> 16); +#else + apdu[0] = (uint8_t)((value & 0xff0000) >> 16); + apdu[1] = (uint8_t)((value & 0x00ff00) >> 8); + apdu[2] = (uint8_t)(value & 0x0000ff); +#endif + + return 3; +} + +int decode_unsigned24(uint8_t * apdu, uint32_t * value) +{ + if (value) { +#if BIG_ENDIAN + *value = (uint32_t)(apdu[0] & 0x000000ff); + *value |= ((uint32_t)((apdu[1] << 8) & 0x0000ff00)); + *value |= ((uint32_t)((apdu[2] << 16) & 0x00ff0000)); +#else + *value = ((uint32_t)((apdu[0] << 16) & 0x00ff0000)); + *value |= (uint32_t)((apdu[1] << 8) & 0x0000ff00); + *value |= ((uint32_t)(apdu[2] & 0x000000ff)); +#endif + } + + return 3; +} + +int encode_unsigned32(uint8_t * apdu, uint32_t value) +{ +#if BIG_ENDIAN + apdu[0] = (uint8_t)(value & 0x000000ff); + apdu[1] = (uint8_t)((value & 0x0000ff00) >> 8); + apdu[2] = (uint8_t)((value & 0x00ff0000) >> 16); + apdu[3] = (uint8_t)((value & 0xff000000) >> 24); +#else + apdu[0] = (uint8_t)((value & 0xff000000) >> 24); + apdu[1] = (uint8_t)((value & 0x00ff0000) >> 16); + apdu[2] = (uint8_t)((value & 0x0000ff00) >> 8); + apdu[3] = (uint8_t)(value & 0x000000ff); +#endif + + return 4; +} + +int decode_unsigned32(uint8_t * apdu, uint32_t * value) +{ + if (value) { +#if BIG_ENDIAN + *value = (uint32_t)(apdu[0] & 0x000000ff); + *value |= ((uint32_t)((apdu[1] << 8) & 0x0000ff00)); + *value |= ((uint32_t)((apdu[2] << 16) & 0x00ff0000)); + *value |= ((uint32_t)((apdu[3] << 24) & 0xff000000)); +#else + *value = ((uint32_t)((apdu[0] << 24) & 0xff000000)); + *value |= ((uint32_t)((apdu[1] << 16) & 0x00ff0000)); + *value |= ((uint32_t)((apdu[2] << 8) & 0x0000ff00)); + *value |= ((uint32_t)(apdu[3] & 0x000000ff)); +#endif + } + + return 4; +} + +int encode_signed8(uint8_t * apdu, int8_t value) +{ + apdu[0] = (uint8_t)value; + + return 1; +} + +int decode_signed8(uint8_t * apdu, int32_t * value) +{ + if (value) { +#if BIG_ENDIAN + /* negative - bit 7 is set */ + if (apdu[0] & 0x80) + *value = 0x00FFFFFF; + else + *value = 0; + *value |= ((int32_t)((apdu[0] << 24) & 0xff000000)); +#else + /* negative - bit 7 is set */ + if (apdu[0] & 0x80) + *value = 0xFFFFFF00; + else + *value = 0; + *value |= ((int32_t)(apdu[0] & 0x000000ff)); +#endif + } + + return 1; +} + +int encode_signed16(uint8_t * apdu, int16_t value) +{ +#if BIG_ENDIAN + apdu[0] = (uint8_t)(value & 0x00ff); + apdu[1] = (uint8_t)((value & 0xff00) >> 8); +#else + apdu[0] = (uint8_t)((value & 0xff00) >> 8); + apdu[1] = (uint8_t)(value & 0x00ff); +#endif + + return 2; +} + +int decode_signed16(uint8_t * apdu, int32_t * value) +{ + if (value) { +#if BIG_ENDIAN + /* negative - bit 7 is set */ + if (apdu[0] & 0x80) + *value = 0x0000FFFF; + else + *value = 0; + *value |= ((int32_t)((apdu[0] << 24) & 0xff000000)); + *value |= ((int32_t)((apdu[1] << 16) & 0x00ff0000)); +#else + /* negative - bit 7 is set */ + if (apdu[0] & 0x80) + *value = 0xFFFF0000; + else + *value = 0; + *value |= ((int32_t)((apdu[0] << 8) & 0x0000ff00)); + *value |= ((int32_t)(apdu[1] & 0x000000ff)); +#endif + } + + return 2; +} + +int encode_signed24(uint8_t * apdu, int32_t value) +{ +#if BIG_ENDIAN + apdu[0] = (uint8_t)(value & 0x0000ff); + apdu[1] = (uint8_t)((value & 0x00ff00) >> 8); + apdu[2] = (uint8_t)((value & 0xff0000) >> 16); +#else + apdu[0] = (uint8_t)((value & 0xff0000) >> 16); + apdu[1] = (uint8_t)((value & 0x00ff00) >> 8); + apdu[2] = (uint8_t)(value & 0x0000ff); +#endif + + return 3; +} + +int decode_signed24(uint8_t * apdu, int32_t * value) +{ + if (value) { +#if BIG_ENDIAN + /* negative - bit 7 is set */ + if (apdu[0] & 0x80) + *value = 0x000000FF; + else + *value = 0; + *value |= ((uint32_t)((apdu[0] << 8) & 0x0000ff00)); + *value |= ((uint32_t)((apdu[1] << 16) & 0x00ff0000)); + *value |= ((uint32_t)((apdu[2] << 24) & 0xff000000)); +#else + /* negative - bit 7 is set */ + if (apdu[0] & 0x80) + *value = 0xFF000000; + else + *value = 0; + *value |= ((uint32_t)((apdu[0] << 16) & 0x00ff0000)); + *value |= ((uint32_t)((apdu[1] << 8) & 0x0000ff00)); + *value |= ((uint32_t)(apdu[2] & 0x000000ff)); +#endif + } + + return 3; +} + +int encode_signed32(uint8_t * apdu, int32_t value) +{ +#if BIG_ENDIAN + apdu[0] = (uint8_t)(value & 0x000000ff); + apdu[1] = (uint8_t)((value & 0x0000ff00) >> 8); + apdu[2] = (uint8_t)((value & 0x00ff0000) >> 16); + apdu[3] = (uint8_t)((value & 0xff000000) >> 24); +#else + apdu[0] = (uint8_t)((value & 0xff000000) >> 24); + apdu[1] = (uint8_t)((value & 0x00ff0000) >> 16); + apdu[2] = (uint8_t)((value & 0x0000ff00) >> 8); + apdu[3] = (uint8_t)(value & 0x000000ff); +#endif + + return 4; +} + +int decode_signed32(uint8_t * apdu, int32_t * value) +{ + if (value) { +#if BIG_ENDIAN + *value = (int32_t)(apdu[0] & 0x000000ff); + *value |= ((int32_t)((apdu[1] << 8) & 0x0000ff00)); + *value |= ((int32_t)((apdu[2] << 16) & 0x00ff0000)); + *value |= ((int32_t)((apdu[3] << 24) & 0xff000000)); +#else + *value = ((int32_t)((apdu[0] << 24) & 0xff000000)); + *value |= ((int32_t)((apdu[1] << 16) & 0x00ff0000)); + *value |= ((int32_t)((apdu[2] << 8) & 0x0000ff00)); + *value |= ((int32_t)(apdu[3] & 0x000000ff)); +#endif + } + + return 4; +} + +/* from clause 20.2.5 Encoding of a Signed Integer Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int decode_signed(uint8_t * apdu, uint32_t len_value, int32_t * value) +{ + if (value) { + switch (len_value) { + case 1: + decode_signed8(&apdu[0], value); + break; + case 2: + decode_signed16(&apdu[0], value); + break; + case 3: + decode_signed24(&apdu[0], value); + break; + case 4: + decode_signed32(&apdu[0], value); + break; + default: + *value = 0; + break; + } + } + + return len_value; +} + +/* from clause 20.2.5 Encoding of a Signed Integer Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_bacnet_signed(uint8_t * apdu, int32_t value) +{ + int len = 0; /* return value */ + + /* don't encode the leading X'FF' or X'00' of the two's compliment. + That is, the first octet of any multi-octet encoded value shall + not be X'00' if the most significant bit (bit 7) of the second + octet is 0, and the first octet shall not be X'FF' if the most + significant bit of the second octet is 1. */ + if ((value >= -128) && (value < 128)) { + len = encode_signed8(&apdu[0], (int8_t) value); + } else if ((value >= -32768) && (value < 32768)) { + len = encode_signed16(&apdu[0], (int16_t) value); + } else if ((value > -8388608) && (value < 8388608)) { + len = encode_signed24(&apdu[0], value); + } else { + len = encode_signed32(&apdu[0], value); + } + + return len; +} + +/* from clause 20.2.4 Encoding of an Unsigned Integer Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int encode_bacnet_unsigned(uint8_t * apdu, uint32_t value) +{ + int len = 0; /* return value */ + + if (value < 0x100) { + apdu[0] = (uint8_t) value; + len = 1; + } else if (value < 0x10000) { + len = encode_unsigned16(&apdu[0], (uint16_t) value); + } else if (value < 0x1000000) { + len = encode_unsigned24(&apdu[0], value); + } else { + len = encode_unsigned32(&apdu[0], value); + } + + return len; +} + +/* from clause 20.2.4 Encoding of an Unsigned Integer Value */ +/* and 20.2.1 General Rules for Encoding BACnet Tags */ +/* returns the number of apdu bytes consumed */ +int decode_unsigned(uint8_t * apdu, uint32_t len_value, uint32_t * value) +{ + uint16_t unsigned16_value = 0; + + if (value) { + switch (len_value) { + case 1: + *value = apdu[0]; + break; + case 2: + decode_unsigned16(&apdu[0], &unsigned16_value); + *value = unsigned16_value; + break; + case 3: + decode_unsigned24(&apdu[0], value); + break; + case 4: + decode_unsigned32(&apdu[0], value); + break; + default: + *value = 0; + break; + } + } + + return len_value; +} + +/* end of decoding_encoding.c */ +#ifdef TEST +#include +#include +#include +#include "ctest.h" + +void testBACnetUnsigned16(Test * pTest) +{ + uint8_t apdu[32] = { 0 }; + uint16_t value = 0, test_value = 0; + int len = 0; + + for (value = 0; ; value++) { + len = encode_unsigned16(&apdu[0], value); + ct_test(pTest, len == 2); + len = decode_unsigned16(&apdu[0], &test_value); + ct_test(pTest, value == test_value); + if (value == 0xFFFF) + break; + } +} + +void testBACnetUnsigned24(Test * pTest) +{ + uint8_t apdu[32] = { 0 }; + uint32_t value = 0, test_value = 0; + int len = 0; + + for (value = 0; ; value+=0xf) { + len = encode_unsigned24(&apdu[0], value); + ct_test(pTest, len == 3); + len = decode_unsigned24(&apdu[0], &test_value); + ct_test(pTest, value == test_value); + if (value == 0xffffff) + break; + } +} + +void testBACnetUnsigned32(Test * pTest) +{ + uint8_t apdu[32] = { 0 }; + uint32_t value = 0, test_value = 0; + int len = 0; + + for (value = 0; ; value+=0xff) { + len = encode_unsigned32(&apdu[0], value); + ct_test(pTest, len == 4); + len = decode_unsigned32(&apdu[0], &test_value); + ct_test(pTest, value == test_value); + if (value == 0xffffffff) + break; + } +} + +void testBACnetSigned8(Test * pTest) +{ + uint8_t apdu[32] = { 0 }; + int32_t value = 0, test_value = 0; + int len = 0; + + for (value = -127; ; value++) { + len = encode_signed8(&apdu[0], value); + ct_test(pTest, len == 1); + len = decode_signed8(&apdu[0], &test_value); + ct_test(pTest, value == test_value); + if (value == 127) + break; + } +} + +void testBACnetSigned16(Test * pTest) +{ + uint8_t apdu[32] = { 0 }; + int32_t value = 0, test_value = 0; + int len = 0; + + for (value = -32767; ; value++) { + len = encode_signed16(&apdu[0], value); + ct_test(pTest, len == 2); + len = decode_signed16(&apdu[0], &test_value); + ct_test(pTest, value == test_value); + if (value == 32767) + break; + } +} + +void testBACnetSigned24(Test * pTest) +{ + uint8_t apdu[32] = { 0 }; + int32_t value = 0, test_value = 0; + int len = 0; + + for (value = -8388607; value <= 8388607; value+=15) { + len = encode_signed24(&apdu[0], value); + ct_test(pTest, len == 3); + len = decode_signed24(&apdu[0], &test_value); + ct_test(pTest, value == test_value); + } +} + +void testBACnetSigned32(Test * pTest) +{ + uint8_t apdu[32] = { 0 }; + int32_t value = 0, test_value = 0; + int len = 0; + + for (value = -2147483647; value < 0; value+=127) { + len = encode_signed32(&apdu[0], value); + ct_test(pTest, len == 4); + len = decode_signed32(&apdu[0], &test_value); + ct_test(pTest, value == test_value); + } + for (value = 2147483647; value > 0; value-=127) { + len = encode_signed32(&apdu[0], value); + ct_test(pTest, len == 4); + len = decode_signed32(&apdu[0], &test_value); + ct_test(pTest, value == test_value); + } +} + +void testBACnetSigned(Test * pTest) +{ + uint8_t apdu[32] = { 0 }; + int32_t value = 0, test_value = 0; + int len = 0, test_len = 0; + + for (value = -2147483647; value < 0; value+=127) { + len = encode_bacnet_signed(&apdu[0], value); + test_len = decode_signed(&apdu[0], len, &test_value); + ct_test(pTest, len == test_len); + ct_test(pTest, value == test_value); + } + for (value = 2147483647; value > 0; value-=127) { + len = encode_bacnet_signed(&apdu[0], value); + test_len = decode_signed(&apdu[0], len, &test_value); + ct_test(pTest, len == test_len); + ct_test(pTest, value == test_value); + } +} + +void testBACnetUnsigned(Test * pTest) +{ + uint8_t apdu[32] = { 0 }; + uint32_t value = 0, test_value = 0; + int len = 0, test_len = 0; + + for (value = 0; ;value+=0xFF) { + len = encode_bacnet_unsigned(&apdu[0], value); + test_len = decode_unsigned(&apdu[0], len, &test_value); + ct_test(pTest, len == test_len); + ct_test(pTest, value == test_value); + if (value == 0xFFFFFFFF) + break; + } +} + + +#ifdef TEST_BACINT +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACint", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testBACnetUnsigned16); + assert(rc); + rc = ct_addTestFunction(pTest, testBACnetUnsigned24); + assert(rc); + rc = ct_addTestFunction(pTest, testBACnetUnsigned32); + assert(rc); + rc = ct_addTestFunction(pTest, testBACnetSigned8); + assert(rc); + rc = ct_addTestFunction(pTest, testBACnetSigned16); + assert(rc); + rc = ct_addTestFunction(pTest, testBACnetSigned24); + assert(rc); + rc = ct_addTestFunction(pTest, testBACnetSigned32); + assert(rc); + rc = ct_addTestFunction(pTest, testBACnetSigned); + assert(rc); + rc = ct_addTestFunction(pTest, testBACnetUnsigned); + assert(rc); + /* configure output */ + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_DECODE */ +#endif /* TEST */ diff --git a/bacnet-stack/bacnet.h b/bacnet-stack/bacnet.h index e69de29b..78404f2c 100644 --- a/bacnet-stack/bacnet.h +++ b/bacnet-stack/bacnet.h @@ -0,0 +1,76 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef BACNET_H +#define BACNET_H + +/* This file is designed to reference the entire BACnet stack library */ + +/* core files */ +#include "config.h" +#include "address.h" +#include "apdu.h" +#include "bacapp.h" +#include "bacdcode.h" +#include "bacdef.h" +#include "bacenum.h" +#include "bacerror.h" +#include "bacstr.h" +#include "bactext.h" +#include "datalink.h" +#include "indtext.h" +#include "npdu.h" +#include "reject.h" +#include "tsm.h" + +/* services */ +#include "arf.h" +#include "awf.h" +#include "cov.h" +#include "dcc.h" +#include "iam.h" +#include "ihave.h" +#include "rd.h" +#include "rp.h" +#include "rpm.h" +#include "timesync.h" +#include "whohas.h" +#include "whois.h" +#include "wp.h" + +/* required object - note: developer must supply the device.c file + since it is not included in the library. However, the library + references the device.c members via the device.h API. */ +#include "device.h" + +#endif diff --git a/bacnet-stack/bacprop.c b/bacnet-stack/bacprop.c index e69de29b..7bfa0013 100644 --- a/bacnet-stack/bacprop.c +++ b/bacnet-stack/bacprop.c @@ -0,0 +1,111 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 John Goulah + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include +#include +#include "bacprop.h" + +PROP_TAG_DATA bacnet_object_device_property_tag_map[] = { + {PROP_OBJECT_IDENTIFIER, BACNET_APPLICATION_TAG_OBJECT_ID} + , + {PROP_OBJECT_NAME, BACNET_APPLICATION_TAG_CHARACTER_STRING} + , + {PROP_OBJECT_TYPE, BACNET_APPLICATION_TAG_ENUMERATED} + , + {PROP_SYSTEM_STATUS, BACNET_APPLICATION_TAG_ENUMERATED} + , + {PROP_VENDOR_NAME, BACNET_APPLICATION_TAG_CHARACTER_STRING} + , + {PROP_VENDOR_IDENTIFIER, BACNET_APPLICATION_TAG_UNSIGNED_INT} + , + {PROP_MODEL_NAME, BACNET_APPLICATION_TAG_CHARACTER_STRING} + , + {PROP_FIRMWARE_REVISION, BACNET_APPLICATION_TAG_CHARACTER_STRING} + , + {PROP_APPLICATION_SOFTWARE_VERSION, + BACNET_APPLICATION_TAG_CHARACTER_STRING} + , + {PROP_PROTOCOL_VERSION, BACNET_APPLICATION_TAG_UNSIGNED_INT} + , + {PROP_PROTOCOL_CONFORMANCE_CLASS, BACNET_APPLICATION_TAG_UNSIGNED_INT} + , + {PROP_PROTOCOL_SERVICES_SUPPORTED, BACNET_APPLICATION_TAG_BIT_STRING} + , + {PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, + BACNET_APPLICATION_TAG_BIT_STRING} + , + {PROP_MAX_APDU_LENGTH_ACCEPTED, BACNET_APPLICATION_TAG_UNSIGNED_INT} + , + {PROP_SEGMENTATION_SUPPORTED, BACNET_APPLICATION_TAG_ENUMERATED} + , + {PROP_APDU_TIMEOUT, BACNET_APPLICATION_TAG_UNSIGNED_INT} + , + {PROP_NUMBER_OF_APDU_RETRIES, BACNET_APPLICATION_TAG_UNSIGNED_INT} + , + {-1, -1} +}; + +signed bacprop_tag_by_index_default(PROP_TAG_DATA * data_list, + signed index, signed default_ret) +{ + signed pUnsigned = 0; + + if (data_list) { + while (data_list->prop_id != -1) { + if (data_list->prop_id == index) { + pUnsigned = data_list->tag_id; + break; + } + data_list++; + } + } + + return pUnsigned ? pUnsigned : default_ret; +} + + +signed bacprop_property_tag(BACNET_OBJECT_TYPE type, signed prop) +{ + switch (type) { + case OBJECT_DEVICE: + return + bacprop_tag_by_index_default + (bacnet_object_device_property_tag_map, prop, -1); + default: + fprintf(stderr, "Unsupported object type"); + break; + } + + return -1; +} diff --git a/bacnet-stack/bacprop.h b/bacnet-stack/bacprop.h index e69de29b..9fb34dc8 100644 --- a/bacnet-stack/bacprop.h +++ b/bacnet-stack/bacprop.h @@ -0,0 +1,58 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 John Goulah + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef BACPROP_H +#define BACPROP_H + +#include +#include +#include "bacenum.h" + +typedef struct { + signed prop_id; /* index number that matches the text */ + signed tag_id; /* text pair - use NULL to end the list */ +} PROP_TAG_DATA; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + signed bacprop_tag_by_index_default(PROP_TAG_DATA * data_list, + signed index, signed default_ret); + + signed bacprop_property_tag(BACNET_OBJECT_TYPE type, signed prop); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/bacstr.c b/bacnet-stack/bacstr.c index e69de29b..cd142503 100644 --- a/bacnet-stack/bacstr.c +++ b/bacnet-stack/bacstr.c @@ -0,0 +1,637 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#include +#include +#include /* for strlen */ +#include "bacstr.h" +#include "bits.h" + +void bitstring_init(BACNET_BIT_STRING * bit_string) +{ + int i; + + bit_string->bits_used = 0; + for (i = 0; i < MAX_BITSTRING_BYTES; i++) { + bit_string->value[i] = 0; + } +} + +void bitstring_set_bit(BACNET_BIT_STRING * bit_string, uint8_t bit, + bool value) +{ + uint8_t byte_number = bit / 8; + uint8_t bit_mask = 1; + + if (byte_number < MAX_BITSTRING_BYTES) { + /* set max bits used */ + if (bit_string->bits_used < (bit + 1)) + bit_string->bits_used = bit + 1; + bit_mask = bit_mask << (bit - (byte_number * 8)); + if (value) + bit_string->value[byte_number] |= bit_mask; + else + bit_string->value[byte_number] &= (~(bit_mask)); + } +} + +bool bitstring_bit(BACNET_BIT_STRING * bit_string, uint8_t bit) +{ + bool value = false; + uint8_t byte_number = bit / 8; + uint8_t bit_mask = 1; + + if (bit < (MAX_BITSTRING_BYTES * 8)) { + bit_mask = bit_mask << (bit - (byte_number * 8)); + if (bit_string->value[byte_number] & bit_mask) + value = true; + } + + return value; +} + +uint8_t bitstring_bits_used(BACNET_BIT_STRING * bit_string) +{ + return bit_string->bits_used; +} + +/* returns the number of bytes that a bit string is using */ +int bitstring_bytes_used(BACNET_BIT_STRING * bit_string) +{ + int len = 0; /* return value */ + uint8_t used_bytes = 0; + uint8_t last_bit = 0; + + if (bit_string->bits_used) { + last_bit = bit_string->bits_used - 1; + used_bytes = last_bit / 8; + /* add one for the first byte */ + used_bytes++; + len = used_bytes; + } + + return len; +} + +uint8_t bitstring_octet(BACNET_BIT_STRING * bit_string, uint8_t index) +{ + uint8_t octet = 0; + + if (bit_string) { + if (index < MAX_BITSTRING_BYTES) { + octet = bit_string->value[index]; + } + } + + return octet; +} + +bool bitstring_set_octet(BACNET_BIT_STRING * bit_string, + uint8_t index, uint8_t octet) +{ + bool status = false; + + if (bit_string) { + if (index < MAX_BITSTRING_BYTES) { + bit_string->value[index] = octet; + status = true; + } + } + + return status; +} + +bool bitstring_set_bits_used(BACNET_BIT_STRING * bit_string, + uint8_t bytes_used, uint8_t unused_bits) +{ + bool status = false; + + if (bit_string) { + /* FIXME: check that bytes_used is at least one? */ + bit_string->bits_used = bytes_used * 8; + bit_string->bits_used -= unused_bits; + status = true; + } + + return status; +} + +uint8_t bitstring_bits_capacity(BACNET_BIT_STRING * bit_string) +{ + if (bit_string) + return (sizeof(bit_string->value) * 8); + else + return 0; +} + +#define CHARACTER_STRING_CAPACITY (MAX_CHARACTER_STRING_BYTES - 1) +/* returns false if the string exceeds capacity + initialize by using length=0 */ +bool characterstring_init(BACNET_CHARACTER_STRING * char_string, + uint8_t encoding, char *value, size_t length) +{ + bool status = false; /* return value */ + size_t i; /* counter */ + + if (char_string) { + char_string->length = 0; + char_string->encoding = encoding; + /* save a byte at the end for NULL - + note: assumes printable characters */ + if (length <= CHARACTER_STRING_CAPACITY) { + if (value) { + for (i = 0; i < MAX_CHARACTER_STRING_BYTES; i++) { + if (i < length) { + char_string->value[char_string->length] = value[i]; + char_string->length++; + } else + char_string->value[i] = 0; + } + } else { + for (i = 0; i < MAX_CHARACTER_STRING_BYTES; i++) { + char_string->value[i] = 0; + } + } + status = true; + } + } + + return status; +} + +bool characterstring_init_ansi(BACNET_CHARACTER_STRING * char_string, + char *value) +{ + return characterstring_init(char_string, + CHARACTER_ANSI_X34, value, value ? strlen(value) : 0); +} + +bool characterstring_copy(BACNET_CHARACTER_STRING * dest, + BACNET_CHARACTER_STRING * src) +{ + return characterstring_init(dest, + characterstring_encoding(src), + characterstring_value(src), characterstring_length(src)); +} + +bool characterstring_same(BACNET_CHARACTER_STRING * dest, + BACNET_CHARACTER_STRING * src) +{ + size_t i; /* counter */ + bool same_status = false; + + if (src && dest) { + if ((src->length == dest->length) && + (src->encoding == dest->encoding)) { + same_status = true; + for (i = 0; i < src->length; i++) { + if (src->value[i] != dest->value[i]) { + same_status = false; + break; + } + } + } + } else if (src) { + if (src->length == 0) + same_status = true; + } else if (dest) { + if (dest->length == 0) + same_status = true; + } + + return same_status; +} + +bool characterstring_ansi_same(BACNET_CHARACTER_STRING * dest, + const char *src) +{ + size_t i; /* counter */ + bool same_status = false; + + if (src && dest) { + if ((dest->length == strlen(src)) && + (dest->encoding == CHARACTER_ANSI_X34)) { + same_status = true; + for (i = 0; i < dest->length; i++) { + if (src[i] != dest->value[i]) { + same_status = false; + break; + } + } + } + } + /* NULL matches an empty string in our world */ + else if (src) { + if (strlen(src) == 0) + same_status = true; + } else if (dest) { + if (dest->length == 0) + same_status = true; + } + + return same_status; +} + +/* returns false if the string exceeds capacity */ +bool characterstring_append(BACNET_CHARACTER_STRING * char_string, + char *value, size_t length) +{ + size_t i; /* counter */ + bool status = false; /* return value */ + + if (char_string) { + if ((length + char_string->length) <= CHARACTER_STRING_CAPACITY) { + for (i = 0; i < length; i++) { + char_string->value[char_string->length] = value[i]; + char_string->length++; + } + status = true; + } + } + + return status; +} + +/* This function sets a new length without changing the value. + If length exceeds capacity, no modification happens and + function returns false. */ +bool characterstring_truncate(BACNET_CHARACTER_STRING * char_string, + size_t length) +{ + bool status = false; /* return value */ + + if (char_string) { + if (length <= CHARACTER_STRING_CAPACITY) { + char_string->length = length; + status = true; + } + } + + return status; +} + +/* Returns the value. */ +char *characterstring_value(BACNET_CHARACTER_STRING * char_string) +{ + char *value = NULL; + + if (char_string) { + value = char_string->value; + } + + return value; +} + +/* returns the length. */ +size_t characterstring_length(BACNET_CHARACTER_STRING * char_string) +{ + size_t length = 0; + + if (char_string) { + /* FIXME: validate length is within bounds? */ + length = char_string->length; + } + + return length; +} + +size_t characterstring_capacity(BACNET_CHARACTER_STRING * char_string) +{ + size_t length = 0; + + if (char_string) { + length = CHARACTER_STRING_CAPACITY; + } + + return length; +} + +/* returns the encoding. */ +uint8_t characterstring_encoding(BACNET_CHARACTER_STRING * char_string) +{ + uint8_t encoding = 0; + + if (char_string) { + encoding = char_string->encoding; + } + + return encoding; +} + +/* returns false if the string exceeds capacity + initialize by using length=0 */ +bool octetstring_init(BACNET_OCTET_STRING * octet_string, + uint8_t * value, size_t length) +{ + bool status = false; /* return value */ + size_t i; /* counter */ + + if (octet_string) { + octet_string->length = 0; + if (length <= sizeof(octet_string->value)) { + if (value) { + for (i = 0; i < length; i++) { + octet_string->value[octet_string->length] = value[i]; + octet_string->length++; + } + } else { + for (i = 0; i < sizeof(octet_string->value); i++) { + octet_string->value[i] = 0; + } + } + status = true; + } + } + + return status; +} + +bool octetstring_copy(BACNET_OCTET_STRING * dest, + BACNET_OCTET_STRING * src) +{ + return octetstring_init(dest, + octetstring_value(src), octetstring_length(src)); +} + +/* returns false if the string exceeds capacity */ +bool octetstring_append(BACNET_OCTET_STRING * octet_string, + uint8_t * value, size_t length) +{ + size_t i; /* counter */ + bool status = false; /* return value */ + + if (octet_string) { + if ((length + octet_string->length) <= sizeof(octet_string->value)) { + for (i = 0; i < length; i++) { + octet_string->value[octet_string->length] = value[i]; + octet_string->length++; + } + status = true; + } + } + + return status; +} + +/* This function sets a new length without changing the value. + If length exceeds capacity, no modification happens and + function returns false. */ +bool octetstring_truncate(BACNET_OCTET_STRING * octet_string, + size_t length) +{ + bool status = false; /* return value */ + + if (octet_string) { + if (length <= sizeof(octet_string->value)) { + octet_string->length = length; + status = true; + } + } + + return status; +} + +/* returns the length. Returns the value in parameter. */ +uint8_t *octetstring_value(BACNET_OCTET_STRING * octet_string) +{ + uint8_t *value = NULL; + + if (octet_string) { + value = octet_string->value; + } + + return value; +} + +/* returns the length. */ +size_t octetstring_length(BACNET_OCTET_STRING * octet_string) +{ + size_t length = 0; + + if (octet_string) { + /* FIXME: validate length is within bounds? */ + length = octet_string->length; + } + + return length; +} + +/* returns the length. */ +size_t octetstring_capacity(BACNET_OCTET_STRING * octet_string) +{ + size_t length = 0; + + if (octet_string) { + /* FIXME: validate length is within bounds? */ + length = sizeof(octet_string->value); + } + + return length; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testBitString(Test * pTest) +{ + uint8_t bit = 0; + BACNET_BIT_STRING bit_string; + + bitstring_init(&bit_string); + /* verify initialization */ + ct_test(pTest, bitstring_bits_used(&bit_string) == 0); + for (bit = 0; bit < (MAX_BITSTRING_BYTES * 8); bit++) { + ct_test(pTest, bitstring_bit(&bit_string, bit) == false); + } + + /* test for true */ + for (bit = 0; bit < (MAX_BITSTRING_BYTES * 8); bit++) { + bitstring_set_bit(&bit_string, bit, true); + ct_test(pTest, bitstring_bits_used(&bit_string) == (bit + 1)); + ct_test(pTest, bitstring_bit(&bit_string, bit) == true); + } + /* test for false */ + bitstring_init(&bit_string); + for (bit = 0; bit < (MAX_BITSTRING_BYTES * 8); bit++) { + bitstring_set_bit(&bit_string, bit, false); + ct_test(pTest, bitstring_bits_used(&bit_string) == (bit + 1)); + ct_test(pTest, bitstring_bit(&bit_string, bit) == false); + } +} + +void testCharacterString(Test * pTest) +{ + BACNET_CHARACTER_STRING bacnet_string; + char *value = "Joshua,Mary,Anna,Christopher"; + char test_value[MAX_APDU] = "Patricia"; + char test_append_value[MAX_APDU] = " and the Kids"; + char test_append_string[MAX_APDU] = ""; + bool status = false; + size_t length = 0; + size_t test_length = 0; + size_t i = 0; + + /* verify initialization */ + status = + characterstring_init(&bacnet_string, CHARACTER_ANSI_X34, NULL, 0); + ct_test(pTest, status == true); + ct_test(pTest, characterstring_length(&bacnet_string) == 0); + ct_test(pTest, + characterstring_encoding(&bacnet_string) == CHARACTER_ANSI_X34); + /* bounds check */ + status = characterstring_init(&bacnet_string, + CHARACTER_ANSI_X34, + NULL, characterstring_capacity(&bacnet_string) + 1); + ct_test(pTest, status == false); + status = + characterstring_truncate(&bacnet_string, + characterstring_capacity(&bacnet_string) + 1); + ct_test(pTest, status == false); + status = + characterstring_truncate(&bacnet_string, + characterstring_capacity(&bacnet_string)); + ct_test(pTest, status == true); + + test_length = strlen(test_value); + status = characterstring_init(&bacnet_string, + CHARACTER_ANSI_X34, &test_value[0], test_length); + ct_test(pTest, status == true); + value = characterstring_value(&bacnet_string); + length = characterstring_length(&bacnet_string); + ct_test(pTest, length == test_length); + for (i = 0; i < test_length; i++) { + ct_test(pTest, value[i] == test_value[i]); + } + test_length = strlen(test_append_value); + status = characterstring_append(&bacnet_string, + &test_append_value[0], test_length); + strcat(test_append_string, test_value); + strcat(test_append_string, test_append_value); + test_length = strlen(test_append_string); + ct_test(pTest, status == true); + length = characterstring_length(&bacnet_string); + value = characterstring_value(&bacnet_string); + ct_test(pTest, length == test_length); + for (i = 0; i < test_length; i++) { + ct_test(pTest, value[i] == test_append_string[i]); + } +} + +void testOctetString(Test * pTest) +{ + BACNET_OCTET_STRING bacnet_string; + uint8_t *value = NULL; + uint8_t test_value[MAX_APDU] = "Patricia"; + uint8_t test_append_value[MAX_APDU] = " and the Kids"; + uint8_t test_append_string[MAX_APDU] = ""; + bool status = false; + size_t length = 0; + size_t test_length = 0; + size_t i = 0; + + /* verify initialization */ + status = octetstring_init(&bacnet_string, NULL, 0); + ct_test(pTest, status == true); + ct_test(pTest, octetstring_length(&bacnet_string) == 0); + value = octetstring_value(&bacnet_string); + for (i = 0; i < octetstring_capacity(&bacnet_string); i++) { + ct_test(pTest, value[i] == 0); + } + /* bounds check */ + status = octetstring_init(&bacnet_string, NULL, + octetstring_capacity(&bacnet_string) + 1); + ct_test(pTest, status == false); + status = octetstring_init(&bacnet_string, NULL, + octetstring_capacity(&bacnet_string)); + ct_test(pTest, status == true); + status = + octetstring_truncate(&bacnet_string, + octetstring_capacity(&bacnet_string) + 1); + ct_test(pTest, status == false); + status = + octetstring_truncate(&bacnet_string, + octetstring_capacity(&bacnet_string)); + ct_test(pTest, status == true); + + test_length = strlen((char *) test_value); + status = octetstring_init(&bacnet_string, &test_value[0], test_length); + ct_test(pTest, status == true); + length = octetstring_length(&bacnet_string); + value = octetstring_value(&bacnet_string); + ct_test(pTest, length == test_length); + for (i = 0; i < test_length; i++) { + ct_test(pTest, value[i] == test_value[i]); + } + + test_length = strlen((char *) test_append_value); + status = octetstring_append(&bacnet_string, + &test_append_value[0], test_length); + strcat((char *) test_append_string, (char *) test_value); + strcat((char *) test_append_string, (char *) test_append_value); + test_length = strlen((char *) test_append_string); + ct_test(pTest, status == true); + length = octetstring_length(&bacnet_string); + value = octetstring_value(&bacnet_string); + ct_test(pTest, length == test_length); + for (i = 0; i < test_length; i++) { + ct_test(pTest, value[i] == test_append_string[i]); + } +} + +#ifdef TEST_BACSTR +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Strings", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testBitString); + assert(rc); + rc = ct_addTestFunction(pTest, testCharacterString); + assert(rc); + rc = ct_addTestFunction(pTest, testOctetString); + assert(rc); + /* configure output */ + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_BACSTR */ +#endif /* TEST */ diff --git a/bacnet-stack/bacstr.h b/bacnet-stack/bacstr.h index e69de29b..52637002 100644 --- a/bacnet-stack/bacstr.h +++ b/bacnet-stack/bacstr.h @@ -0,0 +1,141 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef BACSTR_H +#define BACSTR_H + +#include +#include +#include +#include "bacdef.h" + +/* bit strings + They could be as large as 256/8=32 octets */ +#define MAX_BITSTRING_BYTES 15 +typedef struct BACnet_Bit_String { + uint8_t bits_used; + uint8_t value[MAX_BITSTRING_BYTES]; +} BACNET_BIT_STRING; + +#define MAX_CHARACTER_STRING_BYTES (MAX_APDU-6) +typedef struct BACnet_Character_String { + size_t length; + uint8_t encoding; + /* limit - 6 octets is the most our tag and type could be */ + char value[MAX_CHARACTER_STRING_BYTES]; +} BACNET_CHARACTER_STRING; + +/* FIXME: convert the bacdcode library to use BACNET_OCTET_STRING + for APDU buffer to prevent buffer overflows */ +typedef struct BACnet_Octet_String { + size_t length; + /* limit - 6 octets is the most our tag and type could be */ + uint8_t value[MAX_APDU - 6]; +} BACNET_OCTET_STRING; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + void bitstring_init(BACNET_BIT_STRING * bit_string); + void bitstring_set_bit(BACNET_BIT_STRING * bit_string, uint8_t bit, + bool value); + bool bitstring_bit(BACNET_BIT_STRING * bit_string, uint8_t bit); + uint8_t bitstring_bits_used(BACNET_BIT_STRING * bit_string); +/* returns the number of bytes that a bit string is using */ + int bitstring_bytes_used(BACNET_BIT_STRING * bit_string); + uint8_t bitstring_bits_capacity(BACNET_BIT_STRING * bit_string); +/* used for encoding and decoding from the APDU */ + uint8_t bitstring_octet(BACNET_BIT_STRING * bit_string, + uint8_t octet_index); + bool bitstring_set_octet(BACNET_BIT_STRING * bit_string, uint8_t index, + uint8_t octet); + bool bitstring_set_bits_used(BACNET_BIT_STRING * bit_string, + uint8_t bytes_used, uint8_t unused_bits); + +/* returns false if the string exceeds capacity + initialize by using length=0 */ + bool characterstring_init(BACNET_CHARACTER_STRING * char_string, + uint8_t encoding, char *value, size_t length); +/* used for ANSI C-Strings */ + bool characterstring_init_ansi(BACNET_CHARACTER_STRING * char_string, + char *value); + bool characterstring_copy(BACNET_CHARACTER_STRING * dest, + BACNET_CHARACTER_STRING * src); +/* returns true if the strings are the same length, encoding, value */ + bool characterstring_same(BACNET_CHARACTER_STRING * dest, + BACNET_CHARACTER_STRING * src); + bool characterstring_ansi_same(BACNET_CHARACTER_STRING * dest, + const char *src); +/* returns false if the string exceeds capacity */ + bool characterstring_append(BACNET_CHARACTER_STRING * char_string, + char *value, size_t length); +/* This function sets a new length without changing the value. + If length exceeds capacity, no modification happens and + function returns false. */ + bool characterstring_truncate(BACNET_CHARACTER_STRING * char_string, + size_t length); + bool characterstring_set_encoding(BACNET_CHARACTER_STRING * + char_string, uint8_t encoding); +/* Returns the value */ + char *characterstring_value(BACNET_CHARACTER_STRING * char_string); +/* returns the length */ + size_t characterstring_length(BACNET_CHARACTER_STRING * char_string); + uint8_t characterstring_encoding(BACNET_CHARACTER_STRING * + char_string); + size_t characterstring_capacity(BACNET_CHARACTER_STRING * char_string); + + /* returns false if the string exceeds capacity + initialize by using length=0 */ + bool octetstring_init(BACNET_OCTET_STRING * octet_string, + uint8_t * value, size_t length); + bool octetstring_copy(BACNET_OCTET_STRING * dest, + BACNET_OCTET_STRING * src); +/* returns false if the string exceeds capacity */ + bool octetstring_append(BACNET_OCTET_STRING * octet_string, + uint8_t * value, size_t length); +/* This function sets a new length without changing the value. + If length exceeds capacity, no modification happens and + function returns false. */ + bool octetstring_truncate(BACNET_OCTET_STRING * octet_string, + size_t length); +/* Returns the value */ + uint8_t *octetstring_value(BACNET_OCTET_STRING * octet_string); +/* Returns the length.*/ + size_t octetstring_length(BACNET_OCTET_STRING * octet_string); + size_t octetstring_capacity(BACNET_OCTET_STRING * octet_string); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/bactext.c b/bacnet-stack/bactext.c index e69de29b..46a8f4ff 100644 --- a/bacnet-stack/bactext.c +++ b/bacnet-stack/bactext.c @@ -0,0 +1,1494 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005-2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#include +#include "indtext.h" +#include "bacenum.h" + +static const char *ASHRAE_Reserved_String = "Reserved for Use by ASHRAE"; +static const char *Vendor_Proprietary_String = "Vendor Proprietary Value"; + +INDTEXT_DATA bacnet_confirmed_service_names[] = { + {SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM, "Acknowledge-Alarm"}, + {SERVICE_CONFIRMED_COV_NOTIFICATION, "COV-Notification"}, + {SERVICE_CONFIRMED_EVENT_NOTIFICATION, "Event-Notification"}, + {SERVICE_CONFIRMED_GET_ALARM_SUMMARY, "Get-Alarm-Summary"}, + {SERVICE_CONFIRMED_GET_ENROLLMENT_SUMMARY, "Get-Enrollment-Summary"}, + {SERVICE_CONFIRMED_SUBSCRIBE_COV, "Subscribe-COV"}, + {SERVICE_CONFIRMED_ATOMIC_READ_FILE, "Atomic-Read-File"}, + {SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, "Atomic-Write-File"}, + {SERVICE_CONFIRMED_ADD_LIST_ELEMENT, "Add-List-Element"}, + {SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT, "Remove-List-Element"}, + {SERVICE_CONFIRMED_CREATE_OBJECT, "Create-Object"}, + {SERVICE_CONFIRMED_DELETE_OBJECT, "Delete-Object"}, + {SERVICE_CONFIRMED_READ_PROPERTY, "Read-Property"}, + {SERVICE_CONFIRMED_READ_PROPERTY_CONDITIONAL, + "Read-Property-Conditional"}, + {SERVICE_CONFIRMED_READ_PROPERTY_MULTIPLE, "Read-Property-Multiple"}, + {SERVICE_CONFIRMED_WRITE_PROPERTY, "Write-Property"}, + {SERVICE_CONFIRMED_WRITE_PROPERTY_MULTIPLE, "Write-Property-Multiple"}, + {SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + "Device-Communication-Control"}, + {SERVICE_CONFIRMED_PRIVATE_TRANSFER, "Private-Transfer"}, + {SERVICE_CONFIRMED_TEXT_MESSAGE, "Text-Message"}, + {SERVICE_CONFIRMED_REINITIALIZE_DEVICE, "Reinitialize-Device"}, + {SERVICE_CONFIRMED_VT_OPEN, "VT-Open"}, + {SERVICE_CONFIRMED_VT_CLOSE, "VT-Close"}, + {SERVICE_CONFIRMED_VT_DATA, "VT-Data"}, + {SERVICE_CONFIRMED_AUTHENTICATE, "Authenticate"}, + {SERVICE_CONFIRMED_REQUEST_KEY, "Request-Key"}, + {SERVICE_CONFIRMED_READ_RANGE, "Read-Range"}, + {SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION, "Life-Safety_Operation"}, + {SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, "Subscribe-COV-Property"}, + {SERVICE_CONFIRMED_GET_EVENT_INFORMATION, "Get-Event-Information"}, + {0, NULL} +}; + +const char *bactext_confirmed_service_name(int index) +{ + return indtext_by_index_default(bacnet_confirmed_service_names, + index, ASHRAE_Reserved_String); +} + +INDTEXT_DATA bacnet_unconfirmed_service_names[] = { + {SERVICE_UNCONFIRMED_I_AM, "I-Am"} + , + {SERVICE_UNCONFIRMED_I_HAVE, "I-Have"} + , + {SERVICE_UNCONFIRMED_COV_NOTIFICATION, "COV-Notification"} + , + {SERVICE_UNCONFIRMED_EVENT_NOTIFICATION, "Event-Notification"} + , + {SERVICE_UNCONFIRMED_PRIVATE_TRANSFER, "Private-Transfer"} + , + {SERVICE_UNCONFIRMED_TEXT_MESSAGE, "Text-Message"} + , + {SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION, "Time-Synchronization"} + , + {SERVICE_UNCONFIRMED_WHO_HAS, "Who-Has"} + , + {SERVICE_UNCONFIRMED_WHO_IS, "Who-Is"} + , + {SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION, + "UTC-Time-Synchronization"} + , + {0, NULL} +}; + +const char *bactext_unconfirmed_service_name(int index) +{ + return indtext_by_index_default(bacnet_unconfirmed_service_names, + index, ASHRAE_Reserved_String); +} + +INDTEXT_DATA bacnet_application_tag_names[] = { + {BACNET_APPLICATION_TAG_NULL, "Null"} + , + {BACNET_APPLICATION_TAG_BOOLEAN, "Boolean"} + , + {BACNET_APPLICATION_TAG_UNSIGNED_INT, "Unsigned Int"} + , + {BACNET_APPLICATION_TAG_SIGNED_INT, "Signed Int"} + , + {BACNET_APPLICATION_TAG_REAL, "Real"} + , + {BACNET_APPLICATION_TAG_DOUBLE, "Double"} + , + {BACNET_APPLICATION_TAG_OCTET_STRING, "Octet String"} + , + {BACNET_APPLICATION_TAG_CHARACTER_STRING, "Character String"} + , + {BACNET_APPLICATION_TAG_BIT_STRING, "Bit String"} + , + {BACNET_APPLICATION_TAG_ENUMERATED, "Enumerated"} + , + {BACNET_APPLICATION_TAG_DATE, "Date"} + , + {BACNET_APPLICATION_TAG_TIME, "Time"} + , + {BACNET_APPLICATION_TAG_OBJECT_ID, "Object ID"} + , + {BACNET_APPLICATION_TAG_RESERVED1, "Reserved 1"} + , + {BACNET_APPLICATION_TAG_RESERVED2, "Reserved 2"} + , + {BACNET_APPLICATION_TAG_RESERVED3, "Reserved 3"} + , + {0, NULL} +}; + +const char *bactext_application_tag_name(int index) +{ + return indtext_by_index_default(bacnet_application_tag_names, + index, ASHRAE_Reserved_String); +} + +INDTEXT_DATA bacnet_object_type_names[] = { + {OBJECT_ANALOG_INPUT, "Analog Input"} + , + {OBJECT_ANALOG_OUTPUT, "Analog Output"} + , + {OBJECT_ANALOG_VALUE, "Analog Value"} + , + {OBJECT_BINARY_INPUT, "Binary Input"} + , + {OBJECT_BINARY_OUTPUT, "Binary Output"} + , + {OBJECT_BINARY_VALUE, "Binary Value"} + , + {OBJECT_CALENDAR, "Calendar"} + , + {OBJECT_COMMAND, "Command"} + , + {OBJECT_DEVICE, "Device"} + , + {OBJECT_EVENT_ENROLLMENT, "Event Enrollment"} + , + {OBJECT_FILE, "File"} + , + {OBJECT_GROUP, "Group"} + , + {OBJECT_LOOP, "Loop"} + , + {OBJECT_MULTI_STATE_INPUT, "Multi-State Input"} + , + {OBJECT_MULTI_STATE_OUTPUT, "Multi-State Output"} + , + {OBJECT_NOTIFICATION_CLASS, "Notification Class"} + , + {OBJECT_PROGRAM, "Program"} + , + {OBJECT_SCHEDULE, "Schedule"} + , + {OBJECT_AVERAGING, "Averaging"} + , + {OBJECT_MULTI_STATE_VALUE, "Multi-State Value"} + , + {OBJECT_TRENDLOG, "Trendlog"} + , + {OBJECT_LIFE_SAFETY_POINT, "Life Safety Point"} + , + {OBJECT_LIFE_SAFETY_ZONE, "Life Safety Zone"} + , + {OBJECT_ACCUMULATOR, "Accumulator"} + , + {OBJECT_PULSE_CONVERTER, "Pulse-Converter"} + , + + {OBJECT_EVENT_LOG, "Event-Log"} + , + {OBJECT_GLOBAL_GROUP, "Global-Group"} + , + {OBJECT_TREND_LOG_MULTIPLE, "Trend-Log-Multiple"} + , + {OBJECT_LOAD_CONTROL, "Load-Control"} + , + {OBJECT_STRUCTURED_VIEW, "Structured-View"} + , + + {0, NULL} +/* Enumerated values 0-127 are reserved for definition by ASHRAE. + Enumerated values 128-1023 may be used by others subject to + the procedures and constraints described in Clause 23. */ +}; + +const char *bactext_object_type_name(int index) +{ + return indtext_by_index_split_default(bacnet_object_type_names, + index, 128, ASHRAE_Reserved_String, Vendor_Proprietary_String); +} + +INDTEXT_DATA bacnet_property_names[] = { +/* FIXME: use the enumerations from bacenum.h */ + {PROP_ACKED_TRANSITIONS, "acked-transitions"} + , + {PROP_ACK_REQUIRED, "ack-required"} + , + {PROP_ACTION, "action"} + , + {PROP_ACTION_TEXT, "action-text"} + , + {PROP_ACTIVE_TEXT, "active-text"} + , + {PROP_ACTIVE_VT_SESSIONS, "active-vt-sessions"} + , + {PROP_ALARM_VALUE, "alarm-value"} + , + {PROP_ALARM_VALUES, "alarm-values"} + , + {PROP_ALL, "all"} + , + {PROP_ALL_WRITES_SUCCESSFUL, "all-writes-successful"} + , + {PROP_APDU_SEGMENT_TIMEOUT, "apdu-segment-timeout"} + , + {PROP_APDU_TIMEOUT, "apdu-timeout"} + , + {PROP_APPLICATION_SOFTWARE_VERSION, "application-software-version"} + , + {PROP_ARCHIVE, "archive"} + , + {PROP_BIAS, "bias"} + , + {PROP_CHANGE_OF_STATE_COUNT, "change-of-state-count"} + , + {PROP_CHANGE_OF_STATE_TIME, "change-of-state-time"} + , + {PROP_NOTIFICATION_CLASS, "notification-class"} + , + {PROP_BLANK_1, "(deleted in 135-2001)"} + , + {PROP_CONTROLLED_VARIABLE_REFERENCE, "controlled-variable-reference"} + , + {PROP_CONTROLLED_VARIABLE_UNITS, "controlled-variable-units"} + , + {PROP_CONTROLLED_VARIABLE_VALUE, "controlled-variable-value"} + , + {PROP_COV_INCREMENT, "COV-increment"} + , + {PROP_DATE_LIST, "datelist"} + , + {PROP_DAYLIGHT_SAVINGS_STATUS, "daylight-savings-status"} + , + {PROP_DEADBAND, "deadband"} + , + {PROP_DERIVATIVE_CONSTANT, "derivative-constant"} + , + {PROP_DERIVATIVE_CONSTANT_UNITS, "derivative-constant-units"} + , + {PROP_DESCRIPTION, "description"} + , + {PROP_DESCRIPTION_OF_HALT, "description-of-halt"} + , + {PROP_DEVICE_ADDRESS_BINDING, "device-address-binding"} + , + {PROP_DEVICE_TYPE, "device-type"} + , + {PROP_EFFECTIVE_PERIOD, "effective-period"} + , + {PROP_ELAPSED_ACTIVE_TIME, "elapsed-active-time"} + , + {PROP_ERROR_LIMIT, "error-limit"} + , + {PROP_EVENT_ENABLE, "event-enable"} + , + {PROP_EVENT_STATE, "event-state"} + , + {PROP_EVENT_TYPE, "event-type"} + , + {PROP_EXCEPTION_SCHEDULE, "exception-schedule"} + , + {PROP_FAULT_VALUES, "fault-values"} + , + {PROP_FEEDBACK_VALUE, "feedback-value"} + , + {PROP_FILE_ACCESS_METHOD, "file-access-method"} + , + {PROP_FILE_SIZE, "file-size"} + , + {PROP_FILE_TYPE, "file-type"} + , + {PROP_FIRMWARE_REVISION, "firmware-version"} + , + {PROP_HIGH_LIMIT, "high-limit"} + , + {PROP_INACTIVE_TEXT, "inactive-text"} + , + {PROP_IN_PROCESS, "in-process"} + , + {PROP_INSTANCE_OF, "instance-of"} + , + {PROP_INTEGRAL_CONSTANT, "integral-constant"} + , + {PROP_INTEGRAL_CONSTANT_UNITS, "integral-constant-units"} + , + {PROP_ISSUE_CONFIRMED_NOTIFICATIONS, "issue-confirmednotifications"} + , + {PROP_LIMIT_ENABLE, "limit-enable"} + , + {PROP_LIST_OF_GROUP_MEMBERS, "list-of-group-members"} + , + {PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES, + "list-of-object-property-references"} + , + {PROP_LIST_OF_SESSION_KEYS, "list-of-session-keys"} + , + {PROP_LOCAL_DATE, "local-date"} + , + {PROP_LOCAL_TIME, "local-time"} + , + {PROP_LOCATION, "location"} + , + {PROP_LOW_LIMIT, "low-limit"} + , + {PROP_MANIPULATED_VARIABLE_REFERENCE, "manipulated-variable-reference"} + , + {PROP_MAXIMUM_OUTPUT, "maximum-output"} + , + {PROP_MAX_APDU_LENGTH_ACCEPTED, "max-apdu-length-accepted"} + , + {PROP_MAX_INFO_FRAMES, "max-info-frames"} + , + {PROP_MAX_MASTER, "max-master"} + , + {PROP_MAX_PRES_VALUE, "max-pres-value"} + , + {PROP_MINIMUM_OFF_TIME, "minimum-off-time"} + , + {PROP_MINIMUM_ON_TIME, "minimum-on-time"} + , + {PROP_MINIMUM_OUTPUT, "minimum-output"} + , + {PROP_MIN_PRES_VALUE, "min-pres-value"} + , + {PROP_MODEL_NAME, "model-name"} + , + {PROP_MODIFICATION_DATE, "modification-date"} + , + {PROP_NOTIFY_TYPE, "notify-type"} + , + {PROP_NUMBER_OF_APDU_RETRIES, "number-of-APDU-retries"} + , + {PROP_NUMBER_OF_STATES, "number-of-states"} + , + {PROP_OBJECT_IDENTIFIER, "object-identifier"} + , + {PROP_OBJECT_LIST, "object-list"} + , + {PROP_OBJECT_NAME, "object-name"} + , + {PROP_OBJECT_PROPERTY_REFERENCE, "object-property-reference"} + , + {PROP_OBJECT_TYPE, "object-type"} + , + {PROP_OPTIONAL, "optional"} + , + {PROP_OUT_OF_SERVICE, "out-of-service"} + , + {PROP_OUTPUT_UNITS, "output-units"} + , + {PROP_EVENT_PARAMETERS, "event-parameters"} + , + {PROP_POLARITY, "polarity"} + , + {PROP_PRESENT_VALUE, "present-value"} + , + {PROP_PRIORITY, "priority"} + , + {PROP_PRIORITY_ARRAY, "priority-array"} + , + {PROP_PRIORITY_FOR_WRITING, "priority-for-writing"} + , + {PROP_PROCESS_IDENTIFIER, "process-identifier"} + , + {PROP_PROGRAM_CHANGE, "program-change"} + , + {PROP_PROGRAM_LOCATION, "program-location"} + , + {PROP_PROGRAM_STATE, "program-state"} + , + {PROP_PROPORTIONAL_CONSTANT, "proportional-constant"} + , + {PROP_PROPORTIONAL_CONSTANT_UNITS, "proportional-constant-units"} + , + {PROP_PROTOCOL_CONFORMANCE_CLASS, "protocol-conformance-class"} + , + {PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, + "protocol-object-types-supported"} + , + {PROP_PROTOCOL_SERVICES_SUPPORTED, "protocol-services-supported"} + , + {PROP_PROTOCOL_VERSION, "protocol-version"} + , + {PROP_READ_ONLY, "read-only"} + , + {PROP_REASON_FOR_HALT, "reason-for-halt"} + , + {PROP_RECIPIENT, "recipient"} + , + {PROP_RECIPIENT_LIST, "recipient-list"} + , + {PROP_RELIABILITY, "reliability"} + , + {PROP_RELINQUISH_DEFAULT, "relinquish-default"} + , + {PROP_REQUIRED, "required"} + , + {PROP_RESOLUTION, "resolution"} + , + {PROP_SEGMENTATION_SUPPORTED, "segmentation-supported"} + , + {PROP_SETPOINT, "setpoint"} + , + {PROP_SETPOINT_REFERENCE, "setpoint-reference"} + , + {PROP_STATE_TEXT, "state-text"} + , + {PROP_STATUS_FLAGS, "status-flags"} + , + {PROP_SYSTEM_STATUS, "system-status"} + , + {PROP_TIME_DELAY, "time-delay"} + , + {PROP_TIME_OF_ACTIVE_TIME_RESET, "time-of-active-time-reset"} + , + {PROP_TIME_OF_STATE_COUNT_RESET, "time-of-state-count-reset"} + , + {PROP_TIME_SYNCHRONIZATION_RECIPIENTS, + "time-synchronization-recipients"} + , + {PROP_UNITS, "units"} + , + {PROP_UPDATE_INTERVAL, "update-interval"} + , + {PROP_UTC_OFFSET, "utc-offset"} + , + {PROP_VENDOR_IDENTIFIER, "vendor-identifier"} + , + {PROP_VENDOR_NAME, "vendor-name"} + , + {PROP_VT_CLASSES_SUPPORTED, "vt-classes-supported"} + , + {PROP_WEEKLY_SCHEDULE, "weekly-schedule"} + , + {PROP_ATTEMPTED_SAMPLES, "attempted-samples"} + , + {PROP_AVERAGE_VALUE, "average-value"} + , + {PROP_BUFFER_SIZE, "buffer-size"} + , + {PROP_CLIENT_COV_INCREMENT, "client-cov-increment"} + , + {PROP_COV_RESUBSCRIPTION_INTERVAL, "cov-resubscription-interval"} + , + {PROP_CURRENT_NOTIFY_TIME, "current-notify-time"} + , + {PROP_EVENT_TIME_STAMPS, "event-time-stamps"} + , + {PROP_LOG_BUFFER, "log-buffer"} + , + {PROP_LOG_DEVICE_OBJECT, "log-device-object-property"} + , + {PROP_ENABLE, "enable"} + , + {PROP_LOG_INTERVAL, "log-interval"} + , + {PROP_MAXIMUM_VALUE, "maximum-value"} + , + {PROP_MINIMUM_VALUE, "minimum-value"} + , + {PROP_NOTIFICATION_THRESHOLD, "notification-threshold"} + , + {PROP_PREVIOUS_NOTIFY_TIME, "previous-notify-time"} + , + {PROP_PROTOCOL_REVISION, "protocol-revision"} + , + {PROP_RECORDS_SINCE_NOTIFICATION, "records-since-notification"} + , + {PROP_RECORD_COUNT, "record-count"} + , + {PROP_START_TIME, "start-time"} + , + {PROP_STOP_TIME, "stop-time"} + , + {PROP_STOP_WHEN_FULL, "stop-when-full"} + , + {PROP_TOTAL_RECORD_COUNT, "total-record-count"} + , + {PROP_VALID_SAMPLES, "valid-samples"} + , + {PROP_WINDOW_INTERVAL, "window-interval"} + , + {PROP_WINDOW_SAMPLES, "window-samples"} + , + {PROP_MAXIMUM_VALUE_TIMESTAMP, "maximum-value-timestamp"} + , + {PROP_MINIMUM_VALUE_TIMESTAMP, "minimum-value-timestamp"} + , + {PROP_VARIANCE_VALUE, "variance-value"} + , + {PROP_ACTIVE_COV_SUBSCRIPTIONS, "active-cov-subscriptions"} + , + {PROP_BACKUP_FAILURE_TIMEOUT, "backup-failure-timeout"} + , + {PROP_CONFIGURATION_FILES, "configuration-files"} + , + {PROP_DATABASE_REVISION, "database-revision"} + , + {PROP_DIRECT_READING, "direct-reading"} + , + {PROP_LAST_RESTORE_TIME, "last-restore-time"} + , + {PROP_MAINTENANCE_REQUIRED, "maintenance-required"} + , + {PROP_MEMBER_OF, "member-of"} + , + {PROP_MODE, "mode"} + , + {PROP_OPERATION_EXPECTED, "operation-expected"} + , + {PROP_SETTING, "setting"} + , + {PROP_SILENCED, "silenced"} + , + {PROP_TRACKING_VALUE, "tracking-value"} + , + {PROP_ZONE_MEMBERS, "zone-members"} + , + {PROP_LIFE_SAFETY_ALARM_VALUES, "life-safety-alarm-values"} + , + {PROP_MAX_SEGMENTS_ACCEPTED, "max-segments-accepted"} + , + {PROP_PROFILE_NAME, "profile-name"} + , + {PROP_AUTO_SLAVE_DISCOVERY, "auto-slave-discovery"} + , + {PROP_MANUAL_SLAVE_ADDRESS_BINDING, "manual-slave-address-binding"} + , + {PROP_SLAVE_ADDRESS_BINDING, "slave-address-binding"} + , + {PROP_SLAVE_PROXY_ENABLE, "slave-proxy-enable"} + , + {PROP_LAST_NOTIFY_TIME, "last-notify-time"} + , + {PROP_SCHEDULE_DEFAULT, "schedule-default"} + , + {PROP_ACCEPTED_MODES, "accepted-modes"} + , + {PROP_ADJUST_VALUE, "adjust-value"} + , + {PROP_COUNT, "count"} + , + {PROP_COUNT_BEFORE_CHANGE, "count-before-change"} + , + {PROP_COUNT_CHANGE_TIME, "count-change-time"} + , + {PROP_COV_PERIOD, "COV-period"} + , + {PROP_INPUT_REFERENCE, "input-reference"} + , + {PROP_LIMIT_MONITORING_INTERVAL, "limit-monitoring-interval"} + , + {PROP_LOGGING_DEVICE, "logging-device"} + , + {PROP_LOGGING_RECORD, "logging-record"} + , + {PROP_PRESCALE, "prescale"} + , + {PROP_PULSE_RATE, "pulse-rate"} + , + {PROP_SCALE, "scale"} + , + {PROP_SCALE_FACTOR, "scale-factor"} + , + {PROP_UPDATE_TIME, "update-time"} + , + {PROP_VALUE_BEFORE_CHANGE, "value-before-change"} + , + {PROP_VALUE_SET, "value-set"} + , + {PROP_VALUE_CHANGE_TIME, "value-change-time"} + , + {PROP_ALIGN_INTERVALS, "align-intervals"} + , + {PROP_GROUP_MEMBER_NAMES, "group-member-names"} + , + {PROP_INTERVAL_OFFSET, "interval-offset"} + , + {PROP_LAST_RESTART_REASON, "last-restart-reason"} + , + {PROP_LOGGING_TYPE, "logging-type"} + , + {PROP_MEMBER_STATUS_FLAGS, "member-status-flags"} + , + {PROP_NOTIFICATION_PERIOD, "notification-period"} + , + {PROP_PREVIOUS_NOTIFY_RECORD, "previous-notify-record"} + , + {PROP_REQUESTED_UPDATE_INTERVAL, "requested-update-interval"} + , + {PROP_RESTART_NOTIFICATION_RECIPIENTS, + "restart-notification-recipients"} + , + {PROP_TIME_OF_DEVICE_RESTART, "time-of-device-restart"} + , + {PROP_TIME_SYNCHRONIZATION_INTERVAL, "time-synchronization-interval"} + , + {PROP_TRIGGER, "trigger"} + , + {PROP_UTC_TIME_SYNCHRONIZATION_RECIPIENTS, + "UTC-time-synchronization-recipients"} + , + {PROP_NODE_SUBTYPE, "node-subtype"} + , + {PROP_NODE_TYPE, "node-type"} + , + {PROP_STRUCTURED_OBJECT_LIST, "structured-object-list"} + , + {PROP_SUBORDINATE_ANNOTATIONS, "subordinate-annotations"} + , + {PROP_SUBORDINATE_LIST, "subordinate-list"} + , + {PROP_ACTUAL_SHED_LEVEL, "actual-shed-level"} + , + {PROP_DUTY_WINDOW, "duty-window"} + , + {PROP_EXPECTED_SHED_LEVEL, "expected-shed-level"} + , + {PROP_FULL_DUTY_BASELINE, "full-duty-baseline"} + , + {PROP_REQUESTED_SHED_LEVEL, "requested-shed-level"} + , + {PROP_SHED_DURATION, "shed-duration"} + , + {PROP_SHED_LEVEL_DESCRIPTIONS, "shed-level-descriptions"} + , + {PROP_SHED_LEVELS, "shed-levels"} + , + {PROP_STATE_DESCRIPTION, "state-descriptions"} + , + + {0, NULL} + /* Enumerated values 0-511 are reserved for definition by ASHRAE. + Enumerated values 512-4194303 may be used by others subject to the + procedures and constraints described in Clause 23. */ +}; + +const char *bactext_property_name(int index) +{ + return indtext_by_index_split_default(bacnet_property_names, + index, 512, ASHRAE_Reserved_String, Vendor_Proprietary_String); +} + +unsigned bactext_property_id(const char *name) +{ + return indtext_by_istring_default(bacnet_property_names, name, 0); +} + +INDTEXT_DATA bacnet_engineering_unit_names[] = { + {UNITS_SQUARE_METERS, "square-meters"} + , + {UNITS_SQUARE_FEET, "square-feet"} + , + {UNITS_MILLIAMPERES, "milliamperes"} + , + {UNITS_AMPERES, "amperes"} + , + {UNITS_OHMS, "ohms"} + , + {UNITS_VOLTS, "volts"} + , + {UNITS_KILOVOLTS, "kilovolts"} + , + {UNITS_MEGAVOLTS, "megavolts"} + , + {UNITS_VOLT_AMPERES, "volt-amperes"} + , + {UNITS_KILOVOLT_AMPERES, "kilovolt-amperes"} + , + {UNITS_MEGAVOLT_AMPERES, "megavolt-amperes"} + , + {UNITS_VOLT_AMPERES_REACTIVE, "volt-amperes-reactive"} + , + {UNITS_KILOVOLT_AMPERES_REACTIVE, "kilovolt-amperes-reactive"} + , + {UNITS_MEGAVOLT_AMPERES_REACTIVE, "megavolt-amperes-reactive"} + , + {UNITS_DEGREES_PHASE, "degrees-phase"} + , + {UNITS_POWER_FACTOR, "power-factor"} + , + {UNITS_JOULES, "joules"} + , + {UNITS_KILOJOULES, "kilojoules"} + , + {UNITS_WATT_HOURS, "watt-hours"} + , + {UNITS_KILOWATT_HOURS, "kilowatt-hours"} + , + {UNITS_BTUS, "btus"} + , + {UNITS_THERMS, "therms"} + , + {UNITS_TON_HOURS, "ton-hours"} + , + {UNITS_JOULES_PER_KILOGRAM_DRY_AIR, "joules-per-kilogram-dry-air"} + , + {UNITS_BTUS_PER_POUND_DRY_AIR, "btus-per-pound-dry-air"} + , + {UNITS_CYCLES_PER_HOUR, "cycles-per-hour"} + , + {UNITS_CYCLES_PER_MINUTE, "cycles-per-minute"} + , + {UNITS_HERTZ, "hertz"} + , + {UNITS_GRAMS_OF_WATER_PER_KILOGRAM_DRY_AIR, + "grams-of-water-per-kilogram-dry-air"} + , + {UNITS_PERCENT_RELATIVE_HUMIDITY, "percent-relative-humidity"} + , + {UNITS_MILLIMETERS, "millimeters"} + , + {UNITS_METERS, "meters"} + , + {UNITS_INCHES, "inches"} + , + {UNITS_FEET, "feet"} + , + {UNITS_WATTS_PER_SQUARE_FOOT, "watts-per-square-foot"} + , + {UNITS_WATTS_PER_SQUARE_METER, "watts-per-square-meter"} + , + {UNITS_LUMENS, "lumens"} + , + {UNITS_LUXES, "luxes"} + , + {UNITS_FOOT_CANDLES, "foot-candles"} + , + {UNITS_KILOGRAMS, "kilograms"} + , + {UNITS_POUNDS_MASS, "pounds-mass"} + , + {UNITS_TONS, "tons"} + , + {UNITS_KILOGRAMS_PER_SECOND, "kilograms-per-second"} + , + {UNITS_KILOGRAMS_PER_MINUTE, "kilograms-per-minute"} + , + {UNITS_KILOGRAMS_PER_HOUR, "kilograms-per-hour"} + , + {UNITS_POUNDS_MASS_PER_MINUTE, "pounds-mass-per-minute"} + , + {UNITS_POUNDS_MASS_PER_HOUR, "pounds-mass-per-hour"} + , + {UNITS_WATTS, "watts"} + , + {UNITS_KILOWATTS, "kilowatts"} + , + {UNITS_MEGAWATTS, "megawatts"} + , + {UNITS_BTUS_PER_HOUR, "btus-per-hour"} + , + {UNITS_HORSEPOWER, "horsepower"} + , + {UNITS_TONS_REFRIGERATION, "tons-refrigeration"} + , + {UNITS_PASCALS, "pascals"} + , + {UNITS_KILOPASCALS, "kilopascals"} + , + {UNITS_BARS, "bars"} + , + {UNITS_POUNDS_FORCE_PER_SQUARE_INCH, "pounds-force-per-square-inch"} + , + {UNITS_CENTIMETERS_OF_WATER, "centimeters-of-water"} + , + {UNITS_INCHES_OF_WATER, "inches-of-water"} + , + {UNITS_MILLIMETERS_OF_MERCURY, "millimeters-of-mercury"} + , + {UNITS_CENTIMETERS_OF_MERCURY, "centimeters-of-mercury"} + , + {UNITS_INCHES_OF_MERCURY, "inches-of-mercury"} + , + {UNITS_DEGREES_CELSIUS, "degrees-celsius"} + , + {UNITS_DEGREES_KELVIN, "degrees-kelvin"} + , + {UNITS_DEGREES_FAHRENHEIT, "degrees-fahrenheit"} + , + {UNITS_DEGREE_DAYS_CELSIUS, "degree-days-celsius"} + , + {UNITS_DEGREE_DAYS_FAHRENHEIT, "degree-days-fahrenheit"} + , + {UNITS_YEARS, "years"} + , + {UNITS_MONTHS, "months"} + , + {UNITS_WEEKS, "weeks"} + , + {UNITS_DAYS, "days"} + , + {UNITS_HOURS, "hours"} + , + {UNITS_MINUTES, "minutes"} + , + {UNITS_SECONDS, "secondS"} + , + {UNITS_METERS_PER_SECOND, "meters-per-second"} + , + {UNITS_KILOMETERS_PER_HOUR, "kilometers-per-hour"} + , + {UNITS_FEET_PER_SECOND, "feet-per-second"} + , + {UNITS_FEET_PER_MINUTE, "feet-per-minute"} + , + {UNITS_MILES_PER_HOUR, "miles-per-hour"} + , + {UNITS_CUBIC_FEET, "cubic-feet"} + , + {UNITS_CUBIC_METERS, "cubic-meters"} + , + {UNITS_IMPERIAL_GALLONS, "imperial-gallons"} + , + {UNITS_LITERS, "liters"} + , + {UNITS_US_GALLONS, "us-gallons"} + , + {UNITS_CUBIC_FEET_PER_MINUTE, "cubic-feet-per-minute"} + , + {UNITS_CUBIC_METERS_PER_SECOND, "cubic-meters-per-second"} + , + {UNITS_IMPERIAL_GALLONS_PER_MINUTE, "imperial-gallons-per-minute"} + , + {UNITS_LITERS_PER_SECOND, "liters-per-second"} + , + {UNITS_LITERS_PER_MINUTE, "liters-per-minute"} + , + {UNITS_US_GALLONS_PER_MINUTE, "us-gallons-per-minute"} + , + {UNITS_DEGREES_ANGULAR, "degrees-angular"} + , + {UNITS_DEGREES_CELSIUS_PER_HOUR, "degrees-celsius-per-hour"} + , + {UNITS_DEGREES_CELSIUS_PER_MINUTE, "degrees-celsius-per-minute"} + , + {UNITS_DEGREES_FAHRENHEIT_PER_HOUR, "degrees-fahrenheit-per-hour"} + , + {UNITS_DEGREES_FAHRENHEIT_PER_MINUTE, "degrees-fahrenheit-per-minute"} + , + {UNITS_NO_UNITS, "no-units"} + , + {UNITS_PARTS_PER_MILLION, "parts-per-million"} + , + {UNITS_PARTS_PER_BILLION, "parts-per-billion"} + , + {UNITS_PERCENT, "percent"} + , + {UNITS_PERCENT_PER_SECOND, "percent-per-second"} + , + {UNITS_PER_MINUTE, "per-minute"} + , + {UNITS_PER_SECOND, "per-second"} + , + {UNITS_PSI_PER_DEGREE_FAHRENHEIT, "psi-per-degree-fahrenheit"} + , + {UNITS_RADIANS, "radians"} + , + {UNITS_REVOLUTIONS_PER_MINUTE, "revolutions-per-minute"} + , + {UNITS_CURRENCY1, "currency1"} + , + {UNITS_CURRENCY2, "currency2"} + , + {UNITS_CURRENCY3, "currency3"} + , + {UNITS_CURRENCY4, "currency4"} + , + {UNITS_CURRENCY5, "currency5"} + , + {UNITS_CURRENCY6, "currency6"} + , + {UNITS_CURRENCY7, "currency7"} + , + {UNITS_CURRENCY8, "currency8"} + , + {UNITS_CURRENCY9, "currency9"} + , + {UNITS_CURRENCY10, "currency10"} + , + {UNITS_SQUARE_INCHES, "square-inches"} + , + {UNITS_SQUARE_CENTIMETERS, "square-centimeters"} + , + {UNITS_BTUS_PER_POUND, "btus_per-pound"} + , + {UNITS_CENTIMETERS, "centimeters"} + , + {UNITS_POUNDS_MASS_PER_SECOND, "pounds-mass-per-second"} + , + {UNITS_DELTA_DEGREES_FAHRENHEIT, "delta-degrees-fahrenheit"} + , + {UNITS_DELTA_DEGREES_KELVIN, "delta-degrees-kelvin"} + , + {UNITS_KILOHMS, "kilohms"} + , + {UNITS_MEGOHMS, "megohms"} + , + {UNITS_MILLIVOLTS, "millivolts"} + , + {UNITS_KILOJOULES_PER_KILOGRAM, "kilojoules-per-kilogram"} + , + {UNITS_MEGAJOULES, "megajoules"} + , + {UNITS_JOULES_PER_DEGREE_KELVIN, "joules-per-degree-kelvin"} + , + {UNITS_JOULES_PER_KILOGRAM_DEGREE_KELVIN, + "joules-per-kilogram-degree-kelvin"} + , + {UNITS_KILOHERTZ, "kilohertz"} + , + {UNITS_MEGAHERTZ, "megahertz"} + , + {UNITS_PER_HOUR, "per-hour"} + , + {UNITS_MILLIWATTS, "milliwatts"} + , + {UNITS_HECTOPASCALS, "hectopascals"} + , + {UNITS_MILLIBARS, "millibars"} + , + {UNITS_CUBIC_METERS_PER_HOUR, "cubic-meters-per-hour"} + , + {UNITS_LITERS_PER_HOUR, "liters-per-hour"} + , + {UNITS_KILOWATT_HOURS_PER_SQUARE_METER, + "kilowatt-hours-per-square-meter"} + , + {UNITS_KILOWATT_HOURS_PER_SQUARE_FOOT, "kilowatt-hours-per-square-foot"} + , + {UNITS_MEGAJOULES_PER_SQUARE_METER, "megajoules-per-square-meter"} + , + {UNITS_MEGAJOULES_PER_SQUARE_FOOT, "megajoules-per-square-foot"} + , + {UNITS_CUBIC_FEET_PER_SECOND, "cubic-feet-per-second"} + , + {UNITS_WATTS_PER_SQUARE_METER_DEGREE_KELVIN, + "watts-per-square-meter-degree-kelvin"} + , + {UNITS_PERCENT_OBSCURATION_PER_FOOT, "percent-obscuration-per-foot"} + , + {UNITS_PERCENT_OBSCURATION_PER_METER, "percent-obscuration-per-meter"} + , + {UNITS_MILLIOHMS, "milliohms"} + , + {UNITS_MEGAWATT_HOURS, "megawatt-hours"} + , + {UNITS_KILO_BTUS, "kilo-btus"} + , + {UNITS_MEGA_BTUS, "mega-btus"} + , + {UNITS_KILOJOULES_PER_KILOGRAM_DRY_AIR, + "kilojoules-per-kilogram-dry-air"} + , + {UNITS_MEGAJOULES_PER_KILOGRAM_DRY_AIR, + "megajoules-per-kilogram-dry-air"} + , + {UNITS_KILOJOULES_PER_DEGREE_KELVIN, "kilojoules-per-degree-Kelvin"} + , + {UNITS_MEGAJOULES_PER_DEGREE_KELVIN, "megajoules-per-degree-Kelvin"} + , + {UNITS_NEWTON, "newton"} + , + {UNITS_GRAMS_PER_SECOND, "grams-per-second"} + , + {UNITS_GRAMS_PER_MINUTE, "grams-per-minute"} + , + {UNITS_TONS_PER_HOUR, "tons-per-hour"} + , + {UNITS_KILO_BTUS_PER_HOUR, "kilo-btus-per-hour"} + , + {UNITS_HUNDREDTHS_SECONDS, "hundredths-seconds"} + , + {UNITS_MILLISECONDS, "milliseconds"} + , + {UNITS_NEWTON_METERS, "newton-meters"} + , + {UNITS_MILLIMETERS_PER_SECOND, "millimeters-per-second"} + , + {UNITS_MILLIMETERS_PER_MINUTE, "millimeters-per-minute"} + , + {UNITS_METERS_PER_MINUTE, "meters-per-minute"} + , + {UNITS_METERS_PER_HOUR, "meters-per-hour"} + , + {UNITS_CUBIC_METERS_PER_MINUTE, "cubic-meters-per-minute"} + , + {UNITS_METERS_PER_SECOND_PER_SECOND, "meters-per-second-per-second"} + , + {UNITS_AMPERES_PER_METER, "amperes-per-meter"} + , + {UNITS_AMPERES_PER_SQUARE_METER, "amperes-per-square-meter"} + , + {UNITS_AMPERE_SQUARE_METERS, "ampere-square-meters"} + , + {UNITS_FARADS, "farads"} + , + {UNITS_HENRYS, "henrys"} + , + {UNITS_OHM_METERS, "ohm-meters"} + , + {UNITS_SIEMENS, "siemens"} + , + {UNITS_SIEMENS_PER_METER, "siemens-per-meter"} + , + {UNITS_TESLAS, "teslas"} + , + {UNITS_VOLTS_PER_DEGREE_KELVIN, "volts-per-degree-Kelvin"} + , + {UNITS_VOLTS_PER_METER, "volts-per-meter"} + , + {UNITS_WEBERS, "webers"} + , + {UNITS_CANDELAS, "candelas"} + , + {UNITS_CANDELAS_PER_SQUARE_METER, "candelas-per-square-meter"} + , + {UNITS_DEGREES_KELVIN_PER_HOUR, "degrees-Kelvin-per-hour"} + , + {UNITS_DEGREES_KELVIN_PER_MINUTE, "degrees-Kelvin-per-minute"} + , + {UNITS_JOULE_SECONDS, "joule-seconds"} + , + {UNITS_RADIANS_PER_SECOND, "radians-per-second"} + , + {UNITS_SQUARE_METERS_PER_NEWTON, "square-meters-per-Newton"} + , + {UNITS_KILOGRAMS_PER_CUBIC_METER, "kilograms-per-cubic-meter"} + , + {UNITS_NEWTON_SECONDS, "newton-seconds"} + , + {UNITS_NEWTONS_PER_METER, "newtons-per-meter"} + , + {UNITS_WATTS_PER_METER_PER_DEGREE_KELVIN, + "watts-per-meter-per-degree-Kelvin"} + , + {0, NULL} +/* Enumerated values 0-255 are reserved for definition by ASHRAE. + Enumerated values 256-65535 may be used by others subject to + the procedures and constraints described in Clause 23. */ +}; + +const char *bactext_engineering_unit_name(int index) +{ + return indtext_by_index_split_default(bacnet_engineering_unit_names, + index, 256, ASHRAE_Reserved_String, Vendor_Proprietary_String); +} + +INDTEXT_DATA bacnet_reject_reason_names[] = { + {REJECT_REASON_OTHER, "Other"} + , + {REJECT_REASON_BUFFER_OVERFLOW, "Buffer Overflow"} + , + {REJECT_REASON_INCONSISTENT_PARAMETERS, "Inconsistent Parameters"} + , + {REJECT_REASON_INVALID_PARAMETER_DATA_TYPE, + "Invalid Parameter Data Type"} + , + {REJECT_REASON_INVALID_TAG, "Invalid Tag"} + , + {REJECT_REASON_MISSING_REQUIRED_PARAMETER, "Missing Required Parameter"} + , + {REJECT_REASON_PARAMETER_OUT_OF_RANGE, "Parameter Out of Range"} + , + {REJECT_REASON_TOO_MANY_ARGUMENTS, "Too Many Arguments"} + , + {REJECT_REASON_UNDEFINED_ENUMERATION, "Undefined Enumeration"} + , + {REJECT_REASON_UNRECOGNIZED_SERVICE, "Unrecognized Service"} + , + {0, NULL} +}; + +const char *bactext_reject_reason_name(int index) +{ + return indtext_by_index_split_default(bacnet_reject_reason_names, + index, + FIRST_PROPRIETARY_REJECT_REASON, + ASHRAE_Reserved_String, Vendor_Proprietary_String); +} + +INDTEXT_DATA bacnet_abort_reason_names[] = { + {ABORT_REASON_OTHER, "Other"} + , + {ABORT_REASON_BUFFER_OVERFLOW, "Buffer Overflow"} + , + {ABORT_REASON_INVALID_APDU_IN_THIS_STATE, "Invalid APDU in this State"} + , + {ABORT_REASON_PREEMPTED_BY_HIGHER_PRIORITY_TASK, + "Preempted by Higher Priority Task"} + , + {ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, "Segmentation Not Supported"} + , + {0, NULL} +}; + +const char *bactext_abort_reason_name(int index) +{ + return indtext_by_index_split_default(bacnet_abort_reason_names, + index, + FIRST_PROPRIETARY_ABORT_REASON, + ASHRAE_Reserved_String, Vendor_Proprietary_String); +} + +INDTEXT_DATA bacnet_error_class_names[] = { + {ERROR_CLASS_DEVICE, "device"} + , + {ERROR_CLASS_OBJECT, "object"} + , + {ERROR_CLASS_PROPERTY, "property"} + , + {ERROR_CLASS_RESOURCES, "resources"} + , + {ERROR_CLASS_SECURITY, "security"} + , + {ERROR_CLASS_SERVICES, "services"} + , + {ERROR_CLASS_VT, "vt"} + , + {0, NULL} +}; + +const char *bactext_error_class_name(int index) +{ + return indtext_by_index_split_default(bacnet_error_class_names, + index, + FIRST_PROPRIETARY_ERROR_CLASS, + ASHRAE_Reserved_String, Vendor_Proprietary_String); +} + +INDTEXT_DATA bacnet_error_code_names[] = { + {ERROR_CODE_OTHER, "other"} + , + {ERROR_CODE_AUTHENTICATION_FAILED, "authentication-failed"} + , + {ERROR_CODE_CHARACTER_SET_NOT_SUPPORTED, "character-set-not-supported"} + , + {ERROR_CODE_CONFIGURATION_IN_PROGRESS, "configuration-in-progress"} + , + {ERROR_CODE_DATATYPE_NOT_SUPPORTED, "datatype-not-supported"} + , + {ERROR_CODE_DEVICE_BUSY, "device-busy"} + , + {ERROR_CODE_DUPLICATE_NAME, "duplicate-name"} + , + {ERROR_CODE_DUPLICATE_OBJECT_ID, "duplicate-object-id"} + , + {ERROR_CODE_DYNAMIC_CREATION_NOT_SUPPORTED, + "dynamic-creation-not-supported"} + , + {ERROR_CODE_FILE_ACCESS_DENIED, "file-access-denied"} + , + {ERROR_CODE_INCOMPATIBLE_SECURITY_LEVELS, + "incompatible-security-levels"} + , + {ERROR_CODE_INCONSISTENT_PARAMETERS, "inconsistent-parameters"} + , + {ERROR_CODE_INCONSISTENT_SELECTION_CRITERION, + "inconsistent-selection-criterion"} + , + {ERROR_CODE_INVALID_ARRAY_INDEX, "invalid-array-index"} + , + {ERROR_CODE_INVALID_CONFIGURATION_DATA, "invalid-configuration-data"} + , + {ERROR_CODE_INVALID_DATA_TYPE, "invalid-data-type"} + , + {ERROR_CODE_INVALID_FILE_ACCESS_METHOD, "invalid-file-access-method"} + , + {ERROR_CODE_ERROR_CODE_INVALID_FILE_START_POSITION, + "error-code-invalid-file-start-position"} + , + {ERROR_CODE_INVALID_OPERATOR_NAME, "invalid-operator-name"} + , + {ERROR_CODE_INVALID_PARAMETER_DATA_TYPE, "invalid-parameter-data-type"} + , + {ERROR_CODE_INVALID_TIME_STAMP, "invalid-time-stamp"} + , + {ERROR_CODE_KEY_GENERATION_ERROR, "key-generation-error"} + , + {ERROR_CODE_MISSING_REQUIRED_PARAMETER, "missing-required-parameter"} + , + {ERROR_CODE_NO_OBJECTS_OF_SPECIFIED_TYPE, + "no-objects-of-specified-type"} + , + {ERROR_CODE_NO_SPACE_FOR_OBJECT, "no-space-for-object"} + , + {ERROR_CODE_NO_SPACE_TO_ADD_LIST_ELEMENT, + "no-space-to-add-list-element"} + , + {ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY, "no-space-to-write-property"} + , + {ERROR_CODE_NO_VT_SESSIONS_AVAILABLE, "no-vt-sessions-available"} + , + {ERROR_CODE_OBJECT_DELETION_NOT_PERMITTED, + "object-deletion-not-permitted"} + , + {ERROR_CODE_OBJECT_IDENTIFIER_ALREADY_EXISTS, + "object-identifier-already-exists"} + , + {ERROR_CODE_OPERATIONAL_PROBLEM, "operational-problem"} + , + {ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED, + "optional-functionality-not-supported"} + , + {ERROR_CODE_PASSWORD_FAILURE, "password-failure"} + , + {ERROR_CODE_PROPERTY_IS_NOT_A_LIST, "property-is-not-a-list"} + , + {ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY, "property-is-not-an-array"} + , + {ERROR_CODE_READ_ACCESS_DENIED, "read-access-denied"} + , + {ERROR_CODE_SECURITY_NOT_SUPPORTED, "security-not-supported"} + , + {ERROR_CODE_SERVICE_REQUEST_DENIED, "service-request-denied"} + , + {ERROR_CODE_TIMEOUT, "timeout"} + , + {ERROR_CODE_UNKNOWN_OBJECT, "unknown-object"} + , + {ERROR_CODE_UNKNOWN_PROPERTY, "unknown-property"} + , + {ERROR_CODE_RESERVED1, "reserved1"} + , + {ERROR_CODE_UNKNOWN_VT_CLASS, "unknown-vt-class"} + , + {ERROR_CODE_UNKNOWN_VT_SESSION, "unknown-vt-session"} + , + {ERROR_CODE_UNSUPPORTED_OBJECT_TYPE, "unsupported-object-type"} + , + {ERROR_CODE_VALUE_OUT_OF_RANGE, "value-out-of-range"} + , + {ERROR_CODE_VT_SESSION_ALREADY_CLOSED, "vt-session-already-closed"} + , + {ERROR_CODE_VT_SESSION_TERMINATION_FAILURE, + "vt-session-termination-failure"} + , + {ERROR_CODE_WRITE_ACCESS_DENIED, "write-access-denied"} + , + {ERROR_CODE_COV_SUBSCRIPTION_FAILED, "cov-subscription-failed"} + , + {ERROR_CODE_NOT_COV_PROPERTY, "not-cov-property"} + , + {0, NULL} +}; + +const char *bactext_error_code_name(int index) +{ + return indtext_by_index_split_default(bacnet_error_code_names, + index, + FIRST_PROPRIETARY_ERROR_CLASS, + ASHRAE_Reserved_String, Vendor_Proprietary_String); +} + +INDTEXT_DATA bacnet_month_names[] = { + {1, "January"} + , + {2, "February"} + , + {3, "March"} + , + {4, "April"} + , + {5, "May"} + , + {6, "June"} + , + {7, "July"} + , + {8, "August"} + , + {9, "September"} + , + {10, "October"} + , + {11, "November"} + , + {12, "December"} + , + {255, "Any Month"} + , + {0, NULL} +}; + +const char *bactext_month_name(int index) +{ + return indtext_by_index_default(bacnet_month_names, + index, ASHRAE_Reserved_String); +} + +INDTEXT_DATA bacnet_week_of_month_names[] = { + {1, "days numbered 1-7"} + , + {2, "days numbered 8-14"} + , + {3, "days numbered 15-21"} + , + {4, "days numbered 22-28"} + , + {5, "days numbered 29-31"} + , + {6, "last 7 days of this month"} + , + {255, "any week of this month"} + , + {0, NULL} +}; + +const char *bactext_week_of_month_name(int index) +{ + return indtext_by_index_default(bacnet_week_of_month_names, + index, ASHRAE_Reserved_String); +} + +/* note: different than DaysOfWeek bit string where 0=monday */ +INDTEXT_DATA bacnet_day_of_week_names[] = { + {1, "Monday"} + , + {2, "Tuesday"} + , + {3, "Wednesday"} + , + {4, "Thursday"} + , + {5, "Friday"} + , + {6, "Saturday"} + , + {7, "Sunday"} + , + {255, "any day of week"} + , + {0, NULL} +}; + +const char *bactext_day_of_week_name(int index) +{ + return indtext_by_index_default(bacnet_day_of_week_names, + index, ASHRAE_Reserved_String); +} + +INDTEXT_DATA bacnet_event_state_names[] = { + {EVENT_STATE_NORMAL, "normal"} + , + {EVENT_STATE_FAULT, "fault"} + , + {EVENT_STATE_OFFNORMAL, "offnormal"} + , + {EVENT_STATE_HIGH_LIMIT, "high limit"} + , + {EVENT_STATE_LOW_LIMIT, "low limit"} + , + {0, NULL} +}; + +const char *bactext_event_state_name(int index) +{ + return indtext_by_index_default(bacnet_event_state_names, + index, ASHRAE_Reserved_String); +} + +INDTEXT_DATA bacnet_binary_present_value_names[] = { + {BINARY_INACTIVE, "inactive"} + , + {BINARY_ACTIVE, "active"} + , + {0, NULL} +}; + +const char *bactext_binary_present_value_name(int index) +{ + return indtext_by_index_default(bacnet_binary_present_value_names, + index, ASHRAE_Reserved_String); +} + +INDTEXT_DATA bacnet_reliability_names[] = { + {RELIABILITY_NO_FAULT_DETECTED, "no-fault-detected"} + , + {RELIABILITY_NO_SENSOR, "no-sensor"} + , + {RELIABILITY_OVER_RANGE, "over-range"} + , + {RELIABILITY_UNDER_RANGE, "under-range"} + , + {RELIABILITY_OPEN_LOOP, "open-loop"} + , + {RELIABILITY_SHORTED_LOOP, "shorted-loop"} + , + {RELIABILITY_NO_OUTPUT, "no-output"} + , + {RELIABILITY_UNRELIABLE_OTHER, "unreliable-other"} + , + {RELIABILITY_PROCESS_ERROR, "process-error"} + , + {RELIABILITY_MULTI_STATE_FAULT, "mult-state-fault"} + , + {RELIABILITY_CONFIGURATION_ERROR, "configuration-error"} + , + {0, NULL} +}; + +const char *bactext_reliability_name(int index) +{ + return indtext_by_index_default(bacnet_reliability_names, + index, ASHRAE_Reserved_String); +} + +INDTEXT_DATA bacnet_device_status_names[] = { + {STATUS_OPERATIONAL, "operational"} + , + {STATUS_OPERATIONAL_READ_ONLY, "operational-read-only"} + , + {STATUS_DOWNLOAD_REQUIRED, "download-required"} + , + {STATUS_DOWNLOAD_IN_PROGRESS, "download-in-progress"} + , + {STATUS_NON_OPERATIONAL, "non-operational"} + , + {0, NULL} +}; + +const char *bactext_device_status_name(int index) +{ + return indtext_by_index_default(bacnet_device_status_names, + index, ASHRAE_Reserved_String); +} + +INDTEXT_DATA bacnet_segmentation_names[] = { + {SEGMENTATION_BOTH, "segmented-both"} + , + {SEGMENTATION_TRANSMIT, "segmented-transmit"} + , + {SEGMENTATION_RECEIVE, "segmented-receive"} + , + {SEGMENTATION_NONE, "no-segmentation"} + , + {0, NULL} +}; + +const char *bactext_segmentation_name(int index) +{ + return indtext_by_index_default(bacnet_segmentation_names, + index, ASHRAE_Reserved_String); +} diff --git a/bacnet-stack/bactext.h b/bacnet-stack/bactext.h index e69de29b..2e961c7e 100644 --- a/bacnet-stack/bactext.h +++ b/bacnet-stack/bactext.h @@ -0,0 +1,81 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef BACTEXT_H +#define BACTEXT_H + +/* tiny implementations have no need to print */ +#if PRINT_ENABLED +#define BACTEXT_PRINT_ENABLED +#else +#ifdef TEST +#define BACTEXT_PRINT_ENABLED +#endif +#endif + +#ifdef BACTEXT_PRINT_ENABLED + +#include +#include +#include "indtext.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + const char *bactext_confirmed_service_name(int index); + const char *bactext_unconfirmed_service_name(int index); + const char *bactext_application_tag_name(int index); + const char *bactext_object_type_name(int index); + const char *bactext_property_name(int index); + const char *bactext_engineering_unit_name(int index); + const char *bactext_reject_reason_name(int index); + const char *bactext_abort_reason_name(int index); + const char *bactext_error_class_name(int index); + const char *bactext_error_code_name(int index); + unsigned bactext_property_id(const char *name); + const char *bactext_month_name(int index); + const char *bactext_week_of_month_name(int index); + const char *bactext_day_of_week_name(int index); + const char *bactext_event_state_name(int index); + const char *bactext_binary_present_value_name(int index); + const char *bactext_reliability_name(int index); + const char *bactext_device_status_name(int index); + const char *bactext_segmentation_name(int index); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* BACTEXT_PRINT_ENABLED */ +#endif diff --git a/bacnet-stack/bigend.c b/bacnet-stack/bigend.c index e69de29b..222327b9 100644 --- a/bacnet-stack/bigend.c +++ b/bacnet-stack/bigend.c @@ -0,0 +1,28 @@ +/* Big-Endian systems save the most significant byte first. */ +/* Sun and Motorola processors, IBM-370s and PDP-10s are big-endian. */ +/* "Network Byte Order" is also know as "Big-Endian Byte Order" */ +/* for example, a 4 byte integer 67305985 is 0x04030201 in hexidecimal. */ +/* x[0] = 0x04 */ +/* x[1] = 0x03 */ +/* x[2] = 0x02 */ +/* x[3] = 0x01 */ + +/* Little-Endian systems save the least significant byte first. */ +/* The entire Intel x86 family, Vaxes, Alphas and PDP-11s are little-endian. */ +/* for example, a 4 byte integer 67305985 is 0x04030201 in hexidecimal. */ +/* x[0] = 0x01 */ +/* x[1] = 0x02 */ +/* x[2] = 0x03 */ +/* x[3] = 0x04 */ + +int big_endian(void) +{ + union { + long l; + char c[sizeof(long)]; + } u; + + u.l = 1; + + return (u.c[sizeof(long) - 1] == 1); +} diff --git a/bacnet-stack/bigend.h b/bacnet-stack/bigend.h index e69de29b..c82b3a65 100644 --- a/bacnet-stack/bigend.h +++ b/bacnet-stack/bigend.h @@ -0,0 +1,29 @@ +#ifndef BIGEND_H +#define BIGEND_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Big-Endian systems save the most significant byte first. */ +/* Sun and Motorola processors, IBM-370s and PDP-10s are big-endian. */ +/* for example, a 4 byte integer 67305985 is 0x04030201 in hexidecimal. */ +/* x[0] = 0x04 */ +/* x[1] = 0x03 */ +/* x[2] = 0x02 */ +/* x[3] = 0x01 */ + +/* Little-Endian systems save the least significant byte first. */ +/* The entire Intel x86 family, Vaxes, Alphas and PDP-11s are little-endian. */ +/* for example, a 4 byte integer 67305985 is 0x04030201 in hexidecimal. */ +/* x[0] = 0x01 */ +/* x[1] = 0x02 */ +/* x[2] = 0x03 */ +/* x[3] = 0x04 */ + + int big_endian(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/bip.c b/bacnet-stack/bip.c index e69de29b..a0740691 100644 --- a/bacnet-stack/bip.c +++ b/bacnet-stack/bip.c @@ -0,0 +1,284 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#include /* for standard integer types uint8_t etc. */ +#include /* for the standard bool type. */ +#include "bacdcode.h" +#include "bip.h" +#include "net.h" /* custom per port */ + +static int BIP_Socket = -1; +/* port to use - stored in host byte order */ +static uint16_t BIP_Port = 0xBAC0; +/* IP Address - stored in host byte order */ +static struct in_addr BIP_Address; +/* Broadcast Address - stored in host byte order */ +static struct in_addr BIP_Broadcast_Address; + +void bip_set_socket(int sock_fd) +{ + BIP_Socket = sock_fd; +} + +int bip_socket(void) +{ + return BIP_Socket; +} + +bool bip_valid(void) +{ + return (BIP_Socket != -1); +} + +void bip_cleanup(void) +{ + if (bip_valid()) + close(BIP_Socket); + BIP_Socket = -1; + + return; +} + +/* set using network byte order */ +void bip_set_addr(uint32_t net_address) +{ + BIP_Address.s_addr = ntohl(net_address); +} + +/* returns host byte order */ +uint32_t bip_get_addr(void) +{ + return BIP_Address.s_addr; +} + +/* set using network byte order */ +void bip_set_broadcast_addr(uint32_t net_address) +{ + BIP_Broadcast_Address.s_addr = ntohl(net_address); +} + +/* returns host byte order */ +uint32_t bip_get_broadcast_addr(void) +{ + return BIP_Broadcast_Address.s_addr; +} + +/* set using host byte order */ +void bip_set_port(uint16_t port) +{ + BIP_Port = port; +} + +/* returns host byte order */ +uint16_t bip_get_port(void) +{ + return BIP_Port; +} + +/* function to send a packet out the BACnet/IP socket (Annex J) */ +/* returns number of bytes sent on success, negative number on failure */ +int bip_send_pdu(BACNET_ADDRESS * dest, /* destination address */ + BACNET_NPDU_DATA * npdu_data, /* network information */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len) +{ /* number of bytes of data */ + struct sockaddr_in bip_dest; + uint8_t mtu[MAX_MPDU] = { 0 }; + int mtu_len = 0; + int bytes_sent = 0; + + (void) npdu_data; + /* assumes that the driver has already been initialized */ + if (BIP_Socket < 0) + return BIP_Socket; + + mtu[0] = BVLL_TYPE_BACNET_IP; + bip_dest.sin_family = AF_INET; + if (dest->net == BACNET_BROADCAST_NETWORK) { + /* broadcast */ + bip_dest.sin_addr.s_addr = htonl(BIP_Broadcast_Address.s_addr); + bip_dest.sin_port = htons(BIP_Port); + memset(&(bip_dest.sin_zero), '\0', 8); + mtu[1] = BVLC_ORIGINAL_BROADCAST_NPDU; + } else if (dest->mac_len == 6) { + /* valid unicast */ + (void) decode_unsigned32(&dest->mac[0], + &(bip_dest.sin_addr.s_addr)); + (void) decode_unsigned16(&dest->mac[4], &(bip_dest.sin_port)); + memset(&(bip_dest.sin_zero), '\0', 8); + mtu[1] = BVLC_ORIGINAL_UNICAST_NPDU; + } else { + /* invalid address */ + return -1; + } + + mtu_len = 2; + mtu_len += + encode_unsigned16(&mtu[mtu_len], + (uint16_t) (pdu_len + 4 /*inclusive */ )); + memcpy(&mtu[mtu_len], pdu, pdu_len); + mtu_len += pdu_len; + + /* Send the packet */ + bytes_sent = sendto(BIP_Socket, (char *) mtu, mtu_len, 0, + (struct sockaddr *) &bip_dest, sizeof(struct sockaddr)); + + return bytes_sent; +} + +/* receives a BACnet/IP packet */ +/* returns the number of octets in the PDU, or zero on failure */ +uint16_t bip_receive(BACNET_ADDRESS * src, /* source address */ + uint8_t * pdu, /* PDU data */ + uint16_t max_pdu, /* amount of space available in the PDU */ + unsigned timeout) +{ /* number of milliseconds to wait for a packet */ + int received_bytes; + uint8_t buf[MAX_MPDU] = { 0 }; /* data */ + uint16_t pdu_len = 0; /* return value */ + fd_set read_fds; + int max; + struct timeval select_timeout; + struct sockaddr_in sin = { -1 }; + socklen_t sin_len = sizeof(sin); + + /* Make sure the socket is open */ + if (BIP_Socket < 0) + return 0; + + /* we could just use a non-blocking socket, but that consumes all + the CPU time. We can use a timeout; it is only supported as + a select. */ + if (timeout >= 1000) { + select_timeout.tv_sec = timeout / 1000; + select_timeout.tv_usec = + 1000 * (timeout - select_timeout.tv_sec * 1000); + } else { + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 1000 * timeout; + } + FD_ZERO(&read_fds); + FD_SET((unsigned int) BIP_Socket, &read_fds); + max = BIP_Socket; + /* see if there is a packet for us */ + if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) + received_bytes = recvfrom(BIP_Socket, + (char *) &buf[0], MAX_MPDU, 0, + (struct sockaddr *) &sin, &sin_len); + else + return 0; + + /* See if there is a problem */ + if (received_bytes < 0) { + return 0; + } + + /* no problem, just no bytes */ + if (received_bytes == 0) + return 0; + + /* the signature of a BACnet/IP packet */ + if (buf[0] != BVLL_TYPE_BACNET_IP) + return 0; + if ((buf[1] == BVLC_ORIGINAL_UNICAST_NPDU) || + (buf[1] == BVLC_ORIGINAL_BROADCAST_NPDU)) { + /* ignore messages from me */ + if (sin.sin_addr.s_addr == htonl(BIP_Address.s_addr)) + pdu_len = 0; + else { + /* copy the source address + FIXME: IPv6? */ + src->mac_len = 6; + (void) encode_unsigned32(&src->mac[0], sin.sin_addr.s_addr); + (void) encode_unsigned16(&src->mac[4], sin.sin_port); + /* FIXME: check destination address */ + /* see if it is broadcast or for us */ + /* decode the length of the PDU - length is inclusive of BVLC */ + (void) decode_unsigned16(&buf[2], &pdu_len); + /* copy the buffer into the PDU */ + pdu_len -= 4; /* BVLC header */ + if (pdu_len < max_pdu) + memmove(&pdu[0], &buf[4], pdu_len); + /* ignore packets that are too large */ + /* clients should check my max-apdu first */ + else + pdu_len = 0; + } + } +#ifdef BBMD_ENABLED + if (buf[1] < MAX_BVLC_FUNCTION) { + bbmd_handler(&buf[0], received_bytes, &sin); + } +#endif + + return pdu_len; +} + +void bip_get_my_address(BACNET_ADDRESS * my_address) +{ + int i = 0; + + my_address->mac_len = 6; + (void) encode_unsigned32(&my_address->mac[0], + htonl(BIP_Address.s_addr)); + (void) encode_unsigned16(&my_address->mac[4], htons(BIP_Port)); + my_address->net = 0; /* local only, no routing */ + my_address->len = 0; /* no SLEN */ + for (i = 0; i < MAX_MAC_LEN; i++) { + /* no SADR */ + my_address->adr[i] = 0; + } + + return; +} + +void bip_get_broadcast_address(BACNET_ADDRESS * dest) +{ /* destination address */ + int i = 0; /* counter */ + + if (dest) { + dest->mac_len = 6; + (void) encode_unsigned32(&dest->mac[0], + htonl(BIP_Broadcast_Address.s_addr)); + (void) encode_unsigned16(&dest->mac[4], htons(BIP_Port)); + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; /* no SLEN */ + for (i = 0; i < MAX_MAC_LEN; i++) { + /* no SADR */ + dest->adr[i] = 0; + } + } + + return; +} diff --git a/bacnet-stack/bip.h b/bacnet-stack/bip.h index e69de29b..a7fc6d3b 100644 --- a/bacnet-stack/bip.h +++ b/bacnet-stack/bip.h @@ -0,0 +1,99 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef BIP_H +#define BIP_H + +#include +#include +#include +#include "bacdef.h" +#include "npdu.h" +#include "net.h" + +/* specific defines for BACnet/IP over Ethernet */ +#define MAX_HEADER (1 + 1 + 2) +#define MAX_MPDU (MAX_HEADER+MAX_PDU) + +#define BVLL_TYPE_BACNET_IP (0x81) + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + /* note: define init and cleanup in your ports section */ + /* on Linux, ifname is eth0, ath0, arc0, and others. + on Windows, ifname is the dotted ip address of the interface */ + bool bip_init(char *ifname); + + /* normal functions... */ + void bip_cleanup(void); + void bip_set_socket(int sock_fd); + int bip_socket(void); + bool bip_valid(void); + void bip_get_broadcast_address(BACNET_ADDRESS * dest); /* destination address */ + void bip_get_my_address(BACNET_ADDRESS * my_address); + + /* function to send a packet out the BACnet/IP socket */ + /* returns zero on success, non-zero on failure */ + int bip_send_pdu(BACNET_ADDRESS * dest, /* destination address */ + BACNET_NPDU_DATA * npdu_data, /* network information */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len); /* number of bytes of data */ + + /* receives a BACnet/IP packet */ + /* returns the number of octets in the PDU, or zero on failure */ + uint16_t bip_receive(BACNET_ADDRESS * src, /* source address */ + uint8_t * pdu, /* PDU data */ + uint16_t max_pdu, /* amount of space available in the PDU */ + unsigned timeout); /* milliseconds to wait for a packet */ + + /* use host byte order for setting */ + void bip_set_port(uint16_t port); + /* returns host byte order */ + uint16_t bip_get_port(void); + + /* use network byte order for setting */ + void bip_set_addr(uint32_t net_address); + /* returns host byte order */ + uint32_t bip_get_addr(void); + + /* use network byte order for setting */ + void bip_set_broadcast_addr(uint32_t net_address); + /* returns host byte order */ + uint32_t bip_get_broadcast_addr(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/bits.h b/bacnet-stack/bits.h index e69de29b..55671e17 100644 --- a/bacnet-stack/bits.h +++ b/bacnet-stack/bits.h @@ -0,0 +1,73 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef BITS_H +#define BITS_H + +/******************************************************************** +* Bit Masks +*********************************************************************/ +#define BIT0 (0x01) +#define BIT1 (0x02) +#define BIT2 (0x04) +#define BIT3 (0x08) +#define BIT4 (0x10) +#define BIT5 (0x20) +#define BIT6 (0x40) +#define BIT7 (0x80) +#define BIT8 (0x0100) +#define BIT9 (0x0200) +#define BIT10 (0x0400) +#define BIT11 (0x0800) +#define BIT12 (0x1000) +#define BIT13 (0x2000) +#define BIT14 (0x4000) +#define BIT15 (0x8000) +#define BIT16 (0x010000UL) +#define BIT17 (0x020000UL) +#define BIT18 (0x040000UL) +#define BIT19 (0x080000UL) +#define BIT20 (0x100000UL) +#define BIT21 (0x200000UL) +#define BIT22 (0x400000UL) +#define BIT23 (0x800000UL) +#define BIT24 (0x01000000UL) +#define BIT25 (0x02000000UL) +#define BIT26 (0x04000000UL) +#define BIT27 (0x08000000UL) +#define BIT28 (0x10000000UL) +#define BIT29 (0x20000000UL) +#define BIT30 (0x40000000UL) +#define BIT31 (0x80000000UL) + +#endif diff --git a/bacnet-stack/bvlc.c b/bacnet-stack/bvlc.c index e69de29b..4e89cad9 100644 --- a/bacnet-stack/bvlc.c +++ b/bacnet-stack/bvlc.c @@ -0,0 +1,731 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#include /* for standard integer types uint8_t etc. */ +#include /* for the standard bool type. */ +#include /* for the standard bool type. */ +#include "bacdcode.h" +#include "bip.h" +#include "net.h" /* custom per port */ + +/* Handle the BACnet Virtual Link Control (BVLC), which includes: + BACnet Broadcast Management Device, + Broadcast Distribution Table, and + Foreign Device Registration */ + +typedef struct +{ + /* true if valid entry - false if not */ + bool valid; + /* BACnet/IP address */ + struct in_addr dest_address; + /* BACnet/IP port number - not always 47808=BAC0h */ + uint16_t dest_port; + /* Broadcast Distribution Mask - stored in host byte order */ + struct in_addr broadcast_mask; +} BBMD_TABLE_ENTRY; + +#define MAX_BBMD_ENTRIES 128 +static BBMD_TABLE_ENTRY BBMD_Table[MAX_BBMD_ENTRIES]; + +/*Each device that registers as a foreign device shall be placed +in an entry in the BBMD's Foreign Device Table (FDT). Each +entry shall consist of the 6-octet B/IP address of the registrant; +the 2-octet Time-to-Live value supplied at the time of +registration; and a 2-octet value representing the number of +seconds remaining before the BBMD will purge the registrant's FDT +entry if no re-registration occurs. This value will be initialized +to the 2-octet Time-to-Live value supplied at the time of +registration.*/ +typedef struct +{ + bool valid; + /* BACnet/IP address */ + struct in_addr dest_address; + /* BACnet/IP port number - not always 47808=BAC0h */ + uint16_t dest_port; + /* seconds for valid entry lifetime */ + uint16_t time_to_live; + /* our counter */ + time_t seconds_remaining; /* includes 30 second grace period */ +} FD_TABLE_ENTRY; + +#define MAX_FD_ENTRIES 128 +static FD_TABLE_ENTRY FD_Table[MAX_FD_ENTRIES]; + +void bvlc_maintenance_timer(unsigned seconds) +{ + unsigned i = 0; + + for (i = 0; i < MAX_FD_ENTRIES; i++) { + if (FD_Table[i].valid) { + if (FD_Table[i].seconds_remaining) { + if (FD_Table[i].seconds_remaining < seconds) { + FD_Table[i].seconds_remaining = 0; + } else { + FD_Table[i].seconds_remaining -= seconds; + } + if (FD_Table[i].seconds_remaining == 0) { + FD_Table[i].valid = false; + } + } + } + } +} + +int bvlc_encode_bip_address( + uint8_t * pdu, /* buffer to store encoding */ + struct in_addr *address, /* in host format */ + uint16_t port) +{ + int len = 0; + + if (pdu) { + len = encode_unsigned32(&pdu[0], address->s_addr); + len += encode_unsigned16(&pdu[len], port); + } + + return len; +} + +int bvlc_decode_bip_address( + uint8_t * pdu, /* buffer to extract encoded address */ + struct in_addr * address, /* in host format */ + uint16_t * port) +{ + int len = 0; + + if (pdu) { + (void) decode_unsigned32(&pdu[0], &(address->s_addr)); + (void) decode_unsigned16(&pdu[4], port); + len = 6; + } + + return len; +} + +/* used for both read and write entries */ +int bvlc_encode_address_entry(uint8_t * pdu, + struct in_addr *address, + uint16_t port, + struct in_addr *mask) +{ + int len = 0; + + if (pdu) { + len = bvlc_encode_bip_address(pdu, address, port); + len += encode_unsigned32(&pdu[len], mask->s_addr); + } + + return len; +} + +int bvlc_encode_bvlc_result( + uint8_t * pdu, + BACNET_BVLC_RESULT result_code) +{ + if (pdu) { + pdu[0] = BVLL_TYPE_BACNET_IP; + pdu[1] = BVLC_RESULT; + /* The 2-octet BVLC Length field is the length, in octets, + of the entire BVLL message, including the two octets of the + length field itself, most significant octet first. */ + encode_unsigned16(&pdu[2], 6); + encode_unsigned16(&pdu[4], result_code); + } + + return 6; +} + +int bvlc_encode_write_bdt_init( + uint8_t * pdu, + unsigned entries) +{ + int len = 0; + + if (pdu) { + pdu[0] = BVLL_TYPE_BACNET_IP; + pdu[1] = BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE; + /* The 2-octet BVLC Length field is the length, in octets, + of the entire BVLL message, including the two octets of the + length field itself, most significant octet first. */ + encode_unsigned16(&pdu[2], 4 + entries * 10); + len = 4; + } + + return len; +} + +int bvlc_encode_read_bdt( + uint8_t * pdu) +{ + int len = 0; + + if (pdu) { + pdu[0] = BVLL_TYPE_BACNET_IP; + pdu[1] = BVLC_READ_BROADCAST_DISTRIBUTION_TABLE; + /* The 2-octet BVLC Length field is the length, in octets, + of the entire BVLL message, including the two octets of the + length field itself, most significant octet first. */ + encode_unsigned16(&pdu[2], 4); + len = 4; + } + + return len; +} + +int bvlc_encode_read_bdt_ack_init( + uint8_t * pdu, + unsigned entries) +{ + int len = 0; + + if (pdu) { + pdu[0] = BVLL_TYPE_BACNET_IP; + pdu[1] = BVLC_READ_BROADCAST_DISTRIBUTION_TABLE_ACK; + /* The 2-octet BVLC Length field is the length, in octets, + of the entire BVLL message, including the two octets of the + length field itself, most significant octet first. */ + encode_unsigned16(&pdu[2], 4 + entries * 10); + len = 4; + } + + return len; +} + +int bvlc_encode_forwarded_npdu(uint8_t * pdu, + BACNET_ADDRESS * src, + uint8_t * npdu, + unsigned npdu_length) +{ + int len = 0; + + unsigned i; /* for loop counter */ + + if (pdu) { + pdu[0] = BVLL_TYPE_BACNET_IP; + pdu[1] = BVLC_FORWARDED_NPDU; + /* The 2-octet BVLC Length field is the length, in octets, + of the entire BVLL message, including the two octets of the + length field itself, most significant octet first. */ + encode_unsigned16(&pdu[2], 4 + 6 + npdu_length); + len = 4; + for (i = 0; i < 6; i++) { + pdu[len] = src->adr[i]; + len++; + } + for (i = 0; i < npdu_length; i++) { + pdu[len] = npdu[i]; + len++; + } + } + + return len; +} + + +int bvlc_encode_register_foreign_device(uint8_t * pdu, + uint16_t time_to_live_seconds) +{ + int len = 0; + + if (pdu) { + pdu[0] = BVLL_TYPE_BACNET_IP; + pdu[1] = BVLC_REGISTER_FOREIGN_DEVICE; + /* The 2-octet BVLC Length field is the length, in octets, + of the entire BVLL message, including the two octets of the + length field itself, most significant octet first. */ + encode_unsigned16(&pdu[2], 6); + encode_unsigned16(&pdu[2], time_to_live_seconds); + len = 6; + } + + return len; +} + +int bvlc_encode_read_fdt( + uint8_t * pdu) +{ + int len = 0; + + if (pdu) { + pdu[0] = BVLL_TYPE_BACNET_IP; + pdu[1] = BVLC_READ_FOREIGN_DEVICE_TABLE; + /* The 2-octet BVLC Length field is the length, in octets, + of the entire BVLL message, including the two octets of the + length field itself, most significant octet first. */ + encode_unsigned16(&pdu[2], 4); + len = 4; + } + + return len; +} + +int bvlc_encode_read_fdt_ack_init( + uint8_t * pdu, + unsigned entries) +{ + int len = 0; + + if (pdu) { + pdu[0] = BVLL_TYPE_BACNET_IP; + pdu[1] = BVLC_READ_FOREIGN_DEVICE_TABLE_ACK; + /* The 2-octet BVLC Length field is the length, in octets, + of the entire BVLL message, including the two octets of the + length field itself, most significant octet first. */ + encode_unsigned16(&pdu[2], 4 + entries * 10); + len = 4; + } + + return len; +} + +int bvlc_encode_delete_fdt_entry(uint8_t * pdu, + struct in_addr *address, + uint16_t port) +{ + int len = 0; + + if (pdu) { + pdu[0] = BVLL_TYPE_BACNET_IP; + pdu[1] = BVLC_READ_FOREIGN_DEVICE_TABLE; + /* The 2-octet BVLC Length field is the length, in octets, + of the entire BVLL message, including the two octets of the + length field itself, most significant octet first. */ + encode_unsigned16(&pdu[2], 10); + /* FDT Entry */ + encode_unsigned32(&pdu[0], address->s_addr); + encode_unsigned16(&pdu[4], port); + len = 10; + } + + return len; +} + +int bvlc_encode_distribute_broadcast_to_network(uint8_t * pdu, + uint8_t * npdu, + unsigned npdu_length) +{ + int len = 0; /* return value */ + unsigned i; /* for loop counter */ + + if (pdu) { + pdu[0] = BVLL_TYPE_BACNET_IP; + pdu[1] = BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK; + /* The 2-octet BVLC Length field is the length, in octets, + of the entire BVLL message, including the two octets of the + length field itself, most significant octet first. */ + len = encode_unsigned16(&pdu[2], 4 + npdu_length) + 2; + for (i = 0; i < npdu_length; i++) { + pdu[len] = npdu[i]; + len++; + } + } + + return len; +} + +int bvlc_encode_original_unicast_npdu(uint8_t * pdu, + uint8_t * npdu, + unsigned npdu_length) +{ + int len = 0; /* return value */ + unsigned i = 0; /* loop counter */ + + if (pdu) { + pdu[0] = BVLL_TYPE_BACNET_IP; + pdu[1] = BVLC_ORIGINAL_UNICAST_NPDU; + /* The 2-octet BVLC Length field is the length, in octets, + of the entire BVLL message, including the two octets of the + length field itself, most significant octet first. */ + len = encode_unsigned16(&pdu[2], 4 + npdu_length) + 2; + for (i = 0; i < npdu_length; i++) { + pdu[len] = npdu[i]; + len++; + } + } + + return len; +} + + +int bvlc_encode_original_broadcast_npdu(uint8_t * pdu, + uint8_t * npdu, + unsigned npdu_length) +{ + int len = 0; /* return value */ + unsigned i = 0; /* loop counter */ + + if (pdu) { + pdu[0] = BVLL_TYPE_BACNET_IP; + pdu[1] = BVLC_ORIGINAL_BROADCAST_NPDU; + /* The 2-octet BVLC Length field is the length, in octets, + of the entire BVLL message, including the two octets of the + length field itself, most significant octet first. */ + len = encode_unsigned16(&pdu[2], 4 + npdu_length) + 2; + for (i = 0; i < npdu_length; i++) { + pdu[len] = npdu[i]; + len++; + } + } + + return len; +} + +/* copy the source internet address to the BACnet address */ +/* FIXME: IPv6? */ +void bvlc_internet_to_bacnet_address( + BACNET_ADDRESS * src, /* returns the BACnet source address */ + struct sockaddr_in *sin) +{ /* source internet address */ + int len = 0; + uint32_t address; + uint16_t port; + + if (src && sin) { + address = ntohl(sin->sin_addr.s_addr); + len = encode_unsigned32(&src->mac[0], address); + port = ntohs(sin->sin_port); + len += encode_unsigned16(&src->mac[4], port); + src->mac_len = len; + src->net = 0; + src->len = 0; + } + + return; +} + +/* copy the source internet address to the BACnet address */ +/* FIXME: IPv6? */ +/* FIXME: is sockaddr_in host or network order? */ +void bvlc_bacnet_to_internet_address( + struct sockaddr_in *sin, /* source internet address */ + BACNET_ADDRESS * src) /* returns the BACnet source address */ +{ + int len = 0; + uint32_t address; + uint16_t port; + + if (src && sin) { + if (src->mac_len == 6) { + len = decode_unsigned32(&src->mac[0], &address); + len += decode_unsigned16(&src->mac[4], &port ); + sin->sin_addr.s_addr = htonl(address); + sin->sin_port = htons(port); + } + } + + return; +} + +void bvlc_bdt_forward_npdu( + struct sockaddr_in *sin, /* the source address */ + uint8_t * npdu, /* the NPDU */ + uint16_t npdu_length) +{ /* length of the NPDU */ + uint8_t mtu[MAX_MPDU] = {0}; + int mtu_len = 0; + int bytes_sent = 0; + unsigned i = 0; /* loop counter */ + struct sockaddr_in bip_dest; + BACNET_ADDRESS src; + + /* assumes that the driver has already been initialized */ + if (bip_socket() < 0) { + return; + } + bvlc_internet_to_bacnet_address(&src, sin); + mtu_len = bvlc_encode_forwarded_npdu( + &mtu[0], + &src, + npdu, + npdu_length); + /* load destination IP address */ + bip_dest.sin_family = AF_INET; + /* loop through the BDT and send one to each entry, except us */ + for (i = 0; i < MAX_BBMD_ENTRIES; i++) { + if (BBMD_Table[i].valid) { + /* The B/IP address to which the Forwarded-NPDU message is + sent is formed by inverting the broadcast distribution + mask in the BDT entry and logically ORing it with the + BBMD address of the same entry. */ + bip_dest.sin_addr.s_addr = + htonl(((~BBMD_Table[i].broadcast_mask.s_addr) | + BBMD_Table[i].dest_address.s_addr)); + bip_dest.sin_port = htons(BBMD_Table[i].dest_port); + /* Send the packet */ + bytes_sent = + sendto(bip_socket(), (char *) mtu, mtu_len, 0, + (struct sockaddr *) &bip_dest, + sizeof(struct sockaddr)); + } + } + + return; +} + +void bvlc_fdt_forward_npdu( + struct sockaddr_in *sin, /* the source address */ + uint8_t * npdu, /* returns the NPDU */ + uint16_t max_npdu) /* amount of space available in the NPDU */ +{ + + /* FIXME: add the code */ +} + +uint16_t bvlc_handler( + BACNET_ADDRESS * src, /* returns the source address */ + uint8_t * npdu, /* returns the NPDU */ + uint16_t max_npdu, /* amount of space available in the NPDU */ + unsigned timeout) /* number of milliseconds to wait for a packet */ +{ + uint8_t buf[MAX_MPDU] = {0}; /* data */ + uint16_t pdu_len = 0; /* return value */ + fd_set read_fds; + int max; + struct timeval select_timeout; + struct sockaddr_in sin = { -1 }; + socklen_t sin_len = sizeof(sin); + int function_type = 0; + int received_bytes; + + /* Make sure the socket is open */ + if (BIP_Socket < 0) { + return 0; + } + + /* we could just use a non-blocking socket, but that consumes all + the CPU time. We can use a timeout; it is only supported as + a select. */ + if (timeout >= 1000) { + select_timeout.tv_sec = timeout / 1000; + select_timeout.tv_usec = + 1000 * (timeout - select_timeout.tv_sec * 1000); + } else { + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 1000 * timeout; + } + FD_ZERO(&read_fds); + FD_SET((unsigned int) BIP_Socket, &read_fds); + max = BIP_Socket; + /* see if there is a packet for us */ + if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) { + received_bytes = recvfrom( + BIP_Socket, + (char *) &buf[0], + MAX_MPDU, 0, + (struct sockaddr *) &sin, &sin_len); + } else { + return 0; + } + /* See if there is a problem */ + if (received_bytes < 0) { + return 0; + } + /* no problem, just no bytes */ + if (received_bytes == 0) { + return 0; + } + /* the signature of a BACnet/IP packet */ + if (buf[0] != BVLL_TYPE_BACNET_IP) { + return 0; + } + function_type = buf[1]; + /* decode the length of the PDU - length is inclusive of BVLC */ + (void) decode_unsigned16(&buf[2], &npdu_len); + /* subtract off the BVLC header */ + npdu_len -= 4; + switch (function_type) { + case BVLC_RESULT: + break; + case BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE: + /* Upon receipt of a BVLL Write-Broadcast-Distribution-Table + message, a BBMD shall attempt to create or replace its BDT, + depending on whether or not a BDT has previously existed. + If the creation or replacement of the BDT is successful, the BBMD + shall return a BVLC-Result message to the originating device with + a result code of X'0000'. Otherwise, the BBMD shall return a + BVLC-Result message to the originating device with a result code + of X'0010' indicating that the write attempt has failed. */ + break; + case BVLC_READ_BROADCAST_DISTRIBUTION_TABLE: + break; + case BVLC_READ_BROADCAST_DISTRIBUTION_TABLE_ACK: + break; + case BVLC_FORWARDED_NPDU: + /* Upon receipt of a BVLL Forwarded-NPDU message, a BBMD shall + process it according to whether it was received from a peer + BBMD as the result of a directed broadcast or a unicast + transmission. A BBMD may ascertain the method by which Forwarded- + NPDU messages will arrive by inspecting the broadcast distribution + mask field in its own BDT entry since all BDTs are required + to be identical. If the message arrived via directed broadcast, + it was also received by the other devices on the BBMD's subnet. In + this case the BBMD merely retransmits the message directly to each + foreign device currently in the BBMD's FDT. If the + message arrived via a unicast transmission it has not yet been + received by the other devices on the BBMD's subnet. In this case, + the message is sent to the devices on the BBMD's subnet using the + B/IP broadcast address as well as to each foreign device + currently in the BBMD's FDT. A BBMD on a subnet with no other + BACnet devices may omit the broadcast using the B/IP + broadcast address. The method by which a BBMD determines whether + or not other BACnet devices are present is a local matter. */ + bvlc_broadcast_npdu(&sin, &buf[4], npdu_len); + bvlc_fdt_forward_npdu(&sin, &buf[4], npdu_len); + break; + case BVLC_REGISTER_FOREIGN_DEVICE: + break; + case BVLC_READ_FOREIGN_DEVICE_TABLE: + break; + case BVLC_READ_FOREIGN_DEVICE_TABLE_ACK: + break; + case BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY: + break; + case BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK: + bvlc_broadcast_forward_npdu(&sin, &buf[4], npdu_len); + bvlc_fdt_forward_npdu(&sin, &buf[4], npdu_len); + break; + case BVLC_ORIGINAL_UNICAST_NPDU: + /* ignore messages from me */ + if (sin.sin_addr.s_addr == BIP_Address.s_addr) { + npdu_len = 0; + } else { + bvlc_internet_to_bacnet_address(src, &sin); + if (npdu_len < max_npdu) { + /* copy the buffer into the PDU */ + memmove(&npdu[0], &buf[4], npdu_len); + } else { + /* ignore packets that are too large */ + /* clients should check my max-apdu first */ + npdu_len = 0; + } + } + break; + case BVLC_ORIGINAL_BROADCAST_NPDU: + /* Upon receipt of a BVLL Original-Broadcast-NPDU message, + a BBMD shall construct a BVLL Forwarded-NPDU message and + send it to each IP subnet in its BDT with the exception + of its own. The B/IP address to which the Forwarded-NPDU + message is sent is formed by inverting the broadcast + distribution mask in the BDT entry and logically ORing it + with the BBMD address of the same entry. This process + produces either the directed broadcast address of the remote + subnet or the unicast address of the BBMD on that subnet + depending on the contents of the broadcast distribution + mask. See J.4.3.2.. In addition, the received BACnet NPDU + shall be sent directly to each foreign device currently in + the BBMD's FDT also using the BVLL Forwarded-NPDU message. */ + bvlc_internet_to_bacnet_address(src, &sin); + if (npdu_len < max_npdu) { + /* copy the buffer into the PDU */ + memmove(&npdu[0], &buf[4], npdu_len); + } else { + /* ignore packets that are too large */ + /* clients should check my max-apdu first */ + npdu_len = 0; + } + /* if BDT or FDT entries exist, Forward the NPDU */ + bvlc_bdt_forward_npdu(&sin, &buf[4], npdu_len); + bvlc_fdt_forward_npdu(&sin, &buf[4], npdu_len); + break; + default: + break; + } + + return npdu_len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testBIPAddress(Test * pTest) +{ + uint8_t apdu[50] = { 0 }; + uint32_t value = 0, test_value = 0; + int len = 0, test_len = 0; + struct in_addr address; + struct in_addr test_address; + uint16_t port = 0, test_port = 0; + + address.s_addr = 42; + len = bvlc_encode_bip_address(&apdu[0], + &address, port); + test_len = bvlc_decode_bip_address(&apdu[0], + &test_address, &test_port); + ct_test(pTest, len == test_len); + ct_test(pTest, address.s_addr == test_address.s_addr); + ct_test(pTest, port == test_port); +} + +void testInternetAddress(Test * pTest) +{ + BACNET_ADDRESS src; + BACNET_ADDRESS test_src; + struct sockaddr_in sin; + struct sockaddr_in test_sin; + + sin.sin_port = htons(0xBAC0); + sin.sin_addr.s_addr = inet_addr("192.168.0.1"); + bvlc_internet_to_bacnet_address(&src, &sin); + bvlc_bacnet_to_internet_address(&test_sin, &src); + ct_test(pTest, sin.sin_port == test_sin.sin_port); + ct_test(pTest, sin.sin_addr.s_addr == test_sin.sin_addr.s_addr); +} + +#ifdef TEST_BVLC +int main(void) +{ + Test * pTest; + bool rc; + + pTest = ct_create("BACnet Virtual Link Control", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testBIPAddress); + assert(rc); + rc = ct_addTestFunction(pTest, testInternetAddress); + assert(rc); + /* configure output */ + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} + +#endif /* TEST_BBMD */ +#endif /* TEST */ diff --git a/bacnet-stack/bvlc.h b/bacnet-stack/bvlc.h index e69de29b..3b2756e5 100644 --- a/bacnet-stack/bvlc.h +++ b/bacnet-stack/bvlc.h @@ -0,0 +1,52 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef BVLC_H +#define BVLC_H + +#include /* for standard integer types uint8_t etc. */ +#include /* for the standard bool type. */ +#include "bip.h" + +#ifdef __cplusplus +extern "C" { + +#endif /* __cplusplus */ + +/* called from BACnet/IP handler */ + void bvlc_handler(uint8_t * buf, int len, struct sockaddr_in *sin); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* */ diff --git a/bacnet-stack/bytes.h b/bacnet-stack/bytes.h index e69de29b..f1c360ab 100644 --- a/bacnet-stack/bytes.h +++ b/bacnet-stack/bytes.h @@ -0,0 +1,70 @@ +/* Defines the bit/byte/word/long conversions that are used in code */ + + +#ifndef BYTES_H +#define BYTES_H + +#include + +#ifndef LO_NIB +#define LO_NIB(b) ((b) & 0xF) +#endif + +#ifndef HI_NIB +#define HI_NIB(b) ((b) >> 4) + +#endif + + + +#ifndef LO_BYTE + +#define LO_BYTE(w) ((uint8_t)(w)) + +#endif + + + +#ifndef HI_BYTE + +#define HI_BYTE(w) ((uint8_t)((uint16_t)(w) >> 8)) + +#endif + + + +#ifndef LO_WORD + +#define LO_WORD(x) ((uint16_t)(x)) + +#endif + + + +#ifndef HI_WORD + +#define HI_WORD(x) ((uint16_t)((uint32_t)(x) >> 16)) + +#endif + + + +#ifndef MAKE_WORD + +#define MAKE_WORD(lo,hi) \ + ((uint16_t)(((uint8_t)(lo))|(((uint16_t)((uint8_t)(hi)))<<8))) + +#endif + + + +#ifndef MAKE_LONG + +#define MAKE_LONG(lo,hi) \ + ((uint32_t)(((uint16_t)(lo))|(((uint32_t)((uint16_t)(hi)))<<16))) + +#endif + + + +#endif /* end of header file */ diff --git a/bacnet-stack/config.h b/bacnet-stack/config.h index e69de29b..7e7dc172 100644 --- a/bacnet-stack/config.h +++ b/bacnet-stack/config.h @@ -0,0 +1,34 @@ +#ifndef CONFIG_H +#define CONFIG_H + +/* declare a single physical layer using your compiler define. + see datalink.h for possible defines. */ + +/* Max number of bytes in an APDU. */ +/* Typical sizes are 50, 128, 206, 480, 1024, and 1476 octets */ +/* This is used in constructing messages and to tell others our limits */ +/* 50 is the minimum; adjust to your memory and physical layer constraints */ +/* Lon=206, MS/TP=480, ARCNET=480, Ethernet=1476 */ +#ifndef MAX_APDU + #define MAX_APDU 50 + /* #define MAX_APDU 480 */ + /* #define MAX_APDU 1476 */ +#endif +/* for confirmed messages, this is the number of transactions */ +/* that we hold in a queue waiting for timeout. */ +/* Configure to zero if you don't want any confirmed messages */ +/* Configure from 1..255 for number of outstanding confirmed */ +/* requests available. */ +#ifndef MAX_TSM_TRANSACTIONS + #define MAX_TSM_TRANSACTIONS 255 +#endif +/* The address cache is used for binding to BACnet devices */ +/* The number of entries corresponds to the number of */ +/* devices that might respond to an I-Am on the network. */ +/* If your device is a simple server and does not need to bind, */ +/* then you don't need to use this. */ +#ifndef MAX_ADDRESS_CACHE + #define MAX_ADDRESS_CACHE 255 +#endif + +#endif diff --git a/bacnet-stack/cov.c b/bacnet-stack/cov.c index e69de29b..08fa8b50 100644 --- a/bacnet-stack/cov.c +++ b/bacnet-stack/cov.c @@ -0,0 +1,979 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacdcode.h" +#include "bacdef.h" +#include "bacapp.h" +#include "cov.h" +#include "device.h" +#include "datalink.h" +#include "npdu.h" + +/* encode service */ + +/* Change-Of-Value Services +COV Subscribe +COV Subscribe Property +COV Notification +Unconfirmed COV Notification +*/ +static int notify_encode_adpu(uint8_t * apdu, BACNET_COV_DATA * data) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + BACNET_PROPERTY_VALUE *value = NULL; /* value in list */ + + if (apdu) { + /* tag 0 - subscriberProcessIdentifier */ + len = encode_context_unsigned(&apdu[apdu_len], + 0, data->subscriberProcessIdentifier); + apdu_len += len; + /* tag 1 - initiatingDeviceIdentifier */ + len = encode_context_object_id(&apdu[apdu_len], + 1, OBJECT_DEVICE, data->initiatingDeviceIdentifier); + apdu_len += len; + /* tag 2 - monitoredObjectIdentifier */ + len = encode_context_object_id(&apdu[apdu_len], + 2, + data->monitoredObjectIdentifier.type, + data->monitoredObjectIdentifier.instance); + apdu_len += len; + /* tag 3 - timeRemaining */ + len = encode_context_unsigned(&apdu[apdu_len], + 3, data->timeRemaining); + apdu_len += len; + /* tag 4 - listOfValues */ + len = encode_opening_tag(&apdu[apdu_len], 4); + apdu_len += len; + /* the first value includes a pointer to the next value, etc */ + /* FIXME: for small implementations, we might try a partial + approach like the rpm.c where the values are encoded with + a separate function */ + value = &data->listOfValues; + while (value != NULL) { + /* tag 0 - propertyIdentifier */ + len = encode_context_enumerated(&apdu[apdu_len], + 0, value->propertyIdentifier); + apdu_len += len; + /* tag 1 - propertyArrayIndex OPTIONAL */ + if (value->propertyArrayIndex != BACNET_ARRAY_ALL) { + len = encode_context_unsigned(&apdu[apdu_len], + 1, value->propertyArrayIndex); + apdu_len += len; + } + /* tag 2 - value */ + /* abstract syntax gets enclosed in a context tag */ + len = encode_opening_tag(&apdu[apdu_len], 2); + apdu_len += len; + len = bacapp_encode_application_data(&apdu[apdu_len], + &value->value); + apdu_len += len; + len = encode_closing_tag(&apdu[apdu_len], 2); + apdu_len += len; + /* tag 3 - priority OPTIONAL */ + if (value->priority != BACNET_NO_PRIORITY) { + len = encode_context_unsigned(&apdu[apdu_len], 3, + value->priority); + apdu_len += len; + } + /* is there another one to encode? */ + /* FIXME: check to see if there is room in the APDU */ + value = value->next; + } + len = encode_closing_tag(&apdu[apdu_len], 4); + apdu_len += len; + } + + return apdu_len; +} + +int ccov_notify_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_COV_DATA * data) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + uint16_t max_apdu = Device_Max_APDU_Length_Accepted(); + + if (apdu) { + apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST; + apdu[1] = encode_max_segs_max_apdu(0, max_apdu); + apdu[2] = invoke_id; + apdu[3] = SERVICE_CONFIRMED_COV_NOTIFICATION; + apdu_len = 4; + len = notify_encode_adpu(&apdu[apdu_len], data); + apdu_len += len; + } + + return apdu_len; +} + +int ucov_notify_encode_apdu(uint8_t * apdu, BACNET_COV_DATA * data) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu && data) { + apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST; + apdu[1] = SERVICE_UNCONFIRMED_COV_NOTIFICATION; /* service choice */ + apdu_len = 2; + len = notify_encode_adpu(&apdu[apdu_len], data); + apdu_len += len; + } + + return apdu_len; +} + +/* decode the service request only */ +/* COV and Unconfirmed COV are the same */ +int cov_notify_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_COV_DATA * data) +{ + int len = 0; /* return value */ + uint8_t tag_number = 0; + uint32_t len_value = 0; + uint32_t decoded_value = 0; /* for decoding */ + int decoded_type = 0; /* for decoding */ + int property = 0; /* for decoding */ + BACNET_PROPERTY_VALUE *value = NULL; /* value in list */ + + if (apdu_len && data) { + /* tag 0 - subscriberProcessIdentifier */ + if (decode_is_context_tag(&apdu[len], 0)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += decode_unsigned(&apdu[len], len_value, &decoded_value); + data->subscriberProcessIdentifier = decoded_value; + } else + return -1; + /* tag 1 - initiatingDeviceIdentifier */ + if (decode_is_context_tag(&apdu[len], 1)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += + decode_object_id(&apdu[len], &decoded_type, + &data->initiatingDeviceIdentifier); + if (decoded_type != OBJECT_DEVICE) + return -1; + } else + return -1; + /* tag 2 - monitoredObjectIdentifier */ + if (decode_is_context_tag(&apdu[len], 2)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += + decode_object_id(&apdu[len], &decoded_type, + &data->monitoredObjectIdentifier.instance); + data->monitoredObjectIdentifier.type = decoded_type; + } else + return -1; + /* tag 3 - timeRemaining */ + if (decode_is_context_tag(&apdu[len], 3)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += decode_unsigned(&apdu[len], len_value, &decoded_value); + data->timeRemaining = decoded_value; + } else + return -1; + /* tag 4: opening context tag - listOfValues */ + if (!decode_is_opening_tag_number(&apdu[len], 4)) + return -1; + /* a tag number of 4 is not extended so only one octet */ + len++; + /* the first value includes a pointer to the next value, etc */ + value = &data->listOfValues; + while (value != NULL) { + /* tag 0 - propertyIdentifier */ + if (decode_is_context_tag(&apdu[len], 0)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += decode_enumerated(&apdu[len], len_value, &property); + value->propertyIdentifier = property; + } else + return -1; + /* tag 1 - propertyArrayIndex OPTIONAL */ + if (decode_is_context_tag(&apdu[len], 1)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += + decode_unsigned(&apdu[len], len_value, &decoded_value); + value->propertyArrayIndex = decoded_value; + } else + value->propertyArrayIndex = BACNET_ARRAY_ALL; + /* tag 2: opening context tag - value */ + if (!decode_is_opening_tag_number(&apdu[len], 2)) + return -1; + /* a tag number of 2 is not extended so only one octet */ + len++; + len += bacapp_decode_application_data(&apdu[len], + apdu_len - len, &value->value); + /* FIXME: check the return value; abort if no valid data? */ + /* FIXME: there might be more than one data element in here! */ + if (!decode_is_closing_tag_number(&apdu[len], 2)) + return -1; + /* a tag number of 2 is not extended so only one octet */ + len++; + /* tag 3 - priority OPTIONAL */ + if (decode_is_context_tag(&apdu[len], 3)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += + decode_unsigned(&apdu[len], len_value, &decoded_value); + value->priority = decoded_value; + } else + value->priority = BACNET_NO_PRIORITY; + /* end of list? */ + if (decode_is_closing_tag_number(&apdu[len], 4)) + break; + /* is there another one to decode? */ + value = value->next; + /* out of room to store more values */ + if (value == NULL) + return -1; + } + } + + return len; +} + +/* +12.11.38Active_COV_Subscriptions +The Active_COV_Subscriptions property is a List of BACnetCOVSubscription, each of which consists of a Recipient, a +Monitored Property Reference, an Issue Confirmed Notifications flag, a Time Remaining value and an optional COV +Increment. This property provides a network-visible indication of those COV subscriptions that are active at any given time. +Whenever a COV Subscription is created with the SubscribeCOV or SubscribeCOVProperty service, a new entry is added to +the Active_COV_Subscriptions list. Similarly, whenever a COV Subscription is terminated, the corresponding entry shall be +removed from the Active_COV_Subscriptions list. +*/ +/* +SubscribeCOV-Request ::= SEQUENCE { + subscriberProcessIdentifier [0] Unsigned32, + monitoredObjectIdentifier [1] BACnetObjectIdentifier, + issueConfirmedNotifications [2] BOOLEAN OPTIONAL, + lifetime [3] Unsigned OPTIONAL + } +*/ + +int cov_subscribe_encode_adpu(uint8_t * apdu, + uint8_t invoke_id, BACNET_SUBSCRIBE_COV_DATA * data) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + uint16_t max_apdu = Device_Max_APDU_Length_Accepted(); + + if (apdu && data) { + apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST; + apdu[1] = encode_max_segs_max_apdu(0, max_apdu); + apdu[2] = invoke_id; + apdu[3] = SERVICE_CONFIRMED_SUBSCRIBE_COV; + apdu_len = 4; + /* tag 0 - subscriberProcessIdentifier */ + len = encode_context_unsigned(&apdu[apdu_len], + 0, data->subscriberProcessIdentifier); + apdu_len += len; + /* tag 1 - monitoredObjectIdentifier */ + len = encode_context_object_id(&apdu[apdu_len], + 1, + data->monitoredObjectIdentifier.type, + data->monitoredObjectIdentifier.instance); + apdu_len += len; + /* + If both the 'Issue Confirmed Notifications' and + 'Lifetime' parameters are absent, then this shall + indicate a cancellation request. + */ + if (!data->cancellationRequest) { + /* tag 2 - issueConfirmedNotifications */ + len = encode_context_boolean(&apdu[apdu_len], + 2, data->issueConfirmedNotifications); + apdu_len += len; + /* tag 3 - lifetime */ + len = encode_context_unsigned(&apdu[apdu_len], + 3, data->lifetime); + apdu_len += len; + } + } + + return apdu_len; +} + +/* decode the service request only */ +int cov_subscribe_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_SUBSCRIBE_COV_DATA * data) +{ + int len = 0; /* return value */ + uint8_t tag_number = 0; + uint32_t len_value = 0; + uint32_t decoded_value = 0; /* for decoding */ + int decoded_type = 0; /* for decoding */ + + if (apdu_len && data) { + /* tag 0 - subscriberProcessIdentifier */ + if (decode_is_context_tag(&apdu[len], 0)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += decode_unsigned(&apdu[len], len_value, &decoded_value); + data->subscriberProcessIdentifier = decoded_value; + } else + return -1; + /* tag 1 - monitoredObjectIdentifier */ + if (decode_is_context_tag(&apdu[len], 1)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += + decode_object_id(&apdu[len], &decoded_type, + &data->monitoredObjectIdentifier.instance); + data->monitoredObjectIdentifier.type = decoded_type; + } else + return -1; + /* optional parameters - if missing, means cancellation */ + if (len < apdu_len) { + /* tag 2 - issueConfirmedNotifications - optional */ + if (decode_is_context_tag(&apdu[len], 2)) { + data->cancellationRequest = false; + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + data->issueConfirmedNotifications = + decode_context_boolean(&apdu[len]); + len += len_value; + } else { + data->cancellationRequest = true; + } + /* tag 3 - lifetime - optional */ + if (decode_is_context_tag(&apdu[len], 3)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += decode_unsigned(&apdu[len], len_value, &decoded_value); + data->lifetime = decoded_value; + } else + data->lifetime = 0; + } else { + data->cancellationRequest = true; + } + } + + return len; +} + + +/* +SubscribeCOVProperty-Request ::= SEQUENCE { + subscriberProcessIdentifier [0] Unsigned32, + monitoredObjectIdentifier [1] BACnetObjectIdentifier, + issueConfirmedNotifications [2] BOOLEAN OPTIONAL, + lifetime [3] Unsigned OPTIONAL, + monitoredPropertyIdentifier [4] BACnetPropertyReference, + covIncrement [5] REAL OPTIONAL + } + +BACnetPropertyReference ::= SEQUENCE { + propertyIdentifier [0] BACnetPropertyIdentifier, + propertyArrayIndex [1] Unsigned OPTIONAL + -- used only with array datatype + -- if omitted with an array the entire array is referenced + } + +*/ + +int cov_subscribe_property_encode_adpu(uint8_t * apdu, + uint8_t invoke_id, BACNET_SUBSCRIBE_COV_DATA * data) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + uint16_t max_apdu = Device_Max_APDU_Length_Accepted(); + + if (apdu && data) { + apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST; + apdu[1] = encode_max_segs_max_apdu(0, max_apdu); + apdu[2] = invoke_id; + apdu[3] = SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY; + apdu_len = 4; + /* tag 0 - subscriberProcessIdentifier */ + len = encode_context_unsigned(&apdu[apdu_len], + 0, data->subscriberProcessIdentifier); + apdu_len += len; + /* tag 1 - monitoredObjectIdentifier */ + len = encode_context_object_id(&apdu[apdu_len], + 1, + data->monitoredObjectIdentifier.type, + data->monitoredObjectIdentifier.instance); + apdu_len += len; + if (!data->cancellationRequest) { + /* tag 2 - issueConfirmedNotifications */ + len = encode_context_boolean(&apdu[apdu_len], + 2, data->issueConfirmedNotifications); + apdu_len += len; + /* tag 3 - lifetime */ + len = encode_context_unsigned(&apdu[apdu_len], + 3, data->lifetime); + apdu_len += len; + } + /* tag 4 - monitoredPropertyIdentifier */ + len = encode_opening_tag(&apdu[apdu_len], 4); + apdu_len += len; + len = encode_context_enumerated(&apdu[apdu_len], + 0, data->monitoredProperty.propertyIdentifier); + apdu_len += len; + if (data->monitoredProperty.propertyArrayIndex != BACNET_ARRAY_ALL) { + len = encode_context_unsigned(&apdu[apdu_len], + 1, data->monitoredProperty.propertyArrayIndex); + apdu_len += len; + + } + len = encode_closing_tag(&apdu[apdu_len], 4); + apdu_len += len; + + /* tag 5 - covIncrement */ + if (data->covIncrementPresent) { + len = encode_context_real(&apdu[apdu_len], + 5, data->covIncrement); + apdu_len += len; + } + } + + return apdu_len; +} + +/* decode the service request only */ +int cov_subscribe_property_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_SUBSCRIBE_COV_DATA * data) +{ + int len = 0; /* return value */ + uint8_t tag_number = 0; + uint32_t len_value = 0; + uint32_t decoded_value = 0; /* for decoding */ + int decoded_type = 0; /* for decoding */ + int property = 0; /* for decoding */ + + if (apdu_len && data) { + /* tag 0 - subscriberProcessIdentifier */ + if (decode_is_context_tag(&apdu[len], 0)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += decode_unsigned(&apdu[len], len_value, &decoded_value); + data->subscriberProcessIdentifier = decoded_value; + } else + return -1; + /* tag 1 - monitoredObjectIdentifier */ + if (decode_is_context_tag(&apdu[len], 1)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += + decode_object_id(&apdu[len], &decoded_type, + &data->monitoredObjectIdentifier.instance); + data->monitoredObjectIdentifier.type = decoded_type; + } else + return -2; + /* tag 2 - issueConfirmedNotifications - optional */ + if (decode_is_context_tag(&apdu[len], 2)) { + data->cancellationRequest = false; + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + data->issueConfirmedNotifications = + decode_context_boolean(&apdu[len]); + len++; + } else + data->cancellationRequest = true; + /* tag 3 - lifetime - optional */ + if (decode_is_context_tag(&apdu[len], 3)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += decode_unsigned(&apdu[len], len_value, &decoded_value); + data->lifetime = decoded_value; + } else + data->lifetime = 0; + /* tag 4 - monitoredPropertyIdentifier */ + if (!decode_is_opening_tag_number(&apdu[len], 4)) + return -3; + /* a tag number of 4 is not extended so only one octet */ + len++; + /* the propertyIdentifier is tag 0 */ + if (decode_is_context_tag(&apdu[len], 0)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += decode_enumerated(&apdu[len], len_value, &property); + data->monitoredProperty.propertyIdentifier = property; + } else + return -4; + /* the optional array index is tag 1 */ + if (decode_is_context_tag(&apdu[len], 1)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += decode_unsigned(&apdu[len], len_value, &decoded_value); + data->monitoredProperty.propertyArrayIndex = decoded_value; + } else { + data->monitoredProperty.propertyArrayIndex = BACNET_ARRAY_ALL; + } + + if (!decode_is_closing_tag_number(&apdu[len], 4)) + return -5; + /* a tag number of 4 is not extended so only one octet */ + len++; + /* tag 5 - covIncrement - optional */ + if (decode_is_context_tag(&apdu[len], 5)) { + data->covIncrementPresent = true; + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += decode_real(&apdu[len], &data->covIncrement); + } else + data->covIncrementPresent = false; + } + + return len; +} + +int ucov_notify_send(uint8_t * buffer, BACNET_COV_DATA * data) +{ + int len = 0; + int pdu_len = 0; + BACNET_ADDRESS dest; + int bytes_sent = 0; + BACNET_NPDU_DATA npdu_data; + + /* unconfirmed is a broadcast */ + datalink_get_broadcast_address(&dest); + /* encode the NPDU portion of the packet */ + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&buffer[0], &dest, NULL, &npdu_data); + /* encode the APDU portion of the packet */ + len = ucov_notify_encode_apdu(&buffer[pdu_len], data); + pdu_len += len; + /* send the data */ + bytes_sent = datalink_send_pdu(&dest, &npdu_data, &buffer[0], pdu_len); + + return bytes_sent; +} + +#ifdef TEST +#include +#include +#include "ctest.h" +#include "bacapp.h" + +int ccov_notify_decode_apdu(uint8_t * apdu, + unsigned apdu_len, uint8_t * invoke_id, BACNET_COV_DATA * data) +{ + int len = 0; + unsigned offset = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) + return -2; + /* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */ + *invoke_id = apdu[2]; /* invoke id - filled in by net layer */ + if (apdu[3] != SERVICE_CONFIRMED_COV_NOTIFICATION) + return -3; + offset = 4; + + /* optional limits - must be used as a pair */ + if (apdu_len > offset) { + len = + cov_notify_decode_service_request(&apdu[offset], + apdu_len - offset, data); + } + + return len; +} + +int ucov_notify_decode_apdu(uint8_t * apdu, + unsigned apdu_len, BACNET_COV_DATA * data) +{ + int len = 0; + unsigned offset = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST) + return -2; + if (apdu[1] != SERVICE_UNCONFIRMED_COV_NOTIFICATION) + return -3; + /* optional limits - must be used as a pair */ + offset = 2; + if (apdu_len > offset) { + len = + cov_notify_decode_service_request(&apdu[offset], + apdu_len - offset, data); + } + + return len; +} + +int cov_subscribe_decode_apdu(uint8_t * apdu, + unsigned apdu_len, uint8_t * invoke_id, + BACNET_SUBSCRIBE_COV_DATA * data) +{ + int len = 0; + unsigned offset = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) + return -2; + /* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */ + *invoke_id = apdu[2]; /* invoke id - filled in by net layer */ + if (apdu[3] != SERVICE_CONFIRMED_SUBSCRIBE_COV) + return -3; + offset = 4; + + /* optional limits - must be used as a pair */ + if (apdu_len > offset) { + len = + cov_subscribe_decode_service_request(&apdu[offset], + apdu_len - offset, data); + } + + return len; +} + +int cov_subscribe_property_decode_apdu(uint8_t * apdu, + unsigned apdu_len, uint8_t * invoke_id, + BACNET_SUBSCRIBE_COV_DATA * data) +{ + int len = 0; + unsigned offset = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) + return -2; + /* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */ + *invoke_id = apdu[2]; /* invoke id - filled in by net layer */ + if (apdu[3] != SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY) + return -3; + offset = 4; + + /* optional limits - must be used as a pair */ + if (apdu_len > offset) { + len = + cov_subscribe_property_decode_service_request(&apdu[offset], + apdu_len - offset, data); + } + + return len; +} + +/* dummy function stubs */ +int npdu_encode_pdu(uint8_t * npdu, + BACNET_ADDRESS * dest, + BACNET_ADDRESS * src, BACNET_NPDU_DATA * npdu_data) +{ + return 0; +} + +void npdu_encode_npdu_data(BACNET_NPDU_DATA * npdu, + bool data_expecting_reply, BACNET_MESSAGE_PRIORITY priority) +{ + +} + +/* dummy function stubs */ +int datalink_send_pdu(BACNET_ADDRESS * dest, + BACNET_NPDU_DATA * npdu_data, uint8_t * pdu, unsigned pdu_len) +{ + (void) dest; + (void) npdu_data; + (void) pdu; + (void) pdu_len; + + return 0; +} + +/* dummy function stubs */ +void datalink_get_broadcast_address(BACNET_ADDRESS * dest) +{ + (void) dest; +} + +/* dummy function stubs */ +uint16_t Device_Max_APDU_Length_Accepted(void) +{ + return MAX_APDU; +} + +void testCOVNotifyData(Test * pTest, + BACNET_COV_DATA * data, BACNET_COV_DATA * test_data) +{ + ct_test(pTest, + test_data->subscriberProcessIdentifier == + data->subscriberProcessIdentifier); + ct_test(pTest, + test_data->initiatingDeviceIdentifier == + data->initiatingDeviceIdentifier); + ct_test(pTest, + test_data->monitoredObjectIdentifier.type == + data->monitoredObjectIdentifier.type); + ct_test(pTest, + test_data->monitoredObjectIdentifier.instance == + data->monitoredObjectIdentifier.instance); + ct_test(pTest, test_data->timeRemaining == data->timeRemaining); + /* FIXME: test the listOfValues in some clever manner */ +} + +void testUCOVNotifyData(Test * pTest, BACNET_COV_DATA * data) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + BACNET_COV_DATA test_data; + + len = ucov_notify_encode_apdu(&apdu[0], data); + ct_test(pTest, len > 0); + apdu_len = len; + + test_data.listOfValues.next = NULL; + len = ucov_notify_decode_apdu(&apdu[0], apdu_len, &test_data); + ct_test(pTest, len != -1); + testCOVNotifyData(pTest, data, &test_data); +} + +void testCCOVNotifyData(Test * pTest, uint8_t invoke_id, + BACNET_COV_DATA * data) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + BACNET_COV_DATA test_data; + uint8_t test_invoke_id = 0; + + len = ccov_notify_encode_apdu(&apdu[0], invoke_id, data); + ct_test(pTest, len != 0); + apdu_len = len; + + test_data.listOfValues.next = NULL; + len = ccov_notify_decode_apdu(&apdu[0], apdu_len, + &test_invoke_id, &test_data); + ct_test(pTest, len > 0); + ct_test(pTest, test_invoke_id == invoke_id); + testCOVNotifyData(pTest, data, &test_data); +} + +void testCOVNotify(Test * pTest) +{ + uint8_t invoke_id = 12; + BACNET_COV_DATA data; + /* BACNET_PROPERTY_VALUE value2; */ + + data.subscriberProcessIdentifier = 1; + data.initiatingDeviceIdentifier = 123; + data.monitoredObjectIdentifier.type = OBJECT_ANALOG_INPUT; + data.monitoredObjectIdentifier.instance = 321; + data.timeRemaining = 456; + + data.listOfValues.propertyIdentifier = PROP_PRESENT_VALUE; + data.listOfValues.propertyArrayIndex = BACNET_ARRAY_ALL; + bacapp_parse_application_data(BACNET_APPLICATION_TAG_REAL, + "21.0", &data.listOfValues.value); + data.listOfValues.priority = 0; + data.listOfValues.next = NULL; + + testUCOVNotifyData(pTest, &data); + testCCOVNotifyData(pTest, invoke_id, &data); + + /* FIXME: add more values to the list of values */ +} + +void testCOVSubscribeData(Test * pTest, + BACNET_SUBSCRIBE_COV_DATA * data, + BACNET_SUBSCRIBE_COV_DATA * test_data) +{ + ct_test(pTest, + test_data->subscriberProcessIdentifier == + data->subscriberProcessIdentifier); + ct_test(pTest, + test_data->monitoredObjectIdentifier.type == + data->monitoredObjectIdentifier.type); + ct_test(pTest, + test_data->monitoredObjectIdentifier.instance == + data->monitoredObjectIdentifier.instance); + ct_test(pTest, + test_data->cancellationRequest == data->cancellationRequest); + if (test_data->cancellationRequest != data->cancellationRequest) { + printf("cancellation request failed!\n"); + } + if (!test_data->cancellationRequest) { + ct_test(pTest, + test_data->issueConfirmedNotifications == + data->issueConfirmedNotifications); + ct_test(pTest, test_data->lifetime == data->lifetime); + } +} + +void testCOVSubscribePropertyData(Test * pTest, + BACNET_SUBSCRIBE_COV_DATA * data, + BACNET_SUBSCRIBE_COV_DATA * test_data) +{ + testCOVSubscribeData(pTest, data, test_data); + ct_test(pTest, + test_data->monitoredProperty.propertyIdentifier == + data->monitoredProperty.propertyIdentifier); + ct_test(pTest, + test_data->monitoredProperty.propertyArrayIndex == + data->monitoredProperty.propertyArrayIndex); + ct_test(pTest, + test_data->covIncrementPresent == data->covIncrementPresent); + if (test_data->covIncrementPresent) { + ct_test(pTest, test_data->covIncrement == data->covIncrement); + } +} + +void testCOVSubscribeEncoding(Test * pTest, uint8_t invoke_id, + BACNET_SUBSCRIBE_COV_DATA * data) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + BACNET_SUBSCRIBE_COV_DATA test_data; + uint8_t test_invoke_id = 0; + + len = cov_subscribe_encode_adpu(&apdu[0], invoke_id, data); + ct_test(pTest, len != 0); + apdu_len = len; + + len = cov_subscribe_decode_apdu(&apdu[0], apdu_len, + &test_invoke_id, &test_data); + ct_test(pTest, len > 0); + ct_test(pTest, test_invoke_id == invoke_id); + testCOVSubscribeData(pTest, data, &test_data); +} + +void testCOVSubscribePropertyEncoding(Test * pTest, uint8_t invoke_id, + BACNET_SUBSCRIBE_COV_DATA * data) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + BACNET_SUBSCRIBE_COV_DATA test_data; + uint8_t test_invoke_id = 0; + + len = cov_subscribe_property_encode_adpu(&apdu[0], invoke_id, data); + ct_test(pTest, len != 0); + apdu_len = len; + + len = cov_subscribe_property_decode_apdu(&apdu[0], apdu_len, + &test_invoke_id, &test_data); + ct_test(pTest, len > 0); + ct_test(pTest, test_invoke_id == invoke_id); + testCOVSubscribePropertyData(pTest, data, &test_data); +} + +void testCOVSubscribe(Test * pTest) +{ + uint8_t invoke_id = 12; + BACNET_SUBSCRIBE_COV_DATA data; + + data.subscriberProcessIdentifier = 1; + data.monitoredObjectIdentifier.type = OBJECT_ANALOG_INPUT; + data.monitoredObjectIdentifier.instance = 321; + data.cancellationRequest = false; + data.issueConfirmedNotifications = true; + data.lifetime = 456; + + testCOVSubscribeEncoding(pTest, invoke_id, &data); + data.cancellationRequest = true; + testCOVSubscribeEncoding(pTest, invoke_id, &data); +} + +void testCOVSubscribeProperty(Test * pTest) +{ + uint8_t invoke_id = 12; + BACNET_SUBSCRIBE_COV_DATA data; + + data.subscriberProcessIdentifier = 1; + data.monitoredObjectIdentifier.type = OBJECT_ANALOG_INPUT; + data.monitoredObjectIdentifier.instance = 321; + data.cancellationRequest = false; + data.issueConfirmedNotifications = true; + data.lifetime = 456; + data.monitoredProperty.propertyIdentifier = PROP_FILE_SIZE; + data.monitoredProperty.propertyArrayIndex = BACNET_ARRAY_ALL; + data.covIncrementPresent = true; + data.covIncrement = 1.0; + + testCOVSubscribePropertyEncoding(pTest, invoke_id, &data); + + data.cancellationRequest = true; + testCOVSubscribePropertyEncoding(pTest, invoke_id, &data); + + data.cancellationRequest = false; + data.covIncrementPresent = false; + testCOVSubscribePropertyEncoding(pTest, invoke_id, &data); +} + +#ifdef TEST_COV +int main(int argc, char *argv[]) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet COV", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testCOVNotify); + assert(rc); + rc = ct_addTestFunction(pTest, testCOVSubscribe); + assert(rc); + rc = ct_addTestFunction(pTest, testCOVSubscribeProperty); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_COV */ +#endif /* TEST */ diff --git a/bacnet-stack/cov.h b/bacnet-stack/cov.h index e69de29b..f70e3372 100644 --- a/bacnet-stack/cov.h +++ b/bacnet-stack/cov.h @@ -0,0 +1,120 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef COV_H +#define COV_H + +#include +#include +#include "bacapp.h" + +struct BACnet_Property_Value; +typedef struct BACnet_Property_Value { + BACNET_PROPERTY_ID propertyIdentifier; + unsigned propertyArrayIndex; + BACNET_APPLICATION_DATA_VALUE value; + uint8_t priority; + /* simple linked list */ + struct BACnet_Property_Value *next; +} BACNET_PROPERTY_VALUE; + +typedef struct BACnet_COV_Data { + uint32_t subscriberProcessIdentifier; + uint32_t initiatingDeviceIdentifier; + BACNET_OBJECT_ID monitoredObjectIdentifier; + unsigned timeRemaining; + /* simple linked list of values */ + BACNET_PROPERTY_VALUE listOfValues; +} BACNET_COV_DATA; + +typedef struct BACnet_Property_Reference { + BACNET_PROPERTY_ID propertyIdentifier; + unsigned propertyArrayIndex; /* optional */ +} BACNET_PROPERTY_REFERENCE; + +typedef struct BACnet_Subscribe_COV_Data { + uint32_t subscriberProcessIdentifier; + BACNET_OBJECT_ID monitoredObjectIdentifier; + bool cancellationRequest; /* true if this is a cancellation request */ + bool issueConfirmedNotifications; /* optional */ + unsigned lifetime; /* optional */ + BACNET_PROPERTY_REFERENCE monitoredProperty; + bool covIncrementPresent; /* true if present */ + float covIncrement; /* optional */ +} BACNET_SUBSCRIBE_COV_DATA; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + int ucov_notify_encode_apdu(uint8_t * apdu, BACNET_COV_DATA * data); + + int ucov_notify_decode_apdu(uint8_t * apdu, + unsigned apdu_len, BACNET_COV_DATA * data); + + int ucov_notify_send(uint8_t * buffer, BACNET_COV_DATA * data); + + int ccov_notify_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_COV_DATA * data); + + int ccov_notify_decode_apdu(uint8_t * apdu, + unsigned apdu_len, uint8_t * invoke_id, BACNET_COV_DATA * data); + + /* common for both confirmed and unconfirmed */ + int cov_notify_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_COV_DATA * data); + + int cov_subscribe_property_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_SUBSCRIBE_COV_DATA * data); + + int cov_subscribe_property_encode_adpu(uint8_t * apdu, + uint8_t invoke_id, BACNET_SUBSCRIBE_COV_DATA * data); + + int cov_subscribe_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_SUBSCRIBE_COV_DATA * data); + + int cov_subscribe_encode_adpu(uint8_t * apdu, + uint8_t invoke_id, BACNET_SUBSCRIBE_COV_DATA * data); + + +#ifdef TEST +#include "ctest.h" + void testCOVNotify(Test * pTest); + void testCOVSubscribeProperty(Test * pTest); + void testCOVSubscribe(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/crc.c b/bacnet-stack/crc.c index e69de29b..7ebeadb0 100644 --- a/bacnet-stack/crc.c +++ b/bacnet-stack/crc.c @@ -0,0 +1,153 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307 + USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include +#include + +/* Accumulate "dataValue" into the CRC in crcValue. */ +/* Return value is updated CRC */ +/* */ +/* The ^ operator means exclusive OR. */ +/* Note: This function is copied directly from the BACnet standard. */ +uint8_t CRC_Calc_Header(uint8_t dataValue, uint8_t crcValue) +{ + uint16_t crc; + + crc = crcValue ^ dataValue; /* XOR C7..C0 with D7..D0 */ + + /* Exclusive OR the terms in the table (top down) */ + crc = crc ^ (crc << 1) ^ (crc << 2) ^ (crc << 3) + ^ (crc << 4) ^ (crc << 5) ^ (crc << 6) + ^ (crc << 7); + + /* Combine bits shifted out left hand end */ + return (crc & 0xfe) ^ ((crc >> 8) & 1); +} + +/* Accumulate "dataValue" into the CRC in crcValue. */ +/* Return value is updated CRC */ +/* */ +/* The ^ operator means exclusive OR. */ +/* Note: This function is copied directly from the BACnet standard. */ +uint16_t CRC_Calc_Data(uint8_t dataValue, uint16_t crcValue) +{ + uint16_t crcLow; + + crcLow = (crcValue & 0xff) ^ dataValue; /* XOR C7..C0 with D7..D0 */ + + /* Exclusive OR the terms in the table (top down) */ + return (crcValue >> 8) ^ (crcLow << 8) ^ (crcLow << 3) + ^ (crcLow << 12) ^ (crcLow >> 4) + ^ (crcLow & 0x0f) ^ ((crcLow & 0x0f) << 7); +} + +#ifdef TEST +#include +#include +#include "ctest.h" +#include "bytes.h" + +/* test from Annex G 1.0 of BACnet Standard */ +void testCRC8(Test * pTest) +{ + uint8_t crc = 0xff; /* accumulates the crc value */ + uint8_t frame_crc; /* appended to the end of the frame */ + + crc = CRC_Calc_Header(0x00, crc); + ct_test(pTest, crc == 0x55); + crc = CRC_Calc_Header(0x10, crc); + ct_test(pTest, crc == 0xC2); + crc = CRC_Calc_Header(0x05, crc); + ct_test(pTest, crc == 0xBC); + crc = CRC_Calc_Header(0x00, crc); + ct_test(pTest, crc == 0x95); + crc = CRC_Calc_Header(0x00, crc); + ct_test(pTest, crc == 0x73); + /* send the ones complement of the CRC in place of */ + /* the CRC, and the resulting CRC will always equal 0x55. */ + frame_crc = ~crc; + ct_test(pTest, frame_crc == 0x8C); + /* use the ones complement value and the next to last CRC value */ + crc = CRC_Calc_Header(frame_crc, crc); + ct_test(pTest, crc == 0x55); +} + +/* test from Annex G 2.0 of BACnet Standard */ +void testCRC16(Test * pTest) +{ + uint16_t crc = 0xffff; + uint16_t data_crc; + + crc = CRC_Calc_Data(0x01, crc); + ct_test(pTest, crc == 0x1E0E); + crc = CRC_Calc_Data(0x22, crc); + ct_test(pTest, crc == 0xEB70); + crc = CRC_Calc_Data(0x30, crc); + ct_test(pTest, crc == 0x42EF); + /* send the ones complement of the CRC in place of */ + /* the CRC, and the resulting CRC will always equal 0xF0B8. */ + data_crc = ~crc; + ct_test(pTest, data_crc == 0xBD10); + crc = CRC_Calc_Data(LO_BYTE(data_crc), crc); + ct_test(pTest, crc == 0x0F3A); + crc = CRC_Calc_Data(HI_BYTE(data_crc), crc); + ct_test(pTest, crc == 0xF0B8); +} + +#endif + +#ifdef TEST_CRC +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("crc", NULL); + + /* individual tests */ + rc = ct_addTestFunction(pTest, testCRC8); + assert(rc); + rc = ct_addTestFunction(pTest, testCRC16); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + + ct_destroy(pTest); + + return 0; +} +#endif diff --git a/bacnet-stack/crc.h b/bacnet-stack/crc.h index e69de29b..c0af37fc 100644 --- a/bacnet-stack/crc.h +++ b/bacnet-stack/crc.h @@ -0,0 +1,51 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307 + USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef CRC_H +#define CRC_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + uint8_t CRC_Calc_Header(uint8_t dataValue, uint8_t crcValue); + uint16_t CRC_Calc_Data(uint8_t dataValue, uint16_t crcValue); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/datalink.h b/bacnet-stack/datalink.h index e69de29b..60230b36 100644 --- a/bacnet-stack/datalink.h +++ b/bacnet-stack/datalink.h @@ -0,0 +1,91 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef DATALINK_H +#define DATALINK_H + +#if defined(BACDL_ETHERNET) +#include "ethernet.h" + +#define datalink_init ethernet_init +#define datalink_send_pdu ethernet_send_pdu +#define datalink_receive ethernet_receive +#define datalink_cleanup ethernet_cleanup +#define datalink_get_broadcast_address ethernet_get_broadcast_address +#define datalink_get_my_address ethernet_get_my_address + +#elif defined(BACDL_ARCNET) +#include "arcnet.h" + +#define datalink_init arcnet_init +#define datalink_send_pdu arcnet_send_pdu +#define datalink_receive arcnet_receive +#define datalink_cleanup arcnet_cleanup +#define datalink_get_broadcast_address arcnet_get_broadcast_address +#define datalink_get_my_address arcnet_get_my_address + +#elif defined(BACDL_MSTP) +#include "dlmstp.h" + +#define datalink_init dlmstp_init +#define datalink_send_pdu dlmstp_send_pdu +#define datalink_receive dlmstp_receive +#define datalink_cleanup dlmstp_cleanup +#define datalink_get_broadcast_address dlmstp_get_broadcast_address +#define datalink_get_my_address dlmstp_get_my_address + +#elif defined(BACDL_BIP) +#include "bip.h" + +#define datalink_init bip_init +#define datalink_send_pdu bip_send_pdu +#define datalink_receive bip_receive +#define datalink_cleanup bip_cleanup +#define datalink_get_broadcast_address bip_get_broadcast_address +#define datalink_get_my_address bip_get_my_address + +#elif defined(BACDL_TEST) +#include "npdu.h" + +extern int datalink_send_pdu(BACNET_ADDRESS * dest, + BACNET_NPDU_DATA * npdu_data, uint8_t * pdu, unsigned pdu_len); +extern uint16_t datalink_receive(BACNET_ADDRESS * src, + uint8_t * pdu, uint16_t max_pdu, unsigned timeout); +extern void datalink_cleanup(void); +extern void datalink_get_broadcast_address(BACNET_ADDRESS * dest); +extern void datalink_get_my_address(BACNET_ADDRESS * my_address); +extern void datalink_set_interface(char *ifname); + +#endif + +#endif diff --git a/bacnet-stack/datetime.c b/bacnet-stack/datetime.c index e69de29b..14226fa6 100644 --- a/bacnet-stack/datetime.c +++ b/bacnet-stack/datetime.c @@ -0,0 +1,727 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2007 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include +#include +#include +#include +#include +#include "datetime.h" + +/* BACnet Date */ +/* year = years since 1900 */ +/* month 1=Jan */ +/* day = day of month 1..31 */ +/* wday 1=Monday...7=Sunday */ + +/* Wildcards: + A value of X'FF' in any of the four octets + shall indicate that the value is unspecified. + If all four octets = X'FF', the corresponding + time or date may be interpreted as "any" or "don't care" +*/ + +static bool is_leap_year(uint16_t year) +{ + if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) + return (true); + else + return (false); +} + +static uint8_t month_days(uint16_t year, uint8_t month) +{ + /* note: start with a zero in the first element to save us from a + month - 1 calculation in the lookup */ + int month_days[13] = + { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + + /* February */ + if ((month == 2) && is_leap_year(year)) + return 29; + else if (month >= 1 && month <= 12) + return month_days[month]; + else + return 0; +} + +static uint32_t days_since_epoch(uint16_t year, uint8_t month, uint8_t day) +{ + uint32_t days = 0; /* return value */ + uint8_t monthdays; /* days in a month */ + uint16_t years = 0; /* loop counter for years */ + uint8_t months = 0; /* loop counter for months */ + + monthdays = month_days(year, month); + if ((year >= 1900) && (monthdays) && (day >= 1) && (day <= monthdays)) { + for (years = 1900; years < year; years++) { + days += 365; + if (is_leap_year(years)) + days++; + } + for (months = 1; months < month; months++) { + days += month_days(years, months); + } + days += (day - 1); + } + + return (days); +} + +static void days_since_epoch_into_ymd(uint32_t days, + uint16_t * pYear, uint8_t * pMonth, uint8_t * pDay) +{ + uint16_t year = 1900; + uint8_t month = 1; + uint8_t day = 1; + + while (days >= 365) { + if ((is_leap_year(year)) && (days == 365)) + break; + days -= 365; + if (is_leap_year(year)) + --days; + year++; + } + + while (days >= (uint32_t) month_days(year, month)) { + days -= month_days(year, month); + month++; + } + + day += ((uint8_t) days); + + if (pYear) + *pYear = year; + if (pMonth) + *pMonth = month; + if (pDay) + *pDay = day; + + return; +} + + +/* Jan 1, 1900 is a Monday */ +/* wday 1=Monday...7=Sunday */ +static uint8_t day_of_week(uint16_t year, uint8_t month, uint8_t day) +{ + return ((uint8_t) (days_since_epoch(year, month, day) % 7) + 1); +} + +/* if the date1 is the same as date2, return is 0 + if date1 is after date2, returns positive + if date1 is before date2, returns negative */ +int datetime_compare_date(BACNET_DATE * date1, BACNET_DATE * date2) +{ + int diff = 0; + + if (date1 && date2) { + diff = (int) date1->year - (int) date2->year; + if (diff == 0) { + diff = (int) date1->month - (int) date2->month; + if (diff == 0) { + diff = (int) date1->day - (int) date2->day; + } + } + } + + return diff; +} + +/* if the time1 is the same as time2, return is 0 + if time1 is after time2, returns positive + if time1 is before time2, returns negative */ +int datetime_compare_time(BACNET_TIME * time1, BACNET_TIME * time2) +{ + int diff = 0; + + if (time1 && time2) { + diff = (int) time1->hour - (int) time2->hour; + if (diff == 0) { + diff = (int) time1->min - (int) time2->min; + if (diff == 0) { + diff = (int) time1->sec - (int) time2->sec; + if (diff == 0) { + diff = + (int) time1->hundredths - (int) time2->hundredths; + } + } + } + } + + return diff; +} + +/* if the datetime1 is the same as datetime2, return is 0 + if datetime1 is before datetime2, returns negative + if datetime1 is after datetime2, returns positive */ +int datetime_compare(BACNET_DATE_TIME * datetime1, + BACNET_DATE_TIME * datetime2) +{ + int diff = 0; + + diff = datetime_compare_date(&datetime1->date, &datetime2->date); + if (diff == 0) { + diff = datetime_compare_time(&datetime1->time, &datetime2->time); + } + + return diff; +} + +void datetime_copy_date(BACNET_DATE * dest_date, BACNET_DATE * src_date) +{ + if (dest_date && src_date) { + dest_date->year = src_date->year; + dest_date->month = src_date->month; + dest_date->day = src_date->day; + dest_date->wday = src_date->wday; + } +} + +void datetime_copy_time(BACNET_TIME * dest_time, BACNET_TIME * src_time) +{ + if (dest_time && src_time) { + dest_time->hour = src_time->hour; + dest_time->min = src_time->min; + dest_time->sec = src_time->sec; + dest_time->hundredths = src_time->hundredths; + } +} + +void datetime_copy(BACNET_DATE_TIME * dest_datetime, + BACNET_DATE_TIME * src_datetime) +{ + datetime_copy_time(&dest_datetime->time, &src_datetime->time); + datetime_copy_date(&dest_datetime->date, &src_datetime->date); +} + +void datetime_set_date(BACNET_DATE * bdate, + uint16_t year, uint8_t month, uint8_t day) +{ + if (bdate) { + bdate->year = year; + bdate->month = month; + bdate->day = day; + bdate->wday = day_of_week(year, month, day); + } +} + +void datetime_set_time(BACNET_TIME * btime, + uint8_t hour, uint8_t minute, uint8_t seconds, uint8_t hundredths) +{ + if (btime) { + btime->hour = hour; + btime->min = minute; + btime->sec = seconds; + btime->hundredths = hundredths; + } +} + +void datetime_set(BACNET_DATE_TIME * bdatetime, + BACNET_DATE * bdate, BACNET_TIME * btime) +{ + if (bdate && btime && bdatetime) { + bdatetime->time.hour = btime->hour; + bdatetime->time.min = btime->min; + bdatetime->time.sec = btime->sec; + bdatetime->time.hundredths = btime->hundredths; + bdatetime->date.year = bdate->year; + bdatetime->date.month = bdate->month; + bdatetime->date.day = bdate->day; + bdatetime->date.wday = bdate->wday; + } +} + +void datetime_set_values(BACNET_DATE_TIME * bdatetime, + uint16_t year, uint8_t month, uint8_t day, + uint8_t hour, uint8_t minute, uint8_t seconds, uint8_t hundredths) +{ + if (bdatetime) { + bdatetime->date.year = year; + bdatetime->date.month = month; + bdatetime->date.day = day; + bdatetime->date.wday = day_of_week(year, month, day); + bdatetime->time.hour = hour; + bdatetime->time.min = minute; + bdatetime->time.sec = seconds; + bdatetime->time.hundredths = hundredths; + } +} + +static uint32_t seconds_since_midnight(uint8_t hours, uint8_t minutes, + uint8_t seconds) +{ + return ((hours * 60 * 60) + (minutes * 60) + seconds); +} + +static void seconds_since_midnight_into_hms(uint32_t seconds, + uint8_t * pHours, uint8_t * pMinutes, uint8_t * pSeconds) +{ + uint8_t hour = 0; + uint8_t minute = 0; + + hour = (uint8_t) (seconds / (60 * 60)); + seconds -= (hour * 60 * 60); + minute = (uint8_t) (seconds / 60); + seconds -= (minute * 60); + + if (pHours) + *pHours = hour; + if (pMinutes) + *pMinutes = minute; + if (pSeconds) + *pSeconds = (uint8_t) seconds; +} + +void datetime_add_minutes(BACNET_DATE_TIME * bdatetime, uint32_t minutes) +{ + uint32_t bdatetime_minutes = 0; + uint32_t bdatetime_days = 0; + uint32_t days = 0; + + /* convert bdatetime to seconds and days */ + bdatetime_minutes = seconds_since_midnight(bdatetime->time.hour, + bdatetime->time.min, bdatetime->time.sec) / 60; + bdatetime_days = days_since_epoch(bdatetime->date.year, + bdatetime->date.month, bdatetime->date.day); + + /* add */ + days = minutes / (24 * 60); + bdatetime_days += days; + minutes -= (days * 24 * 60); + bdatetime_minutes += minutes; + days = bdatetime_minutes / (24 * 60); + bdatetime_days += days; + + /* convert bdatetime from seconds and days */ + seconds_since_midnight_into_hms(bdatetime_minutes * 60, + &bdatetime->time.hour, &bdatetime->time.min, &bdatetime->time.sec); + days_since_epoch_into_ymd(bdatetime_days, + &bdatetime->date.year, + &bdatetime->date.month, &bdatetime->date.day); + bdatetime->date.wday = day_of_week(bdatetime->date.year, + bdatetime->date.month, bdatetime->date.day); +} + +bool datetime_wildcard(BACNET_DATE_TIME * bdatetime) +{ + bool wildcard_present = false; + + if (bdatetime) { + if ((bdatetime->date.year == (1900 + 0xFF)) && + (bdatetime->date.month == 0xFF) && + (bdatetime->date.day == 0xFF) && + (bdatetime->date.wday == 0xFF) && + (bdatetime->time.hour == 0xFF) && + (bdatetime->time.min == 0xFF) && + (bdatetime->time.sec == 0xFF) && + (bdatetime->time.hundredths == 0xFF)) { + wildcard_present = true; + } + } + + return wildcard_present; +} + +void datetime_date_wildcard_set(BACNET_DATE * bdate) +{ + if (bdate) { + bdate->year = 1900 + 0xFF; + bdate->month = 0xFF; + bdate->day = 0xFF; + bdate->wday = 0xFF; + } +} + +void datetime_time_wildcard_set(BACNET_TIME * btime) +{ + if (btime) { + btime->hour = 0xFF; + btime->min = 0xFF; + btime->sec = 0xFF; + btime->hundredths = 0xFF; + } +} + +void datetime_wildcard_set(BACNET_DATE_TIME * bdatetime) +{ + if (bdatetime) { + datetime_date_wildcard_set(&bdatetime->date); + datetime_time_wildcard_set(&bdatetime->time); + } +} + + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testBACnetDateTimeWildcard(Test * pTest) +{ + BACNET_DATE_TIME bdatetime; + bool status = false; + + datetime_set_values(&bdatetime, 1900, 1, 1, 0, 0, 0, 0); + status = datetime_wildcard(&bdatetime); + ct_test(pTest, status == false); + + datetime_wildcard_set(&bdatetime); + status = datetime_wildcard(&bdatetime); + ct_test(pTest, status == true); +} + +void testBACnetDateTimeAdd(Test * pTest) +{ + BACNET_DATE_TIME bdatetime, test_bdatetime; + uint32_t minutes = 0; + int diff = 0; + + datetime_set_values(&bdatetime, 1900, 1, 1, 0, 0, 0, 0); + datetime_copy(&test_bdatetime, &bdatetime); + datetime_add_minutes(&bdatetime, minutes); + diff = datetime_compare(&test_bdatetime, &bdatetime); + ct_test(pTest, diff == 0); + + datetime_set_values(&bdatetime, 1900, 1, 1, 0, 0, 0, 0); + datetime_add_minutes(&bdatetime, 60); + datetime_set_values(&test_bdatetime, 1900, 1, 1, 1, 0, 0, 0); + diff = datetime_compare(&test_bdatetime, &bdatetime); + ct_test(pTest, diff == 0); + + datetime_set_values(&bdatetime, 1900, 1, 1, 0, 0, 0, 0); + datetime_add_minutes(&bdatetime, (24 * 60)); + datetime_set_values(&test_bdatetime, 1900, 1, 2, 0, 0, 0, 0); + diff = datetime_compare(&test_bdatetime, &bdatetime); + ct_test(pTest, diff == 0); + + datetime_set_values(&bdatetime, 1900, 1, 1, 0, 0, 0, 0); + datetime_add_minutes(&bdatetime, (31 * 24 * 60)); + datetime_set_values(&test_bdatetime, 1900, 2, 1, 0, 0, 0, 0); + diff = datetime_compare(&test_bdatetime, &bdatetime); + ct_test(pTest, diff == 0); +} + + + +void testBACnetDateTimeSeconds(Test * pTest) +{ + uint8_t hour = 0, minute = 0, second = 0; + uint8_t test_hour = 0, test_minute = 0, test_second = 0; + uint32_t seconds = 0, test_seconds; + + for (hour = 0; hour < 24; hour++) { + for (minute = 0; minute < 60; minute += 3) { + for (second = 0; second < 60; second += 17) { + seconds = seconds_since_midnight(hour, minute, second); + seconds_since_midnight_into_hms(seconds, + &test_hour, &test_minute, &test_second); + test_seconds = + seconds_since_midnight(test_hour, test_minute, + test_second); + ct_test(pTest, seconds == test_seconds); + } + } + } +} + +void testBACnetDate(Test * pTest) +{ + BACNET_DATE bdate1, bdate2; + int diff = 0; + + datetime_set_date(&bdate1, 1900, 1, 1); + datetime_copy_date(&bdate2, &bdate1); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff == 0); + datetime_set_date(&bdate2, 1900, 1, 2); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff < 0); + datetime_set_date(&bdate2, 1900, 2, 1); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff < 0); + datetime_set_date(&bdate2, 1901, 1, 1); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff < 0); + + /* midpoint */ + datetime_set_date(&bdate1, 2007, 7, 15); + datetime_copy_date(&bdate2, &bdate1); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff == 0); + datetime_set_date(&bdate2, 2007, 7, 14); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff > 0); + datetime_set_date(&bdate2, 2007, 7, 1); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff > 0); + datetime_set_date(&bdate2, 2007, 7, 31); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff < 0); + datetime_set_date(&bdate2, 2007, 8, 15); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff < 0); + datetime_set_date(&bdate2, 2007, 12, 15); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff < 0); + datetime_set_date(&bdate2, 2007, 6, 15); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff > 0); + datetime_set_date(&bdate2, 2007, 1, 15); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff > 0); + datetime_set_date(&bdate2, 2006, 7, 15); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff > 0); + datetime_set_date(&bdate2, 1900, 7, 15); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff > 0); + datetime_set_date(&bdate2, 2008, 7, 15); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff < 0); + datetime_set_date(&bdate2, 2154, 7, 15); + diff = datetime_compare_date(&bdate1, &bdate2); + ct_test(pTest, diff < 0); + + return; +} + +void testBACnetTime(Test * pTest) +{ + BACNET_TIME btime1, btime2; + int diff = 0; + + datetime_set_time(&btime1, 0, 0, 0, 0); + datetime_copy_time(&btime2, &btime1); + diff = datetime_compare_time(&btime1, &btime2); + ct_test(pTest, diff == 0); + + datetime_set_time(&btime1, 23, 59, 59, 99); + datetime_copy_time(&btime2, &btime1); + diff = datetime_compare_time(&btime1, &btime2); + ct_test(pTest, diff == 0); + + /* midpoint */ + datetime_set_time(&btime1, 12, 30, 30, 50); + datetime_copy_time(&btime2, &btime1); + diff = datetime_compare_time(&btime1, &btime2); + ct_test(pTest, diff == 0); + datetime_set_time(&btime2, 12, 30, 30, 51); + diff = datetime_compare_time(&btime1, &btime2); + ct_test(pTest, diff < 0); + datetime_set_time(&btime2, 12, 30, 31, 50); + diff = datetime_compare_time(&btime1, &btime2); + ct_test(pTest, diff < 0); + datetime_set_time(&btime2, 12, 31, 30, 50); + diff = datetime_compare_time(&btime1, &btime2); + ct_test(pTest, diff < 0); + datetime_set_time(&btime2, 13, 30, 30, 50); + diff = datetime_compare_time(&btime1, &btime2); + ct_test(pTest, diff < 0); + + datetime_set_time(&btime2, 12, 30, 30, 49); + diff = datetime_compare_time(&btime1, &btime2); + ct_test(pTest, diff > 0); + datetime_set_time(&btime2, 12, 30, 29, 50); + diff = datetime_compare_time(&btime1, &btime2); + ct_test(pTest, diff > 0); + datetime_set_time(&btime2, 12, 29, 30, 50); + diff = datetime_compare_time(&btime1, &btime2); + ct_test(pTest, diff > 0); + datetime_set_time(&btime2, 11, 30, 30, 50); + diff = datetime_compare_time(&btime1, &btime2); + ct_test(pTest, diff > 0); + + return; +} + +void testBACnetDateTime(Test * pTest) +{ + BACNET_DATE_TIME bdatetime1, bdatetime2; + BACNET_DATE bdate; + BACNET_TIME btime; + int diff = 0; + + datetime_set_values(&bdatetime1, 1900, 1, 1, 0, 0, 0, 0); + datetime_copy(&bdatetime2, &bdatetime1); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff == 0); + datetime_set_time(&btime, 0, 0, 0, 0); + datetime_set_date(&bdate, 1900, 1, 1); + datetime_set(&bdatetime1, &bdate, &btime); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff == 0); + + /* midpoint */ + /* if datetime1 is before datetime2, returns negative */ + datetime_set_values(&bdatetime1, 2000, 7, 15, 12, 30, 30, 50); + datetime_set_values(&bdatetime2, 2000, 7, 15, 12, 30, 30, 51); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff < 0); + datetime_set_values(&bdatetime2, 2000, 7, 15, 12, 30, 31, 50); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff < 0); + datetime_set_values(&bdatetime2, 2000, 7, 15, 12, 31, 30, 50); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff < 0); + datetime_set_values(&bdatetime2, 2000, 7, 15, 13, 30, 30, 50); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff < 0); + datetime_set_values(&bdatetime2, 2000, 7, 16, 12, 30, 30, 50); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff < 0); + datetime_set_values(&bdatetime2, 2000, 8, 15, 12, 30, 30, 50); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff < 0); + datetime_set_values(&bdatetime2, 2001, 7, 15, 12, 30, 30, 50); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff < 0); + datetime_set_values(&bdatetime2, 2000, 7, 15, 12, 30, 30, 49); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff > 0); + datetime_set_values(&bdatetime2, 2000, 7, 15, 12, 30, 29, 50); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff > 0); + datetime_set_values(&bdatetime2, 2000, 7, 15, 12, 29, 30, 50); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff > 0); + datetime_set_values(&bdatetime2, 2000, 7, 15, 11, 30, 30, 50); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff > 0); + datetime_set_values(&bdatetime2, 2000, 7, 14, 12, 30, 30, 50); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff > 0); + datetime_set_values(&bdatetime2, 2000, 6, 15, 12, 30, 30, 50); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff > 0); + datetime_set_values(&bdatetime2, 1999, 7, 15, 12, 30, 30, 50); + diff = datetime_compare(&bdatetime1, &bdatetime2); + ct_test(pTest, diff > 0); + + + return; +} + +void testDateEpoch(Test * pTest) +{ + uint32_t days = 0; + uint16_t year = 0, test_year = 0; + uint8_t month = 0, test_month = 0; + uint8_t day = 0, test_day = 0; + + days = days_since_epoch(1900, 1, 1); + ct_test(pTest, days == 0); + days_since_epoch_into_ymd(days, &year, &month, &day); + ct_test(pTest, year == 1900); + ct_test(pTest, month == 1); + ct_test(pTest, day == 1); + + + for (year = 1900; year <= 2154; year++) { + for (month = 1; month <= 12; month++) { + for (day = 1; day <= month_days(year, month); day++) { + days = days_since_epoch(year, month, day); + days_since_epoch_into_ymd(days, + &test_year, &test_month, &test_day); + ct_test(pTest, year == test_year); + ct_test(pTest, month == test_month); + ct_test(pTest, day == test_day); + } + } + } +} + +void testBACnetDayOfWeek(Test * pTest) +{ + uint8_t dow = 0; + + /* 1/1/1900 is a Monday */ + dow = day_of_week(1900, 1, 1); + ct_test(pTest, dow == 1); + + /* 1/1/2007 is a Monday */ + dow = day_of_week(2007, 1, 1); + ct_test(pTest, dow == 1); + dow = day_of_week(2007, 1, 2); + ct_test(pTest, dow == 2); + dow = day_of_week(2007, 1, 3); + ct_test(pTest, dow == 3); + dow = day_of_week(2007, 1, 4); + ct_test(pTest, dow == 4); + dow = day_of_week(2007, 1, 5); + ct_test(pTest, dow == 5); + dow = day_of_week(2007, 1, 6); + ct_test(pTest, dow == 6); + dow = day_of_week(2007, 1, 7); + ct_test(pTest, dow == 7); + + dow = day_of_week(2007, 1, 31); + ct_test(pTest, dow == 3); +} + +#ifdef TEST_DATE_TIME +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Date Time", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testBACnetDate); + assert(rc); + rc = ct_addTestFunction(pTest, testBACnetTime); + assert(rc); + rc = ct_addTestFunction(pTest, testBACnetDateTime); + assert(rc); + rc = ct_addTestFunction(pTest, testBACnetDayOfWeek); + assert(rc); + rc = ct_addTestFunction(pTest, testDateEpoch); + assert(rc); + rc = ct_addTestFunction(pTest, testBACnetDateTimeSeconds); + assert(rc); + rc = ct_addTestFunction(pTest, testBACnetDateTimeAdd); + assert(rc); + rc = ct_addTestFunction(pTest, testBACnetDateTimeWildcard); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_DATE_TIME */ +#endif /* TEST */ diff --git a/bacnet-stack/datetime.h b/bacnet-stack/datetime.h index e69de29b..dc3f7fcf 100644 --- a/bacnet-stack/datetime.h +++ b/bacnet-stack/datetime.h @@ -0,0 +1,114 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2007 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef DATE_TIME_H +#define DATE_TIME_H + +#include +#include + +typedef enum { + BACNET_WEEKDAY_MONDAY = 1, + BACNET_WEEKDAY_TUESDAY = 2, + BACNET_WEEKDAY_WEDNESDAY = 3, + BACNET_WEEKDAY_THURSDAY = 4, + BACNET_WEEKDAY_FRIDAY = 5, + BACNET_WEEKDAY_SATURDAY = 6, + BACNET_WEEKDAY_SUNDAY = 7 +} BACNET_WEEKDAY; + +/* date */ +typedef struct BACnet_Date { + uint16_t year; /* AD */ + uint8_t month; /* 1=Jan */ + uint8_t day; /* 1..31 */ + uint8_t wday; /* 1=Monday-7=Sunday */ +} BACNET_DATE; + +/* time */ +typedef struct BACnet_Time { + uint8_t hour; + uint8_t min; + uint8_t sec; + uint8_t hundredths; +} BACNET_TIME; + +typedef struct BACnet_DateTime { + BACNET_DATE date; + BACNET_TIME time; +} BACNET_DATE_TIME; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + /* utility initialization functions */ + void datetime_set_date(BACNET_DATE * bdate, + uint16_t year, uint8_t month, uint8_t day); + void datetime_set_time(BACNET_TIME * btime, + uint8_t hour, uint8_t minute, uint8_t seconds, uint8_t hundredths); + void datetime_set(BACNET_DATE_TIME * bdatetime, + BACNET_DATE * bdate, BACNET_TIME * btime); + void datetime_set_values(BACNET_DATE_TIME * bdatetime, + uint16_t year, uint8_t month, uint8_t day, + uint8_t hour, uint8_t minute, uint8_t seconds, uint8_t hundredths); + + /* utility comparison functions: + if the date/times are the same, return is 0 + if date1 is before date2, returns negative + if date1 is after date2, returns positive */ + int datetime_compare_date(BACNET_DATE * date1, BACNET_DATE * date2); + int datetime_compare_time(BACNET_TIME * time1, BACNET_TIME * time2); + int datetime_compare(BACNET_DATE_TIME * datetime1, + BACNET_DATE_TIME * datetime2); + + /* utility copy functions */ + void datetime_copy_date(BACNET_DATE * date1, BACNET_DATE * date2); + void datetime_copy_time(BACNET_TIME * time1, BACNET_TIME * time2); + void datetime_copy(BACNET_DATE_TIME * datetime1, + BACNET_DATE_TIME * datetime2); + + /* utility add function */ + void datetime_add_minutes(BACNET_DATE_TIME * bdatetime, + uint32_t minutes); + + /* date and time wildcards */ + bool datetime_wildcard(BACNET_DATE_TIME * bdatetime); + void datetime_wildcard_set(BACNET_DATE_TIME * bdatetime); + void datetime_date_wildcard_set(BACNET_DATE * bdate); + void datetime_time_wildcard_set(BACNET_TIME * btime); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* DATE_TIME_H */ diff --git a/bacnet-stack/dcc.c b/bacnet-stack/dcc.c index e69de29b..10d6a883 100644 --- a/bacnet-stack/dcc.c +++ b/bacnet-stack/dcc.c @@ -0,0 +1,308 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacdcode.h" +#include "bacdef.h" +#include "dcc.h" + +/* note: the disable and time are not expected to survive + over a power cycle or reinitialization. */ +/* note: time duration is given in Minutes, but in order to be accurate, + we need to count down in seconds. */ +static uint32_t DCC_Time_Duration_Seconds = 0; +static BACNET_COMMUNICATION_ENABLE_DISABLE DCC_Enable_Disable = + COMMUNICATION_ENABLE; +/* password is optionally supported */ + +BACNET_COMMUNICATION_ENABLE_DISABLE dcc_enable_status(void) +{ + return DCC_Enable_Disable; +} + +bool dcc_communication_enabled(void) +{ + return (DCC_Enable_Disable == COMMUNICATION_ENABLE); +} + +/* When network communications are completely disabled, + only DeviceCommunicationControl and ReinitializeDevice APDUs + shall be processed and no messages shall be initiated.*/ +bool dcc_communication_disabled(void) +{ + return (DCC_Enable_Disable == COMMUNICATION_DISABLE); +} + +/* When the initiation of communications is disabled, + all APDUs shall be processed and responses returned as + required and no messages shall be initiated with the + exception of I-Am requests, which shall be initiated only in + response to Who-Is messages. In this state, a device that + supports I-Am request initiation shall send one I-Am request + for any Who-Is request that is received if and only if + the Who-Is request does not contain an address range or + the device is included in the address range. */ +bool dcc_communication_initiation_disabled(void) +{ + return (DCC_Enable_Disable == COMMUNICATION_DISABLE_INITIATION); +} + +uint32_t dcc_duration_seconds(void) +{ + return DCC_Time_Duration_Seconds; +} + +/* called every second or so. If more than one second, + then seconds should be the number of seconds to tick away */ +void dcc_timer_seconds(uint32_t seconds) +{ + if (DCC_Time_Duration_Seconds) { + if (DCC_Time_Duration_Seconds > seconds) + DCC_Time_Duration_Seconds -= seconds; + else + DCC_Time_Duration_Seconds = 0; + /* just expired - do something */ + if (DCC_Time_Duration_Seconds == 0) + DCC_Enable_Disable = COMMUNICATION_ENABLE; + } +} + +bool dcc_set_status_duration(BACNET_COMMUNICATION_ENABLE_DISABLE status, + uint16_t minutes) +{ + bool valid = false; + + /* valid? */ + if (status < MAX_BACNET_COMMUNICATION_ENABLE_DISABLE) { + DCC_Enable_Disable = status; + if (status == COMMUNICATION_ENABLE) + DCC_Time_Duration_Seconds = 0; + else + DCC_Time_Duration_Seconds = minutes * 60; + valid = true; + } + + return valid; +} + +/* encode service */ +int dcc_encode_apdu(uint8_t * apdu, uint8_t invoke_id, uint16_t timeDuration, /* 0=optional */ + BACNET_COMMUNICATION_ENABLE_DISABLE enable_disable, + BACNET_CHARACTER_STRING * password) +{ /* NULL=optional */ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST; + apdu[1] = encode_max_segs_max_apdu(0, MAX_APDU); + apdu[2] = invoke_id; + apdu[3] = SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL; + apdu_len = 4; + /* optional timeDuration */ + if (timeDuration) { + len = + encode_context_unsigned(&apdu[apdu_len], 0, timeDuration); + apdu_len += len; + } + /* enable disable */ + len = + encode_context_enumerated(&apdu[apdu_len], 1, enable_disable); + apdu_len += len; + /* optional password */ + if (password) { + /* FIXME: must be at least 1 character, limited to 20 characters */ + len = + encode_context_character_string(&apdu[apdu_len], 2, + password); + apdu_len += len; + } + } + + return apdu_len; +} + +/* decode the service request only */ +int dcc_decode_service_request(uint8_t * apdu, + unsigned apdu_len, + uint16_t * timeDuration, + BACNET_COMMUNICATION_ENABLE_DISABLE * enable_disable, + BACNET_CHARACTER_STRING * password) +{ + unsigned len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + int value = 0; + uint32_t value32 = 0; + + /* check for value pointers */ + if (apdu_len) { + /* Tag 0: timeDuration --optional-- */ + if (decode_is_context_tag(&apdu[len], 0)) { + len += decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value_type); + len += decode_unsigned(&apdu[len], len_value_type, &value32); + if (timeDuration) + *timeDuration = (uint16_t) value32; + } else if (timeDuration) + *timeDuration = 0; + /* Tag 1: enable_disable */ + if (!decode_is_context_tag(&apdu[len], 1)) + return -1; + len += decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += decode_enumerated(&apdu[len], len_value_type, &value); + if (enable_disable) + *enable_disable = value; + /* Tag 2: password --optional-- */ + if (len < apdu_len) { + if (!decode_is_context_tag(&apdu[len], 2)) + return -1; + len += decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += + decode_character_string(&apdu[len], len_value_type, + password); + } else if (password) + characterstring_init_ansi(password, NULL); + } + + return (int) len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +int dcc_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, + uint16_t * timeDuration, + BACNET_COMMUNICATION_ENABLE_DISABLE * enable_disable, + BACNET_CHARACTER_STRING * password) +{ + int len = 0; + unsigned offset = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) + return -1; + /* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */ + *invoke_id = apdu[2]; /* invoke id - filled in by net layer */ + if (apdu[3] != SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL) + return -1; + offset = 4; + + if (apdu_len > offset) { + len = dcc_decode_service_request(&apdu[offset], + apdu_len - offset, timeDuration, enable_disable, password); + } + + return len; +} + +void test_DeviceCommunicationControlData(Test * pTest, + uint8_t invoke_id, + uint16_t timeDuration, + BACNET_COMMUNICATION_ENABLE_DISABLE enable_disable, + BACNET_CHARACTER_STRING * password) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + uint8_t test_invoke_id = 0; + uint16_t test_timeDuration = 0; + BACNET_COMMUNICATION_ENABLE_DISABLE test_enable_disable; + BACNET_CHARACTER_STRING test_password; + + len = dcc_encode_apdu(&apdu[0], + invoke_id, timeDuration, enable_disable, password); + ct_test(pTest, len != 0); + apdu_len = len; + + len = dcc_decode_apdu(&apdu[0], + apdu_len, + &test_invoke_id, + &test_timeDuration, &test_enable_disable, &test_password); + ct_test(pTest, len != -1); + ct_test(pTest, test_invoke_id == invoke_id); + ct_test(pTest, test_timeDuration == timeDuration); + ct_test(pTest, test_enable_disable == enable_disable); + ct_test(pTest, characterstring_same(&test_password, password)); +} + +void test_DeviceCommunicationControl(Test * pTest) +{ + uint8_t invoke_id = 128; + uint16_t timeDuration = 0; + BACNET_COMMUNICATION_ENABLE_DISABLE enable_disable; + BACNET_CHARACTER_STRING password; + + timeDuration = 0; + enable_disable = COMMUNICATION_DISABLE_INITIATION; + characterstring_init_ansi(&password, "John 3:16"); + test_DeviceCommunicationControlData(pTest, + invoke_id, timeDuration, enable_disable, &password); + + timeDuration = 12345; + enable_disable = COMMUNICATION_DISABLE; + test_DeviceCommunicationControlData(pTest, + invoke_id, timeDuration, enable_disable, NULL); + + return; +} + +#ifdef TEST_DEVICE_COMMUNICATION_CONTROL +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet DeviceCommunicationControl", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, test_DeviceCommunicationControl); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_DEVICE_COMMUNICATION_CONTROL */ +#endif /* TEST */ diff --git a/bacnet-stack/dcc.h b/bacnet-stack/dcc.h index e69de29b..c4557120 100644 --- a/bacnet-stack/dcc.h +++ b/bacnet-stack/dcc.h @@ -0,0 +1,86 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef DCC_H +#define DCC_H + +#include +#include +#include "bacenum.h" +#include "bacstr.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* return the status */ + BACNET_COMMUNICATION_ENABLE_DISABLE dcc_enable_status(void); + bool dcc_communication_enabled(void); + bool dcc_communication_disabled(void); + bool dcc_communication_initiation_disabled(void); +/* return the time */ + uint32_t dcc_duration_seconds(void); +/* called every second or so. If more than one second, + then seconds should be the number of seconds to tick away */ + void dcc_timer_seconds(uint32_t seconds); +/* setup the communication values */ + bool dcc_set_status_duration(BACNET_COMMUNICATION_ENABLE_DISABLE + status, uint16_t minutes); + +/* encode service */ + int dcc_encode_apdu(uint8_t * apdu, uint8_t invoke_id, uint16_t timeDuration, /* 0=optional */ + BACNET_COMMUNICATION_ENABLE_DISABLE enable_disable, BACNET_CHARACTER_STRING * password); /* NULL=optional */ + +/* decode the service request only */ + int dcc_decode_service_request(uint8_t * apdu, + unsigned apdu_len, + uint16_t * timeDuration, + BACNET_COMMUNICATION_ENABLE_DISABLE * enable_disable, + BACNET_CHARACTER_STRING * password); + +#ifdef TEST +#include "ctest.h" + int dcc_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, + uint16_t * timeDuration, + BACNET_COMMUNICATION_ENABLE_DISABLE * enable_disable, + BACNET_CHARACTER_STRING * password); + + void test_DeviceCommunicationControl(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/demo/dcc/main.c b/bacnet-stack/demo/dcc/main.c index e69de29b..eeb03ea1 100644 --- a/bacnet-stack/demo/dcc/main.c +++ b/bacnet-stack/demo/dcc/main.c @@ -0,0 +1,245 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* command line tool demo for BACnet stack */ +#include +#include +#include +#include +#include /* for time */ +#include +#include "bactext.h" +#include "iam.h" +#include "arf.h" +#include "tsm.h" +#include "address.h" +#include "config.h" +#include "bacdef.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "net.h" +#include "datalink.h" +#include "whois.h" +#include "dcc.h" +/* some demo stuff needed */ +#include "filename.h" +#include "handlers.h" +#include "client.h" +#include "txbuf.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/* global variables used in this file */ +static uint32_t Target_Device_Object_Instance = BACNET_MAX_INSTANCE; +static BACNET_ADDRESS Target_Address; +static uint16_t Communication_Timeout_Minutes = 0; +static BACNET_COMMUNICATION_ENABLE_DISABLE Communication_State = + COMMUNICATION_ENABLE; +static char *Communication_Password = NULL; + +static bool Error_Detected = false; + +static void MyErrorHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, + BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + printf("BACnet Error: %s: %s\r\n", + bactext_error_class_name(error_class), + bactext_error_code_name(error_code)); + Error_Detected = true; +} + +void MyAbortHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + (void) server; + printf("BACnet Abort: %s\r\n", + bactext_abort_reason_name(abort_reason)); + Error_Detected = true; +} + +void MyRejectHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t reject_reason) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + printf("BACnet Reject: %s\r\n", + bactext_reject_reason_name(reject_reason)); + Error_Detected = true; +} + +void MyDeviceCommunicationControlSimpleAckHandler(BACNET_ADDRESS * src, + uint8_t invoke_id) +{ + (void) src; + (void) invoke_id; + printf("DeviceCommunicationControl Acknowledged!\r\n"); +} + +static void Init_Service_Handlers(void) +{ + /* 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); + /* handle communication so we can shutup when asked */ + apdu_set_confirmed_handler + (SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + handler_device_communication_control); + /* handle the ack coming back */ + apdu_set_confirmed_simple_ack_handler + (SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + MyDeviceCommunicationControlSimpleAckHandler); + /* handle any errors coming back */ + apdu_set_error_handler(SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + MyErrorHandler); + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + 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; + uint8_t invoke_id = 0; + bool found = false; + + if (argc < 3) { + /* note: priority 16 and 0 should produce the same end results... */ + printf("Usage: %s device-instance state timeout [password]\r\n" + "Send BACnet DeviceCommunicationControl service to device.\r\n" + "\r\n" + "The device-instance can be 0 to %d.\r\n" + "Possible state values:\r\n" + " 0=enable\r\n" + " 1=disable\r\n" + " 2=disable-initiation\r\n" + "The timeout can be 0 for infinite, or a value in minutes for disable.\r\n" + "The optional password is a character string of 1 to 20 characters.\r\n", + filename_remove_path(argv[0]), BACNET_MAX_INSTANCE - 1); + return 0; + } + /* decode the command line parameters */ + Target_Device_Object_Instance = strtol(argv[1], NULL, 0); + Communication_State = strtol(argv[2], NULL, 0); + Communication_Timeout_Minutes = strtol(argv[3], NULL, 0); + /* optional password */ + if (argc > 4) + Communication_Password = argv[4]; + + if (Target_Device_Object_Instance >= BACNET_MAX_INSTANCE) { + fprintf(stderr, "device-instance=%u - it must be less than %u\r\n", + Target_Device_Object_Instance, BACNET_MAX_INSTANCE); + return 1; + } + + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + address_init(); + Init_Service_Handlers(); + if (!datalink_init(NULL)) + return 1; + /* configure the timeout values */ + last_seconds = time(NULL); + timeout_seconds = (Device_APDU_Timeout() / 1000) * + Device_Number_Of_APDU_Retries(); + /* try to bind with the device */ + Send_WhoIs(Target_Device_Object_Instance, + Target_Device_Object_Instance); + /* loop forever */ + for (;;) { + /* increment timer - exit if timed out */ + current_seconds = time(NULL); + + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + /* at least one second has passed */ + if (current_seconds != last_seconds) + tsm_timer_milliseconds(((current_seconds - + last_seconds) * 1000)); + if (Error_Detected) + break; + /* wait until the device is bound, or timeout and quit */ + found = address_bind_request(Target_Device_Object_Instance, + &max_apdu, &Target_Address); + if (found) { + if (invoke_id == 0) { + invoke_id = + Send_Device_Communication_Control_Request + (Target_Device_Object_Instance, + Communication_Timeout_Minutes, Communication_State, + Communication_Password); + } else if (tsm_invoke_id_free(invoke_id)) + break; + else if (tsm_invoke_id_failed(invoke_id)) { + fprintf(stderr, "\rError: TSM Timeout!\r\n"); + tsm_free_invoke_id(invoke_id); + /* try again or abort? */ + break; + } + } else { + /* increment timer - exit if timed out */ + elapsed_seconds += (current_seconds - last_seconds); + if (elapsed_seconds > timeout_seconds) { + printf("\rError: APDU Timeout!\r\n"); + break; + } + } + /* keep track of time for next check */ + last_seconds = current_seconds; + } + + return 0; +} diff --git a/bacnet-stack/demo/epics/main.c b/bacnet-stack/demo/epics/main.c index e69de29b..70764577 100644 --- a/bacnet-stack/demo/epics/main.c +++ b/bacnet-stack/demo/epics/main.c @@ -0,0 +1,373 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* command line tool that sends a BACnet service, and displays the reply */ +#include +#include +#include +#include +#include /* for time */ +#include +#include "bactext.h" +#include "iam.h" +#include "arf.h" +#include "tsm.h" +#include "address.h" +#include "config.h" +#include "bacdef.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "net.h" +#include "datalink.h" +#include "whois.h" +#include "rp.h" +/* some demo stuff needed */ +#include "filename.h" +#include "handlers.h" +#include "client.h" +#include "txbuf.h" +#include "keylist.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/* global variables used in this file */ +static uint32_t Target_Device_Object_Instance = BACNET_MAX_INSTANCE; +static bool Error_Detected = false; +static BACNET_ADDRESS Target_Address; +/* use this parameter to set a special interface: + "eth0" on Linux, or dotted IP address on Windows */ +static char *Network_Interface = NULL; + +typedef struct BACnet_RP_Service_Data_t { + bool new_data; + BACNET_CONFIRMED_SERVICE_ACK_DATA service_data; + BACNET_READ_PROPERTY_DATA data; +} BACNET_RP_SERVICE_DATA; +static BACNET_RP_SERVICE_DATA Read_Property_Data; + +/* FIXME: keep the object list in here */ +/* static OS_Keylist Object_List; */ + +static void MyErrorHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, + BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + #if 1 + printf("BACnet Error: %s: %s\r\n", + bactext_error_class_name(error_class), + bactext_error_code_name(error_code)); + #else + (void) error_class; + (void) error_code; + #endif + Error_Detected = true; +} + +void MyAbortHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + (void) server; + #if 1 + printf("BACnet Abort: %s\r\n", + bactext_abort_reason_name(abort_reason)); + #else + (void) abort_reason; + #endif + Error_Detected = true; +} + +void MyRejectHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t reject_reason) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + #if 1 + printf("BACnet Reject: %s\r\n", + bactext_reject_reason_name(reject_reason)); + #else + (void) reject_reason; + #endif + Error_Detected = true; +} + +void PrintReadPropertyData(BACNET_READ_PROPERTY_DATA * data) +{ + BACNET_APPLICATION_DATA_VALUE value; /* for decode value data */ + int len = 0; + uint8_t *application_data; + int application_data_len; + bool first_value = true; + bool print_brace = false; + + if (data) { +#if 0 + if (data->array_index == BACNET_ARRAY_ALL) + fprintf(stderr, "%s #%u %s\n", + bactext_object_type_name(data->object_type), + data->object_instance, + bactext_property_name(data->object_property)); + else + fprintf(stderr, "%s #%u %s[%d]\n", + bactext_object_type_name(data->object_type), + data->object_instance, + bactext_property_name(data->object_property), + data->array_index); +#endif + application_data = data->application_data; + application_data_len = data->application_data_len; + /* value? loop until all of the len is gone... */ + for (;;) { + len = bacapp_decode_application_data(application_data, + (uint8_t) application_data_len, &value); + if (first_value && (len < application_data_len)) { + first_value = false; + fprintf(stdout, "{"); + print_brace = true; + } + bacapp_print_value(stdout, &value, data->object_property); + if (len) { + if (len < application_data_len) { + application_data += len; + application_data_len -= len; + /* there's more! */ + fprintf(stdout, ","); + } else + break; + } else + break; + } + if (print_brace) + fprintf(stdout, "}"); + fprintf(stdout, "\r\n"); + } +} + +void MyReadPropertyAckHandler(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; + + (void) src; + len = rp_ack_decode_service_request(service_request, + service_len, &data); + if (len > 0) { + memmove(&Read_Property_Data.service_data, service_data, sizeof(data)); + memmove(&Read_Property_Data.data, &data, sizeof(data)); + Read_Property_Data.new_data = true; + } +} + +static void Init_Service_Handlers(void) +{ + /* 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); + /* handle the data coming back from confirmed requests */ + apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_READ_PROPERTY, + MyReadPropertyAckHandler); + /* handle any errors coming back */ + apdu_set_error_handler(SERVICE_CONFIRMED_READ_PROPERTY, + MyErrorHandler); + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +static uint8_t Read_Properties(uint32_t device_instance) +{ + uint8_t invoke_id = 0; + static unsigned index = 0; + /* list of required (and some optional) properties in the + Device Object + note: you could just loop through + all the properties in all the objects. */ + const int object_props[] = { + PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, + PROP_OBJECT_TYPE, + PROP_SYSTEM_STATUS, + PROP_VENDOR_NAME, + PROP_VENDOR_IDENTIFIER, + PROP_MODEL_NAME, + PROP_FIRMWARE_REVISION, + PROP_APPLICATION_SOFTWARE_VERSION, + PROP_PROTOCOL_VERSION, + PROP_PROTOCOL_CONFORMANCE_CLASS, + PROP_PROTOCOL_SERVICES_SUPPORTED, + PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, + PROP_MAX_APDU_LENGTH_ACCEPTED, + PROP_SEGMENTATION_SUPPORTED, + PROP_LOCAL_TIME, + PROP_LOCAL_DATE, + PROP_UTC_OFFSET, + PROP_DAYLIGHT_SAVINGS_STATUS, + PROP_APDU_SEGMENT_TIMEOUT, + PROP_APDU_TIMEOUT, + PROP_NUMBER_OF_APDU_RETRIES, + PROP_TIME_SYNCHRONIZATION_RECIPIENTS, + PROP_MAX_MASTER, + PROP_MAX_INFO_FRAMES, + PROP_DEVICE_ADDRESS_BINDING, + /* note: PROP_OBJECT_LIST is missing cause + we need to get it with an index method since + the list could be very large */ + /* end of list */ + -1 + }; + + if (object_props[index] != -1) { + printf(" %s: ",bactext_property_name(object_props[index])); + invoke_id = Send_Read_Property_Request(device_instance, + OBJECT_DEVICE, + device_instance, + object_props[index], + BACNET_ARRAY_ALL); + if (invoke_id) { + index++; + } + } + + return invoke_id; +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + 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; + uint8_t invoke_id = 0; + bool found = false; + + /* FIXME: handle multi homed systems - use an argument passed to the datalink_init() */ + + /* print help if not enough arguments */ + if (argc < 2) { + printf + ("%s device-instance\r\n", + filename_remove_path(argv[0])); + return 0; + } + + /* decode the command line parameters */ + Target_Device_Object_Instance = strtol(argv[1], NULL, 0); + if (Target_Device_Object_Instance > BACNET_MAX_INSTANCE) { + fprintf(stderr, "device-instance=%u - it must be less than %u\r\n", + Target_Device_Object_Instance, BACNET_MAX_INSTANCE+1); + return 1; + } + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + address_init(); + Init_Service_Handlers(); + datalink_init(Network_Interface); + /* configure the timeout values */ + last_seconds = time(NULL); + timeout_seconds = (Device_APDU_Timeout() / 1000) * + Device_Number_Of_APDU_Retries(); + /* try to bind with the device */ + Send_WhoIs(Target_Device_Object_Instance, + Target_Device_Object_Instance); + printf("List of Objects in test device:\r\n"); + printf("{\r\n"); + /* loop forever */ + for (;;) { + /* increment timer - exit if timed out */ + current_seconds = time(NULL); + + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + /* at least one second has passed */ + if (current_seconds != last_seconds) + tsm_timer_milliseconds(((current_seconds - + last_seconds) * 1000)); + /* wait until the device is bound, or timeout and quit */ + found = address_bind_request(Target_Device_Object_Instance, + &max_apdu, &Target_Address); + if (found) { + if (invoke_id == 0) { + invoke_id = Read_Properties(Target_Device_Object_Instance); + if (invoke_id == 0) { + break; + } + } else if ((Read_Property_Data.new_data) && + (invoke_id == Read_Property_Data.service_data.invoke_id)) { + Read_Property_Data.new_data = false; + PrintReadPropertyData(&Read_Property_Data.data); + } else if (tsm_invoke_id_free(invoke_id)) { + invoke_id = 0; + } + else if (tsm_invoke_id_failed(invoke_id)) { + fprintf(stderr, "\rError: TSM Timeout!\r\n"); + tsm_free_invoke_id(invoke_id); + invoke_id = 0; + } else if (Error_Detected) { + invoke_id = 0; + } + } else { + /* increment timer - exit if timed out */ + elapsed_seconds += (current_seconds - last_seconds); + if (elapsed_seconds > timeout_seconds) { + printf("\rError: APDU Timeout!\r\n"); + break; + } + } + /* keep track of time for next check */ + last_seconds = current_seconds; + } + printf("}\r\n"); + + return 0; +} diff --git a/bacnet-stack/demo/handler/client.h b/bacnet-stack/demo/handler/client.h index e69de29b..459efee2 100644 --- a/bacnet-stack/demo/handler/client.h +++ b/bacnet-stack/demo/handler/client.h @@ -0,0 +1,90 @@ +/************************************************************************** +* +* Copyright (C) 2006 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 CLIENT_H +#define CLIENT_H + +#include +#include +#include +#include "bacdef.h" +#include "apdu.h" +#include "bacapp.h" +#include "bacenum.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* unconfirmed requests */ + void Send_WhoIs(int32_t low_limit, int32_t high_limit); + + void Send_WhoHas_Object(int32_t low_limit, + int32_t high_limit, + BACNET_OBJECT_TYPE object_type, uint32_t object_instance); + + void Send_WhoHas_Name(int32_t low_limit, + int32_t high_limit, char *object_name); + + void Send_I_Have(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, char *object_name); + +/* returns the invoke ID for confirmed request, or 0 if failed */ + uint8_t Send_Read_Property_Request(uint32_t device_id, /* destination device */ + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, int32_t array_index); + +/* returns the invoke ID for confirmed request, or 0 if failed */ + uint8_t Send_Write_Property_Request(uint32_t device_id, /* destination device */ + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + BACNET_APPLICATION_DATA_VALUE * object_value, + uint8_t priority, int32_t array_index); + +/* returns the invoke ID for confirmed request, or 0 if failed */ + uint8_t Send_Reinitialize_Device_Request(uint32_t device_id, + BACNET_REINITIALIZED_STATE state, char *password); + +/* returns the invoke ID for confirmed request, or 0 if failed */ + uint8_t Send_Device_Communication_Control_Request(uint32_t device_id, uint16_t timeDuration, /* 0=optional */ + BACNET_COMMUNICATION_ENABLE_DISABLE state, char *password); /* NULL=optional */ + + void Send_TimeSync(BACNET_DATE * bdate, BACNET_TIME * btime); + void Send_TimeSyncUTC(BACNET_DATE * bdate, BACNET_TIME * btime); + + uint8_t Send_Atomic_Read_File_Stream(uint32_t device_id, + uint32_t file_instance, int fileStartPosition, + unsigned requestedOctetCount); + uint8_t Send_Atomic_Write_File_Stream(uint32_t device_id, + uint32_t file_instance, + int fileStartPosition, BACNET_OCTET_STRING * fileData); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/demo/handler/h_arf.c b/bacnet-stack/demo/handler/h_arf.c index e69de29b..72ac22a6 100644 --- a/bacnet-stack/demo/handler/h_arf.c +++ b/bacnet-stack/demo/handler/h_arf.c @@ -0,0 +1,192 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "apdu.h" +#include "npdu.h" +#include "abort.h" +#include "arf.h" +/* demo objects */ +#include "device.h" +#include "ai.h" +#include "ao.h" +#include "bacfile.h" + +/* +from BACnet SSPC-135-2004 + +14. FILE ACCESS SERVICES + +This clause defines the set of services used to access and +manipulate files contained in BACnet devices. The concept of files +is used here as a network-visible representation for a collection +of octets of arbitrary length and meaning. This is an abstract +concept only and does not imply the use of disk, tape or other +mass storage devices in the server devices. These services may +be used to access vendor-defined files as well as specific +files defined in the BACnet protocol standard. +Every file that is accessible by File Access Services shall +have a corresponding File object in the BACnet device. This File +object is used to identify the particular file by name. In addition, +the File object provides access to "header information," such +as the file's total size, creation date, and type. File Access +Services may model files in two ways: as a continuous stream of +octets or as a contiguous sequence of numbered records. +The File Access Services provide atomic read and write operations. +In this context "atomic" means that during the execution +of a read or write operation, no other AtomicReadFile or +AtomicWriteFile operations are allowed for the same file. +Synchronization of these services with internal operations +of the BACnet device is a local matter and is not defined by this +standard. + +14.1 AtomicReadFile Service + +14.1.5 Service Procedure + +The responding BACnet-user shall first verify the validity +of the 'File Identifier' parameter and return a 'Result(-)' response +with the appropriate error class and code if the File object +is unknown, if there is currently another AtomicReadFile or +AtomicWriteFile service in progress, or if the File object is +currently inaccessible for another reason. If the 'File Start +Position' parameter or the 'File Start Record' parameter is +either less than 0 or exceeds the actual file size, then the appropriate +error is returned in a 'Result(-)' response. If not, then the +responding BACnet-user shall read the number of octets specified by +'Requested Octet Count' or the number of records specified by +'Requested Record Count'. If the number of remaining octets or +records is less than the requested amount, then the length of +the 'File Data' returned or 'Returned Record Count' shall indicate +the actual number read. If the returned response contains the +last octet or record of the file, then the 'End Of File' parameter +shall be TRUE, otherwise FALSE. +*/ + +void handler_atomic_read_file(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data) +{ + BACNET_ATOMIC_READ_FILE_DATA data; + int len = 0; + int pdu_len = 0; + bool error = false; + int bytes_sent = 0; + BACNET_NPDU_DATA npdu_data; + BACNET_ADDRESS my_address; + BACNET_ERROR_CLASS error_class = ERROR_CLASS_OBJECT; + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + +#if PRINT_ENABLED + fprintf(stderr, "Received Atomic-Read-File Request!\n"); +#endif + len = arf_decode_service_request(service_request, service_len, &data); + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], src, + &my_address, &npdu_data); + /* bad decoding - send an abort */ + if (len < 0) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, ABORT_REASON_OTHER, true); +#if PRINT_ENABLED + fprintf(stderr, "Bad Encoding. Sending Abort!\n"); +#endif + } else if (service_data->segmented_message) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true); +#if PRINT_ENABLED + fprintf(stderr, "Segmented Message. Sending Abort!\n"); +#endif + } else if (data.object_type == OBJECT_FILE) { + if (!bacfile_valid_instance(data.object_instance)) { + error = true; + } else if (data.access == FILE_STREAM_ACCESS) { + if (data.type.stream.requestedOctetCount < + octetstring_capacity(&data.fileData)) { + if (bacfile_read_data(&data)) { +#if PRINT_ENABLED + fprintf(stderr, "ARF: Stream offset %d, %d octets.\n", + data.type.stream.fileStartPosition, + data.type.stream.requestedOctetCount); +#endif + len = + arf_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); + } else { + error = true; + error_class = ERROR_CLASS_OBJECT; + error_code = ERROR_CODE_FILE_ACCESS_DENIED; + } + } else { + len = + abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true); +#if PRINT_ENABLED + fprintf(stderr, "Too Big To Send (%d >= %d). Sending Abort!\n", + data.type.stream.requestedOctetCount, + octetstring_capacity(&data.fileData)); +#endif + } + } else { + error = true; + error_class = ERROR_CLASS_SERVICES; + error_code = ERROR_CODE_INVALID_FILE_ACCESS_METHOD; +#if PRINT_ENABLED + fprintf(stderr, "Record Access Requested. Sending Error!\n"); +#endif + } + } else { + error = true; + error_class = ERROR_CLASS_SERVICES; + error_code = ERROR_CODE_FILE_ACCESS_DENIED; + } + if (error) { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_ATOMIC_READ_FILE, error_class, error_code); + } + pdu_len += len; + bytes_sent = datalink_send_pdu(src, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) { + fprintf(stderr, "Failed to send PDU (%s)!\n", strerror(errno)); + } +#endif + + return; +} diff --git a/bacnet-stack/demo/handler/h_arf_a.c b/bacnet-stack/demo/handler/h_arf_a.c index e69de29b..1562c0e5 100644 --- a/bacnet-stack/demo/handler/h_arf_a.c +++ b/bacnet-stack/demo/handler/h_arf_a.c @@ -0,0 +1,93 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include "config.h" +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "address.h" +#include "tsm.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "datalink.h" +#include "arf.h" +#include "bacfile.h" +/* some demo stuff needed */ +#include "handlers.h" +#include "txbuf.h" + +/* We performed an AtomicReadFile Request, */ +/* and here is the data from the server */ +/* Note: it does not have to be the same file=instance */ +/* that someone can read from us. It is common to */ +/* use the description as the file name. */ +#if BACFILE +void handler_atomic_read_file_ack(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_ACK_DATA * service_data) +{ + int len = 0; + BACNET_ATOMIC_READ_FILE_DATA data; + FILE *pFile = NULL; + char *pFilename = NULL; + uint32_t instance = 0; + + (void) src; + /* get the file instance from the tsm data before freeing it */ + instance = bacfile_instance_from_tsm(service_data->invoke_id); + len = arf_ack_decode_service_request(service_request, + service_len, &data); +#if PRINT_ENABLED + fprintf(stderr, "Received Read-File Ack!\n"); +#endif + if ((len > 0) && (instance <= BACNET_MAX_INSTANCE)) { + /* write the data received to the file specified */ + if (data.access == FILE_STREAM_ACCESS) { + pFilename = bacfile_name(instance); + if (pFilename) { + pFile = fopen(pFilename, "rb"); + if (pFile) { + (void) fseek(pFile, + data.type.stream.fileStartPosition, SEEK_SET); + if (fwrite(octetstring_value(&data.fileData), + octetstring_length(&data.fileData), 1, + pFile) != 1) { +#if PRINT_ENABLED + fprintf(stderr, "Failed to write to %s (%u)!\n", + pFilename, instance); +#endif + } + fclose(pFile); + } + } + } else if (data.access == FILE_RECORD_ACCESS) { + /* FIXME: add handling for Record Access */ + } + } +} +#endif diff --git a/bacnet-stack/demo/handler/h_awf.c b/bacnet-stack/demo/handler/h_awf.c index e69de29b..2124b3d4 100644 --- a/bacnet-stack/demo/handler/h_awf.c +++ b/bacnet-stack/demo/handler/h_awf.c @@ -0,0 +1,157 @@ +/************************************************************************** +* +* Copyright (C) 2007 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacstr.h" +#include "bacerror.h" +#include "bacdcode.h" +#include "apdu.h" +#include "npdu.h" +#include "abort.h" +#include "awf.h" +/* demo objects */ +#include "device.h" +#include "bacfile.h" + +/* +from BACnet SSPC-135-2004 + +14. FILE ACCESS SERVICES + +This clause defines the set of services used to access and +manipulate files contained in BACnet devices. The concept of files +is used here as a network-visible representation for a collection +of octets of arbitrary length and meaning. This is an abstract +concept only and does not imply the use of disk, tape or other +mass storage devices in the server devices. These services may +be used to access vendor-defined files as well as specific +files defined in the BACnet protocol standard. +Every file that is accessible by File Access Services shall +have a corresponding File object in the BACnet device. This File +object is used to identify the particular file by name. In addition, +the File object provides access to "header information," such +as the file's total size, creation date, and type. File Access +Services may model files in two ways: as a continuous stream of +octets or as a contiguous sequence of numbered records. +The File Access Services provide atomic read and write operations. +In this context "atomic" means that during the execution +of a read or write operation, no other AtomicReadFile or +AtomicWriteFile operations are allowed for the same file. +Synchronization of these services with internal operations +of the BACnet device is a local matter and is not defined by this +standard. +*/ + +void handler_atomic_write_file(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data) +{ + BACNET_ATOMIC_WRITE_FILE_DATA data; + int len = 0; + int pdu_len = 0; + bool error = false; + int bytes_sent = 0; + BACNET_NPDU_DATA npdu_data; + BACNET_ADDRESS my_address; + BACNET_ERROR_CLASS error_class = ERROR_CLASS_OBJECT; + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + +#if PRINT_ENABLED + fprintf(stderr, "Received AtomicWriteFile Request!\n"); +#endif + len = awf_decode_service_request(service_request, service_len, &data); + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], src, + &my_address, &npdu_data); + /* bad decoding - send an abort */ + if (len < 0) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, ABORT_REASON_OTHER, true); +#if PRINT_ENABLED + fprintf(stderr, "Bad Encoding. Sending Abort!\n"); +#endif + } else if (service_data->segmented_message) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true); +#if PRINT_ENABLED + fprintf(stderr, "Segmented Message. Sending Abort!\n"); +#endif + } else if (data.object_type == OBJECT_FILE) { + if (!bacfile_valid_instance(data.object_instance)) { + error = true; + } else if (data.access == FILE_STREAM_ACCESS) { + if (bacfile_write_stream_data(&data)) { +#if PRINT_ENABLED + fprintf(stderr, "AWF: Stream offset %d, %d bytes\n", + data.type.stream.fileStartPosition, + octetstring_length(&data.fileData)); +#endif + len = + awf_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); + } else { + error = true; + error_class = ERROR_CLASS_OBJECT; + error_code = ERROR_CODE_FILE_ACCESS_DENIED; + } + } else { + error = true; + error_class = ERROR_CLASS_SERVICES; + error_code = ERROR_CODE_INVALID_FILE_ACCESS_METHOD; +#if PRINT_ENABLED + fprintf(stderr, "Record Access Requested. Sending Error!\n"); +#endif + } + } else { + error = true; + error_class = ERROR_CLASS_SERVICES; + error_code = ERROR_CODE_FILE_ACCESS_DENIED; + } + if (error) { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_ATOMIC_READ_FILE, error_class, error_code); + } + pdu_len += len; + bytes_sent = datalink_send_pdu(src, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) { + fprintf(stderr, "Failed to send PDU (%s)!\n", strerror(errno)); + } +#endif + + return; +} diff --git a/bacnet-stack/demo/handler/h_dcc.c b/bacnet-stack/demo/handler/h_dcc.c index e69de29b..c671aa86 100644 --- a/bacnet-stack/demo/handler/h_dcc.c +++ b/bacnet-stack/demo/handler/h_dcc.c @@ -0,0 +1,128 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "bacerror.h" +#include "apdu.h" +#include "npdu.h" +#include "abort.h" +#include "reject.h" +#include "dcc.h" + +static char *My_Password = "filister"; + +void handler_device_communication_control(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data) +{ + uint16_t timeDuration = 0; + BACNET_COMMUNICATION_ENABLE_DISABLE state = COMMUNICATION_ENABLE; + BACNET_CHARACTER_STRING password; + int len = 0; + int pdu_len = 0; + int bytes_sent = 0; + BACNET_NPDU_DATA npdu_data; + BACNET_ADDRESS my_address; + + /* decode the service request only */ + len = dcc_decode_service_request(service_request, + service_len, &timeDuration, &state, &password); + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], src, + &my_address, &npdu_data); +#if PRINT_ENABLED + fprintf(stderr, "DeviceCommunicationControl!\n"); + if (len > 0) + fprintf(stderr, "DeviceCommunicationControl: " + "timeout=%u state=%u password=%s\n", + (unsigned) timeDuration, + (unsigned) state, characterstring_value(&password)); +#endif + /* bad decoding or something we didn't understand - send an abort */ + if (len < 0) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, ABORT_REASON_OTHER, true); +#if PRINT_ENABLED + fprintf(stderr, "DeviceCommunicationControl: " + "Sending Abort - could not decode.\n"); +#endif + } else if (service_data->segmented_message) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true); +#if PRINT_ENABLED + fprintf(stderr, "DeviceCommunicationControl: " + "Sending Abort - segmented message.\n"); +#endif + } else if (state >= MAX_BACNET_COMMUNICATION_ENABLE_DISABLE) { + len = reject_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, REJECT_REASON_UNDEFINED_ENUMERATION); +#if PRINT_ENABLED + fprintf(stderr, "DeviceCommunicationControl: " + "Sending Reject - undefined enumeration\n"); +#endif + } else { + if (characterstring_ansi_same(&password, My_Password)) { + len = encode_simple_ack(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL); +#if PRINT_ENABLED + fprintf(stderr, "DeviceCommunicationControl: " + "Sending Simple Ack!\n"); +#endif + dcc_set_status_duration(state, timeDuration); + } else { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + ERROR_CLASS_SERVICES, ERROR_CODE_PASSWORD_FAILURE); +#if PRINT_ENABLED + fprintf(stderr, + "DeviceCommunicationControl: " + "Sending Error - password failure.\n"); +#endif + } + } + pdu_len += len; + bytes_sent = datalink_send_pdu(src, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, "DeviceCommunicationControl: " + "Failed to send PDU (%s)!\n", strerror(errno)); +#endif + + return; +} diff --git a/bacnet-stack/demo/handler/h_iam.c b/bacnet-stack/demo/handler/h_iam.c index e69de29b..58737886 100644 --- a/bacnet-stack/demo/handler/h_iam.c +++ b/bacnet-stack/demo/handler/h_iam.c @@ -0,0 +1,80 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "iam.h" +#include "address.h" + +void handler_i_am_add(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src) +{ + int len = 0; + uint32_t device_id = 0; + unsigned max_apdu = 0; + int segmentation = 0; + uint16_t vendor_id = 0; + + (void) service_len; + len = iam_decode_service_request(service_request, + &device_id, &max_apdu, &segmentation, &vendor_id); +#if PRINT_ENABLED + fprintf(stderr, "Received I-Am Request"); +#endif + if (len != -1) { +#if PRINT_ENABLED + fprintf(stderr, " from %u!\n", device_id); +#endif + address_add(device_id, max_apdu, src); + } else { +#if PRINT_ENABLED + fprintf(stderr, "!\n"); +#endif + } + + return; +} + +void handler_i_am_bind(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src) +{ + int len = 0; + uint32_t device_id = 0; + unsigned max_apdu = 0; + int segmentation = 0; + uint16_t vendor_id = 0; + + (void) service_len; + len = iam_decode_service_request(service_request, + &device_id, &max_apdu, &segmentation, &vendor_id); + /* only add address if requested to bind */ + address_add_binding(device_id, max_apdu, src); + + return; +} diff --git a/bacnet-stack/demo/handler/h_ihave.c b/bacnet-stack/demo/handler/h_ihave.c index e69de29b..d1d39a71 100644 --- a/bacnet-stack/demo/handler/h_ihave.c +++ b/bacnet-stack/demo/handler/h_ihave.c @@ -0,0 +1,60 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "bactext.h" +#include "ihave.h" + +void handler_i_have(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src) +{ + int len = 0; + BACNET_I_HAVE_DATA data; + + (void) service_len; + (void) src; + len = ihave_decode_service_request(service_request, + service_len, &data); + if (len != -1) { +#if PRINT_ENABLED + fprintf(stderr, "I-Have: %s %d from %s %u!\r\n", + bactext_object_type_name(data.object_id.type), + data.object_id.instance, + bactext_object_type_name(data.device_id.type), + data.device_id.instance); +#endif + } else { +#if PRINT_ENABLED + fprintf(stderr, "I-Have: received, but unable to decode!\n"); +#endif + } + + return; +} diff --git a/bacnet-stack/demo/handler/h_rd.c b/bacnet-stack/demo/handler/h_rd.c index e69de29b..3b169adc 100644 --- a/bacnet-stack/demo/handler/h_rd.c +++ b/bacnet-stack/demo/handler/h_rd.c @@ -0,0 +1,130 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "bacerror.h" +#include "apdu.h" +#include "npdu.h" +#include "abort.h" +#include "reject.h" +#include "rd.h" + +static char *Password = "Jesus"; +static BACNET_CHARACTER_STRING My_Password; + +void handler_reinitialize_device(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data) +{ + BACNET_REINITIALIZED_STATE state; + BACNET_CHARACTER_STRING their_password; + int len = 0; + int pdu_len = 0; + BACNET_NPDU_DATA npdu_data; + int bytes_sent = 0; + BACNET_ADDRESS my_address; + + /* decode the service request only */ + len = rd_decode_service_request(service_request, + service_len, &state, &their_password); + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], src, + &my_address, &npdu_data); +#if PRINT_ENABLED + fprintf(stderr, "ReinitializeDevice!\n"); + if (len > 0) + fprintf(stderr, "ReinitializeDevice: state=%u password=%s\n", + (unsigned) state, characterstring_value(&their_password)); + else + fprintf(stderr, "ReinitializeDevice: Unable to decode request!\n"); +#endif + /* bad decoding or something we didn't understand - send an abort */ + if (len < 0) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, ABORT_REASON_OTHER, true); +#if PRINT_ENABLED + fprintf(stderr, + "ReinitializeDevice: Sending Abort - could not decode.\n"); +#endif + } else if (service_data->segmented_message) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true); +#if PRINT_ENABLED + fprintf(stderr, + "ReinitializeDevice: Sending Abort - segmented message.\n"); +#endif + } else if (state >= MAX_BACNET_REINITIALIZED_STATE) { + len = reject_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, REJECT_REASON_UNDEFINED_ENUMERATION); +#if PRINT_ENABLED + fprintf(stderr, + "ReinitializeDevice: Sending Reject - undefined enumeration\n"); +#endif + } else { + characterstring_init_ansi(&My_Password, Password); + if (characterstring_same(&their_password, &My_Password)) { + len = encode_simple_ack(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_REINITIALIZE_DEVICE); +#if PRINT_ENABLED + fprintf(stderr, "ReinitializeDevice: Sending Simple Ack!\n"); +#endif + /* FIXME: now you can reboot, restart, quit, or something clever */ + /* Note: you can use a mix of state and password to do specific stuff */ + /* Note: if you don't do something clever like actually restart, + you probably should clear any DCC status and timeouts */ + } else { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_REINITIALIZE_DEVICE, + ERROR_CLASS_SERVICES, ERROR_CODE_PASSWORD_FAILURE); +#if PRINT_ENABLED + fprintf(stderr, + "ReinitializeDevice: Sending Error - password failure.\n"); +#endif + } + } + pdu_len += len; + bytes_sent = datalink_send_pdu(src, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, "ReinitializeDevice: Failed to send PDU (%s)!\n", + strerror(errno)); +#endif + + return; +} diff --git a/bacnet-stack/demo/handler/h_rp.c b/bacnet-stack/demo/handler/h_rp.c index e69de29b..8662b4ef 100644 --- a/bacnet-stack/demo/handler/h_rp.c +++ b/bacnet-stack/demo/handler/h_rp.c @@ -0,0 +1,368 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "apdu.h" +#include "npdu.h" +#include "abort.h" +#include "rp.h" +/* demo objects */ +#include "device.h" +#include "ai.h" +#include "ao.h" +#include "av.h" +#include "bi.h" +#include "bo.h" +#include "bv.h" +#include "lc.h" +#include "lsp.h" +#include "mso.h" +#if BACFILE +#include "bacfile.h" +#endif + +static uint8_t Temp_Buf[MAX_APDU] = { 0 }; + +void handler_read_property(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data) +{ + BACNET_READ_PROPERTY_DATA data; + int len = 0; + int pdu_len = 0; + BACNET_NPDU_DATA npdu_data; + bool error = false; + int bytes_sent = 0; + BACNET_ERROR_CLASS error_class = ERROR_CLASS_OBJECT; + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + BACNET_ADDRESS my_address; + + len = rp_decode_service_request(service_request, service_len, &data); + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], src, + &my_address, &npdu_data); +#if PRINT_ENABLED + if (len <= 0) + fprintf(stderr, "Unable to decode Read-Property Request!\n"); +#endif + if (len < 0) { + /* bad decoding - send an abort */ + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, ABORT_REASON_OTHER, true); +#if PRINT_ENABLED + fprintf(stderr, "Sending Abort!\n"); +#endif + } else if (service_data->segmented_message) { + /* we don't support segmentation - send an abort */ + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true); +#if PRINT_ENABLED + fprintf(stderr, "Sending Abort!\n"); +#endif + } else { + /* most cases will be error */ + error = true; + switch (data.object_type) { + case OBJECT_DEVICE: + /* FIXME: probably need a length limitation sent with encode */ + if (data.object_instance == Device_Object_Instance_Number()) { + len = Device_Encode_Property_APDU(&Temp_Buf[0], + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Read Property Ack for Device!\n"); +#endif + error = false; + } + } + break; + case OBJECT_ANALOG_INPUT: + if (Analog_Input_Valid_Instance(data.object_instance)) { + len = Analog_Input_Encode_Property_APDU(&Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); +#if PRINT_ENABLED + fprintf(stderr, "Sending Read Property Ack for AI!\n"); +#endif + error = false; + } + } + break; + case OBJECT_BINARY_INPUT: + if (Binary_Input_Valid_Instance(data.object_instance)) { + len = Binary_Input_Encode_Property_APDU(&Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); +#if PRINT_ENABLED + fprintf(stderr, "Sending Read Property Ack for BI!\n"); +#endif + error = false; + } + } + break; + case OBJECT_BINARY_OUTPUT: + if (Binary_Output_Valid_Instance(data.object_instance)) { + len = Binary_Output_Encode_Property_APDU(&Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); +#if PRINT_ENABLED + fprintf(stderr, "Sending Read Property Ack for BO!\n"); +#endif + error = false; + } + } + break; + case OBJECT_BINARY_VALUE: + if (Binary_Value_Valid_Instance(data.object_instance)) { + len = Binary_Value_Encode_Property_APDU(&Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); +#if PRINT_ENABLED + fprintf(stderr, "Sending Read Property Ack for BV!\n"); +#endif + error = false; + } + } + break; + case OBJECT_ANALOG_OUTPUT: + if (Analog_Output_Valid_Instance(data.object_instance)) { + len = Analog_Output_Encode_Property_APDU(&Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); +#if PRINT_ENABLED + fprintf(stderr, "Sending Read Property Ack for AO!\n"); +#endif + error = false; + } + } + break; + case OBJECT_ANALOG_VALUE: + if (Analog_Value_Valid_Instance(data.object_instance)) { + len = Analog_Value_Encode_Property_APDU(&Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); +#if PRINT_ENABLED + fprintf(stderr, "Sending Read Property Ack for AV!\n"); +#endif + error = false; + } + } + break; + case OBJECT_LIFE_SAFETY_POINT: + if (Life_Safety_Point_Valid_Instance(data.object_instance)) { + len = Life_Safety_Point_Encode_Property_APDU(&Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Read Property Ack for LSP!\n"); +#endif + error = false; + } + } + break; + case OBJECT_LOAD_CONTROL: + if (Load_Control_Valid_Instance(data.object_instance)) { + len = Load_Control_Encode_Property_APDU(&Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Read Property Ack for Load Control!\n"); +#endif + error = false; + } + } + break; + case OBJECT_MULTI_STATE_OUTPUT: + if (Multistate_Output_Valid_Instance(data.object_instance)) { + len = Multistate_Output_Encode_Property_APDU(&Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Read Property Ack for MSO!\n"); +#endif + error = false; + } + } + break; +#if BACFILE + case OBJECT_FILE: + if (bacfile_valid_instance(data.object_instance)) { + len = bacfile_encode_property_apdu(&Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Read Property Ack for File!\n"); +#endif + error = false; + } + } + break; +#endif /* BACFILE */ + default: + break; + } + } + if (error) { + switch (len) { + /* BACnet APDU too small to fit data, so proper response is Abort */ + case -2: + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true); + break; + case -1: + default: + len = bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_READ_PROPERTY, error_class, error_code); + break; + } +#if PRINT_ENABLED + fprintf(stderr, "Sending Read Property Error!\n"); +#endif + } + pdu_len += len; + bytes_sent = datalink_send_pdu(src, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, "Failed to send PDU (%s)!\n", strerror(errno)); +#endif + + return; +} diff --git a/bacnet-stack/demo/handler/h_rp_a.c b/bacnet-stack/demo/handler/h_rp_a.c index e69de29b..617229ef 100644 --- a/bacnet-stack/demo/handler/h_rp_a.c +++ b/bacnet-stack/demo/handler/h_rp_a.c @@ -0,0 +1,114 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include "config.h" +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "address.h" +#include "tsm.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "datalink.h" +#include "bactext.h" +#include "rp.h" +/* some demo stuff needed */ +#include "handlers.h" +#include "txbuf.h" + +/* for debugging... */ +static void PrintReadPropertyData(BACNET_READ_PROPERTY_DATA * data) +{ + BACNET_APPLICATION_DATA_VALUE value; /* for decode value data */ + int len = 0; + uint8_t *application_data; + int application_data_len; + bool first_value = true; + bool print_brace = false; + + if (data) { +#if 0 + if (data->array_index == BACNET_ARRAY_ALL) + fprintf(stderr, "%s #%u %s\n", + bactext_object_type_name(data->object_type), + data->object_instance, + bactext_property_name(data->object_property)); + else + fprintf(stderr, "%s #%u %s[%d]\n", + bactext_object_type_name(data->object_type), + data->object_instance, + bactext_property_name(data->object_property), + data->array_index); +#endif + application_data = data->application_data; + application_data_len = data->application_data_len; + /* FIXME: what if application_data_len is bigger than 255? */ + /* value? need to loop until all of the len is gone... */ + for (;;) { + len = bacapp_decode_application_data(application_data, + (uint8_t) application_data_len, &value); + if (first_value && (len < application_data_len)) { + first_value = false; + fprintf(stdout, "{"); + print_brace = true; + } + bacapp_print_value(stdout, &value, data->object_property); + if (len) { + if (len < application_data_len) { + application_data += len; + application_data_len -= len; + /* there's more! */ + fprintf(stdout, ","); + } else + break; + } else + break; + } + if (print_brace) + fprintf(stdout, "}"); + fprintf(stdout, "\r\n"); + } +} + +void handler_read_property_ack(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; + + (void) src; + (void) service_data; /* we could use these... */ + len = rp_ack_decode_service_request(service_request, + service_len, &data); +#if 0 + fprintf(stderr, "Received Read-Property Ack!\n"); +#endif + if (len > 0) + PrintReadPropertyData(&data); +} diff --git a/bacnet-stack/demo/handler/h_ts.c b/bacnet-stack/demo/handler/h_ts.c index e69de29b..b8dab7de 100644 --- a/bacnet-stack/demo/handler/h_ts.c +++ b/bacnet-stack/demo/handler/h_ts.c @@ -0,0 +1,89 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "timesync.h" + +#if PRINT_ENABLED +static void show_bacnet_date_time(BACNET_DATE * bdate, BACNET_TIME * btime) +{ + /* show the date received */ + fprintf(stderr, "%u", (unsigned) bdate->year); + fprintf(stderr, "/%u", (unsigned) bdate->month); + fprintf(stderr, "/%u", (unsigned) bdate->day); + /* show the time received */ + fprintf(stderr, " %02u", (unsigned) btime->hour); + fprintf(stderr, ":%02u", (unsigned) btime->min); + fprintf(stderr, ":%02u", (unsigned) btime->sec); + fprintf(stderr, ".%02u", (unsigned) btime->hundredths); + fprintf(stderr, "\r\n"); +} +#endif + +void handler_timesync(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src) +{ + int len = 0; + BACNET_DATE bdate; + BACNET_TIME btime; + + (void) src; + (void) service_len; + len = timesync_decode_service_request(service_request, + service_len, &bdate, &btime); +#if PRINT_ENABLED + fprintf(stderr, "Received TimeSyncronization Request\r\n"); + show_bacnet_date_time(&bdate, &btime); +#endif + /* FIXME: set the time? */ + + return; +} + +void handler_timesync_utc(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src) +{ + int len = 0; + BACNET_DATE bdate; + BACNET_TIME btime; + + (void) src; + (void) service_len; + len = timesync_decode_service_request(service_request, + service_len, &bdate, &btime); +#if PRINT_ENABLED + fprintf(stderr, "Received TimeSyncronization Request\r\n"); + show_bacnet_date_time(&bdate, &btime); +#endif + /* FIXME: set the time? */ + + return; +} + diff --git a/bacnet-stack/demo/handler/h_whohas.c b/bacnet-stack/demo/handler/h_whohas.c index e69de29b..0352aa0c 100644 --- a/bacnet-stack/demo/handler/h_whohas.c +++ b/bacnet-stack/demo/handler/h_whohas.c @@ -0,0 +1,85 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "whohas.h" +#include "device.h" +#include "client.h" + +void handler_who_has(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src) +{ + int len = 0; + BACNET_WHO_HAS_DATA data; + bool directed_to_me = false; + int object_type = 0; + uint32_t object_instance = 0; + char *object_name = NULL; + bool found = false; + + (void) src; + len = whohas_decode_service_request(service_request, + service_len, &data); + if (len > 0) { + if ((data.low_limit == -1) || (data.high_limit == -1)) + directed_to_me = true; + else if ((Device_Object_Instance_Number() >= + (uint32_t) data.low_limit) + && (Device_Object_Instance_Number() <= + (uint32_t) data.high_limit)) + directed_to_me = true; + if (directed_to_me) { + /* do we have such an object? If so, send an I-Have. + note: we should have only 1 of such an object */ + if (data.object_name) { + /* valid name in my device? */ + object_name = characterstring_value(&data.object.name); + found = Device_Valid_Object_Name(object_name, + &object_type, &object_instance); + if (found) + Send_I_Have(Device_Object_Instance_Number(), + object_type, object_instance, object_name); + } else { + /* valid object in my device? */ + object_name = + Device_Valid_Object_Id(data.object.identifier.type, + data.object.identifier.instance); + if (object_name) + Send_I_Have(Device_Object_Instance_Number(), + data.object.identifier.type, + data.object.identifier.instance, object_name); + } + } + } + + return; +} diff --git a/bacnet-stack/demo/handler/h_whois.c b/bacnet-stack/demo/handler/h_whois.c index e69de29b..477cc529 100644 --- a/bacnet-stack/demo/handler/h_whois.c +++ b/bacnet-stack/demo/handler/h_whois.c @@ -0,0 +1,66 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "whois.h" +#include "iam.h" +#include "device.h" +#include "client.h" +#include "txbuf.h" + +void handler_who_is(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src) +{ + int len = 0; + int32_t low_limit = 0; + int32_t high_limit = 0; + + (void) src; + len = whois_decode_service_request(service_request, + service_len, &low_limit, &high_limit); + /* in our simple system, we just set a flag and let the main loop + send the I-Am request. */ + if (len == 0) + iam_send(&Handler_Transmit_Buffer[0]); + else if (len != -1) { + /* is my device id within the limits? */ + if (((Device_Object_Instance_Number() >= (uint32_t) low_limit) && + (Device_Object_Instance_Number() <= (uint32_t) high_limit)) + || + /* BACnet wildcard is the max instance number - everyone responds */ + ((BACNET_MAX_INSTANCE >= (uint32_t) low_limit) && + (BACNET_MAX_INSTANCE <= (uint32_t) high_limit))) + iam_send(&Handler_Transmit_Buffer[0]); + } + + return; +} diff --git a/bacnet-stack/demo/handler/h_wp.c b/bacnet-stack/demo/handler/h_wp.c index e69de29b..2be77f93 100644 --- a/bacnet-stack/demo/handler/h_wp.c +++ b/bacnet-stack/demo/handler/h_wp.c @@ -0,0 +1,333 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "apdu.h" +#include "npdu.h" +#include "abort.h" +#include "wp.h" +/* demo objects */ +#include "device.h" +#include "ai.h" +#include "ao.h" +#include "av.h" +#include "bi.h" +#include "bo.h" +#include "bv.h" +#include "lc.h" +#include "lsp.h" +#include "mso.h" +#if BACFILE +#include "bacfile.h" +#endif + +void handler_write_property(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data) +{ + BACNET_WRITE_PROPERTY_DATA wp_data; + int len = 0; + int pdu_len = 0; + BACNET_NPDU_DATA npdu_data; + BACNET_ERROR_CLASS error_class = ERROR_CLASS_OBJECT; + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + int bytes_sent = 0; + BACNET_ADDRESS my_address; + + /* decode the service request only */ + len = wp_decode_service_request(service_request, + service_len, &wp_data); + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], src, + &my_address, &npdu_data); +#if PRINT_ENABLED + fprintf(stderr, "Received Write-Property Request!\n"); + if (len > 0) + fprintf(stderr, "type=%u instance=%u property=%u index=%d\n", + wp_data.object_type, + wp_data.object_instance, + wp_data.object_property, wp_data.array_index); + else + fprintf(stderr, "Unable to decode Write-Property Request!\n"); +#endif + /* bad decoding or something we didn't understand - send an abort */ + if (len <= 0) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, ABORT_REASON_OTHER, true); +#if PRINT_ENABLED + fprintf(stderr, "Sending Abort!\n"); +#endif + } else if (service_data->segmented_message) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true); +#if PRINT_ENABLED + fprintf(stderr, "Sending Abort!\n"); +#endif + } else { + switch (wp_data.object_type) { + case OBJECT_DEVICE: + if (Device_Write_Property(&wp_data, &error_class, &error_code)) { + len = + encode_simple_ack(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Write Property Simple Ack for Device!\n"); +#endif + } else { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY, error_class, + error_code); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Write Property Error for Device!\n"); +#endif + } + break; + case OBJECT_ANALOG_INPUT: + case OBJECT_BINARY_INPUT: + error_class = ERROR_CLASS_PROPERTY; + error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, SERVICE_CONFIRMED_WRITE_PROPERTY, + error_class, error_code); +#if PRINT_ENABLED + fprintf(stderr, "Sending Write Access Error!\n"); +#endif + break; + case OBJECT_BINARY_OUTPUT: + if (Binary_Output_Write_Property(&wp_data, &error_class, + &error_code)) { + len = + encode_simple_ack(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Write Property Simple Ack for BO!\n"); +#endif + } else { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY, error_class, + error_code); +#if PRINT_ENABLED + fprintf(stderr, "Sending Write Access Error for BO!\n"); +#endif + } + break; + case OBJECT_BINARY_VALUE: + if (Binary_Value_Write_Property(&wp_data, &error_class, + &error_code)) { + len = + encode_simple_ack(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Write Property Simple Ack for BV!\n"); +#endif + } else { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY, error_class, + error_code); +#if PRINT_ENABLED + fprintf(stderr, "Sending Write Access Error for BV!\n"); +#endif + } + break; + case OBJECT_ANALOG_OUTPUT: + if (Analog_Output_Write_Property(&wp_data, &error_class, + &error_code)) { + len = + encode_simple_ack(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Write Property Simple Ack for AO!\n"); +#endif + } else { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY, error_class, + error_code); +#if PRINT_ENABLED + fprintf(stderr, "Sending Write Access Error for AO!\n"); +#endif + } + break; + case OBJECT_ANALOG_VALUE: + if (Analog_Value_Write_Property(&wp_data, &error_class, + &error_code)) { + len = + encode_simple_ack(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Write Property Simple Ack for AV!\n"); +#endif + } else { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY, error_class, + error_code); +#if PRINT_ENABLED + fprintf(stderr, "Sending Write Access Error for AV!\n"); +#endif + } + break; + case OBJECT_LIFE_SAFETY_POINT: + if (Life_Safety_Point_Write_Property(&wp_data, &error_class, + &error_code)) { + len = + encode_simple_ack(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Write Property Simple Ack for LSP!\n"); +#endif + } else { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY, error_class, + error_code); +#if PRINT_ENABLED + fprintf(stderr, "Sending Write Access Error for LSP!\n"); +#endif + } + break; + case OBJECT_LOAD_CONTROL: + if (Load_Control_Write_Property(&wp_data, &error_class, + &error_code)) { + len = + encode_simple_ack(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Write Property Simple Ack for Load Control!\n"); +#endif + } else { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY, error_class, + error_code); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Write Access Error for Load Control!\n"); +#endif + } + break; + case OBJECT_MULTI_STATE_OUTPUT: + if (Multistate_Output_Write_Property(&wp_data, &error_class, + &error_code)) { + len = + encode_simple_ack(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Write Property Simple Ack for MSO!\n"); +#endif + } else { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY, error_class, + error_code); +#if PRINT_ENABLED + fprintf(stderr, "Sending Write Access Error for MSO!\n"); +#endif + } + break; +#if BACFILE + case OBJECT_FILE: + if (bacfile_write_property(&wp_data, &error_class, + &error_code)) { + len = + encode_simple_ack(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Write Property Simple Ack for File!\n"); +#endif + } else { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY, error_class, + error_code); +#if PRINT_ENABLED + fprintf(stderr, "Sending Write Access Error for File!\n"); +#endif + } + break; +#endif /* BACFILE */ + default: + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, SERVICE_CONFIRMED_WRITE_PROPERTY, + error_class, error_code); +#if PRINT_ENABLED + fprintf(stderr, "Sending Unknown Object Error!\n"); +#endif + break; + } + } + pdu_len += len; + bytes_sent = datalink_send_pdu(src, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, "Failed to send PDU (%s)!\n", strerror(errno)); +#endif + + return; +} diff --git a/bacnet-stack/demo/handler/handlers.h b/bacnet-stack/demo/handler/handlers.h index e69de29b..0ad74fc6 100644 --- a/bacnet-stack/demo/handler/handlers.h +++ b/bacnet-stack/demo/handler/handlers.h @@ -0,0 +1,107 @@ +/************************************************************************** +* +* Copyright (C) 2005-2006 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 HANDLERS_H +#define HANDLERS_H + +#include +#include +#include +#include "bacdef.h" +#include "apdu.h" +#include "bacapp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + void handler_unrecognized_service(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * dest, + BACNET_CONFIRMED_SERVICE_DATA * service_data); + + void handler_who_is(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src); + + void handler_who_has(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src); + + void handler_i_am_add(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src); + + void handler_i_am_bind(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src); + + void handler_read_property(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, + BACNET_CONFIRMED_SERVICE_DATA * service_data); + + void handler_read_property_ack(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, + BACNET_CONFIRMED_SERVICE_ACK_DATA * service_data); + + void handler_write_property(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, + BACNET_CONFIRMED_SERVICE_DATA * service_data); + + void handler_atomic_read_file(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, + BACNET_CONFIRMED_SERVICE_DATA * service_data); + + void handler_atomic_read_file_ack(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, + BACNET_CONFIRMED_SERVICE_ACK_DATA * service_data); + + void handler_atomic_write_file(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data); + + void handler_reinitialize_device(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, + BACNET_CONFIRMED_SERVICE_DATA * service_data); + + void handler_device_communication_control(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, + BACNET_CONFIRMED_SERVICE_DATA * service_data); + + void handler_i_have(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src); + + void handler_timesync(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src); + + void handler_timesync_utc(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/demo/handler/noserv.c b/bacnet-stack/demo/handler/noserv.c index e69de29b..6de3d71b 100644 --- a/bacnet-stack/demo/handler/noserv.c +++ b/bacnet-stack/demo/handler/noserv.c @@ -0,0 +1,68 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "apdu.h" +#include "npdu.h" +#include "reject.h" + +void handler_unrecognized_service(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data) +{ + int len = 0; + int pdu_len = 0; + int bytes_sent = 0; + BACNET_NPDU_DATA npdu_data; + BACNET_ADDRESS my_address; + + (void) service_request; + (void) service_len; + + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], src, + &my_address, &npdu_data); + /* encode the APDU portion of the packet */ + len = reject_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, REJECT_REASON_UNRECOGNIZED_SERVICE); + pdu_len += len; + /* send the data */ + bytes_sent = datalink_send_pdu(src, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent > 0) + fprintf(stderr, "Sent Reject!\n"); + else + fprintf(stderr, "Failed to Send Reject (%s)!\n", strerror(errno)); +#endif +} diff --git a/bacnet-stack/demo/handler/s_arfs.c b/bacnet-stack/demo/handler/s_arfs.c index e69de29b..4e4f117d 100644 --- a/bacnet-stack/demo/handler/s_arfs.c +++ b/bacnet-stack/demo/handler/s_arfs.c @@ -0,0 +1,112 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ +#include +#include +#include +#include "config.h" +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "address.h" +#include "dcc.h" +#include "tsm.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "datalink.h" +#include "arf.h" +/* some demo stuff needed */ +#include "handlers.h" +#include "txbuf.h" + +uint8_t Send_Atomic_Read_File_Stream(uint32_t device_id, + uint32_t file_instance, + int fileStartPosition, unsigned requestedOctetCount) +{ + BACNET_ADDRESS dest; + BACNET_ADDRESS my_address; + BACNET_NPDU_DATA npdu_data; + unsigned max_apdu = 0; + uint8_t invoke_id = 0; + bool status = false; + int len = 0; + int pdu_len = 0; + int bytes_sent = 0; + BACNET_ATOMIC_READ_FILE_DATA data; + + /* if we are forbidden to send, don't send! */ + if (!dcc_communication_enabled()) + return 0; + + /* is the device bound? */ + status = address_get_by_device(device_id, &max_apdu, &dest); + /* is there a tsm available? */ + if (status) + invoke_id = tsm_next_free_invokeID(); + if (invoke_id) { + /* load the data for the encoding */ + data.object_type = OBJECT_FILE; + data.object_instance = file_instance; + data.access = FILE_STREAM_ACCESS; + data.type.stream.fileStartPosition = fileStartPosition; + data.type.stream.requestedOctetCount = requestedOctetCount; + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, true, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], &dest, + &my_address, &npdu_data); + len = arf_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + invoke_id, &data); + pdu_len += len; + /* will the APDU fit the target device? + note: if there is a bottleneck router in between + us and the destination, we won't know unless + we have a way to check for that and update the + max_apdu in the address binding table. */ + if ((unsigned) pdu_len < max_apdu) { + tsm_set_confirmed_unsegmented_transaction(invoke_id, &dest, + &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); + bytes_sent = + datalink_send_pdu(&dest, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, + "Failed to Send AtomicReadFile Request (%s)!\n", + strerror(errno)); +#endif + } else { + tsm_free_invoke_id(invoke_id); + invoke_id = 0; +#if PRINT_ENABLED + fprintf(stderr, "Failed to Send AtomicReadFile Request " + "(payload exceeds destination maximum APDU)!\n"); +#endif + } + } + + return invoke_id; +} diff --git a/bacnet-stack/demo/handler/s_awfs.c b/bacnet-stack/demo/handler/s_awfs.c index e69de29b..f116c34a 100644 --- a/bacnet-stack/demo/handler/s_awfs.c +++ b/bacnet-stack/demo/handler/s_awfs.c @@ -0,0 +1,126 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ +#include +#include +#include +#include "config.h" +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "address.h" +#include "tsm.h" +#include "dcc.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "datalink.h" +#include "awf.h" +/* some demo stuff needed */ +#include "handlers.h" +#include "txbuf.h" + +uint8_t Send_Atomic_Write_File_Stream(uint32_t device_id, + uint32_t file_instance, + int fileStartPosition, BACNET_OCTET_STRING * fileData) +{ + BACNET_ADDRESS dest; + BACNET_ADDRESS my_address; + BACNET_NPDU_DATA npdu_data; + unsigned max_apdu = 0; + uint8_t invoke_id = 0; + bool status = false; + int len = 0; + int pdu_len = 0; + int bytes_sent = 0; + BACNET_ATOMIC_WRITE_FILE_DATA data; + + /* if we are forbidden to send, don't send! */ + if (!dcc_communication_enabled()) + return 0; + + /* is the device bound? */ + status = address_get_by_device(device_id, &max_apdu, &dest); + /* is there a tsm available? */ + if (status) + invoke_id = tsm_next_free_invokeID(); + if (invoke_id) { + /* load the data for the encoding */ + data.object_type = OBJECT_FILE; + data.object_instance = file_instance; + data.access = FILE_STREAM_ACCESS; + data.type.stream.fileStartPosition = fileStartPosition; + status = octetstring_copy(&data.fileData, fileData); + if (status) { + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, true, + MESSAGE_PRIORITY_NORMAL); + pdu_len = + npdu_encode_pdu(&Handler_Transmit_Buffer[0], &dest, + &my_address, &npdu_data); + /* encode the APDU portion of the packet */ + len = awf_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + invoke_id, &data); + pdu_len += len; + /* will the APDU fit the target device? + note: if there is a bottleneck router in between + us and the destination, we won't know unless + we have a way to check for that and update the + max_apdu in the address binding table. */ + if ((unsigned) pdu_len <= max_apdu) { + tsm_set_confirmed_unsegmented_transaction(invoke_id, &dest, + &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); + bytes_sent = + datalink_send_pdu(&dest, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, + "Failed to Send AtomicWriteFile Request (%s)!\n", + strerror(errno)); +#endif + } else { + tsm_free_invoke_id(invoke_id); + invoke_id = 0; +#if PRINT_ENABLED + fprintf(stderr, "Failed to Send AtomicWriteFile Request " + "(payload [%d] exceeds destination maximum APDU [%u])!\n", + pdu_len, max_apdu); +#endif + } + } else { + tsm_free_invoke_id(invoke_id); + invoke_id = 0; +#if PRINT_ENABLED + fprintf(stderr, "Failed to Send AtomicWriteFile Request " + "(payload [%d] exceeds octet string capacity)!\n", + pdu_len); +#endif + } + } + + return invoke_id; +} diff --git a/bacnet-stack/demo/handler/s_dcc.c b/bacnet-stack/demo/handler/s_dcc.c index e69de29b..1efebe75 100644 --- a/bacnet-stack/demo/handler/s_dcc.c +++ b/bacnet-stack/demo/handler/s_dcc.c @@ -0,0 +1,109 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include "config.h" +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "address.h" +#include "tsm.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "datalink.h" +#include "dcc.h" +/* some demo stuff needed */ +#include "handlers.h" +#include "txbuf.h" + +uint8_t Send_Device_Communication_Control_Request(uint32_t device_id, uint16_t timeDuration, /* 0=optional */ + BACNET_COMMUNICATION_ENABLE_DISABLE state, char *password) +{ /* NULL=optional */ + BACNET_ADDRESS dest; + BACNET_ADDRESS my_address; + unsigned max_apdu = 0; + uint8_t invoke_id = 0; + bool status = false; + int len = 0; + int pdu_len = 0; + int bytes_sent = 0; + BACNET_CHARACTER_STRING password_string; + BACNET_NPDU_DATA npdu_data; + + /* if we are forbidden to send, don't send! */ + if (!dcc_communication_enabled()) + return 0; + + /* is the device bound? */ + status = address_get_by_device(device_id, &max_apdu, &dest); + /* is there a tsm available? */ + if (status) + invoke_id = tsm_next_free_invokeID(); + if (invoke_id) { + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, true, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], &dest, + &my_address, &npdu_data); + /* encode the APDU portion of the packet */ + characterstring_init_ansi(&password_string, password); + len = dcc_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + invoke_id, + timeDuration, state, password ? &password_string : NULL); + pdu_len += len; + /* will it fit in the sender? + note: if there is a bottleneck router in between + us and the destination, we won't know unless + we have a way to check for that and update the + max_apdu in the address binding table. */ + if ((unsigned) pdu_len < max_apdu) { + tsm_set_confirmed_unsegmented_transaction(invoke_id, &dest, + &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); + bytes_sent = + datalink_send_pdu(&dest, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, + "Failed to Send DeviceCommunicationControl Request (%s)!\n", + strerror(errno)); +#endif + } else { + tsm_free_invoke_id(invoke_id); + invoke_id = 0; +#if PRINT_ENABLED + fprintf(stderr, + "Failed to Send DeviceCommunicationControl Request " + "(exceeds destination maximum APDU)!\n"); +#endif + } + } + + return invoke_id; +} diff --git a/bacnet-stack/demo/handler/s_ihave.c b/bacnet-stack/demo/handler/s_ihave.c index e69de29b..a50fb726 100644 --- a/bacnet-stack/demo/handler/s_ihave.c +++ b/bacnet-stack/demo/handler/s_ihave.c @@ -0,0 +1,83 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include "config.h" +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "address.h" +#include "tsm.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "datalink.h" +#include "dcc.h" +#include "ihave.h" +/* some demo stuff needed */ +#include "handlers.h" +#include "txbuf.h" + +/* find a specific device, or use -1 for limit if you want unlimited */ +void Send_I_Have(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, char *object_name) +{ + int len = 0; + int pdu_len = 0; + BACNET_ADDRESS dest; + int bytes_sent = 0; + BACNET_I_HAVE_DATA data; + BACNET_NPDU_DATA npdu_data; + + /* if we are forbidden to send, don't send! */ + if (!dcc_communication_enabled()) + return; + /* Who-Has is a global broadcast */ + datalink_get_broadcast_address(&dest); + /* encode the NPDU portion of the packet */ + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], &dest, + NULL, &npdu_data); + /* encode the APDU portion of the packet */ + data.device_id.type = OBJECT_DEVICE; + data.device_id.instance = device_id; + data.object_id.type = object_type; + data.object_id.instance = object_instance; + characterstring_init_ansi(&data.object_name, object_name); + len = ihave_encode_apdu(&Handler_Transmit_Buffer[pdu_len], &data); + pdu_len += len; + /* send the data */ + bytes_sent = datalink_send_pdu(&dest, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, "Failed to Send I-Have Reply (%s)!\n", + strerror(errno)); +#endif +} diff --git a/bacnet-stack/demo/handler/s_rd.c b/bacnet-stack/demo/handler/s_rd.c index e69de29b..f72fe3bf 100644 --- a/bacnet-stack/demo/handler/s_rd.c +++ b/bacnet-stack/demo/handler/s_rd.c @@ -0,0 +1,107 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include +#include "config.h" +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "address.h" +#include "tsm.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "datalink.h" +#include "dcc.h" +#include "rd.h" +/* some demo stuff needed */ +#include "handlers.h" +#include "txbuf.h" + +uint8_t Send_Reinitialize_Device_Request(uint32_t device_id, + BACNET_REINITIALIZED_STATE state, char *password) +{ + BACNET_ADDRESS dest; + BACNET_ADDRESS my_address; + unsigned max_apdu = 0; + uint8_t invoke_id = 0; + bool status = false; + int len = 0; + int pdu_len = 0; + int bytes_sent = 0; + BACNET_CHARACTER_STRING password_string; + BACNET_NPDU_DATA npdu_data; + + /* if we are forbidden to send, don't send! */ + if (!dcc_communication_enabled()) + return 0; + + /* is the device bound? */ + status = address_get_by_device(device_id, &max_apdu, &dest); + /* is there a tsm available? */ + if (status) + invoke_id = tsm_next_free_invokeID(); + if (invoke_id) { + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, true, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], &dest, + &my_address, &npdu_data); + /* encode the APDU portion of the packet */ + characterstring_init_ansi(&password_string, password); + len = rd_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + invoke_id, state, password ? &password_string : NULL); + pdu_len += len; + /* will it fit in the sender? + note: if there is a bottleneck router in between + us and the destination, we won't know unless + we have a way to check for that and update the + max_apdu in the address binding table. */ + if ((unsigned) pdu_len < max_apdu) { + tsm_set_confirmed_unsegmented_transaction(invoke_id, &dest, + &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); + bytes_sent = + datalink_send_pdu(&dest, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, + "Failed to Send ReinitializeDevice Request (%s)!\n", + strerror(errno)); +#endif + } else { + tsm_free_invoke_id(invoke_id); + invoke_id = 0; +#if PRINT_ENABLED + fprintf(stderr, "Failed to Send ReinitializeDevice Request " + "(exceeds destination maximum APDU)!\n"); +#endif + } + } + + return invoke_id; +} diff --git a/bacnet-stack/demo/handler/s_rp.c b/bacnet-stack/demo/handler/s_rp.c index e69de29b..3a5c1f2f 100644 --- a/bacnet-stack/demo/handler/s_rp.c +++ b/bacnet-stack/demo/handler/s_rp.c @@ -0,0 +1,114 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include "config.h" +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "address.h" +#include "tsm.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "datalink.h" +#include "dcc.h" +#include "rp.h" +/* some demo stuff needed */ +#include "handlers.h" +#include "txbuf.h" + +/* returns invoke id of 0 if device is not bound or no tsm available */ +uint8_t Send_Read_Property_Request(uint32_t device_id, /* destination device */ + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, int32_t array_index) +{ + BACNET_ADDRESS dest; + BACNET_ADDRESS my_address; + unsigned max_apdu = 0; + uint8_t invoke_id = 0; + bool status = false; + int len = 0; + int pdu_len = 0; + int bytes_sent = 0; + BACNET_READ_PROPERTY_DATA data; + BACNET_NPDU_DATA npdu_data; + + if (!dcc_communication_enabled()) + return 0; + + /* is the device bound? */ + status = address_get_by_device(device_id, &max_apdu, &dest); + /* is there a tsm available? */ + if (status) + invoke_id = tsm_next_free_invokeID(); + if (invoke_id) { + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, true, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], &dest, + &my_address, &npdu_data); + /* encode the APDU portion of the packet */ + data.object_type = object_type; + data.object_instance = object_instance; + data.object_property = object_property; + data.array_index = array_index; + len = rp_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + invoke_id, &data); + pdu_len += len; + /* will it fit in the sender? + note: if there is a bottleneck router in between + us and the destination, we won't know unless + we have a way to check for that and update the + max_apdu in the address binding table. */ + if ((unsigned) pdu_len < max_apdu) { + tsm_set_confirmed_unsegmented_transaction(invoke_id, &dest, + &npdu_data, &Handler_Transmit_Buffer[0], + (uint16_t) pdu_len); + bytes_sent = + datalink_send_pdu(&dest, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, + "Failed to Send ReadProperty Request (%s)!\n", + strerror(errno)); +#endif + } else { + tsm_free_invoke_id(invoke_id); + invoke_id = 0; +#if PRINT_ENABLED + fprintf(stderr, "Failed to Send ReadProperty Request " + "(exceeds destination maximum APDU)!\n"); +#endif + } + } + + return invoke_id; +} diff --git a/bacnet-stack/demo/handler/s_ts.c b/bacnet-stack/demo/handler/s_ts.c index e69de29b..98130261 100644 --- a/bacnet-stack/demo/handler/s_ts.c +++ b/bacnet-stack/demo/handler/s_ts.c @@ -0,0 +1,100 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include +#include "config.h" +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "address.h" +#include "tsm.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "datalink.h" +#include "dcc.h" +#include "timesync.h" +/* some demo stuff needed */ +#include "handlers.h" +#include "txbuf.h" + +void Send_TimeSync(BACNET_DATE * bdate, BACNET_TIME * btime) +{ + int len = 0; + int pdu_len = 0; + BACNET_ADDRESS dest; + int bytes_sent = 0; + BACNET_NPDU_DATA npdu_data; + + if (!dcc_communication_enabled()) + return; + + /* we could use unicast or broadcast */ + datalink_get_broadcast_address(&dest); + /* encode the NPDU portion of the packet */ + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], &dest, + NULL, &npdu_data); + /* encode the APDU portion of the packet */ + len = timesync_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + bdate, btime); + pdu_len += len; + /* send it out the datalink */ + bytes_sent = datalink_send_pdu(&dest, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, + "Failed to Send Time-Synchronization Request (%s)!\n", + strerror(errno)); +#endif +} + +void Send_TimeSyncUTC(BACNET_DATE * bdate, BACNET_TIME * btime) +{ + int pdu_len = 0; + BACNET_ADDRESS dest; + int bytes_sent = 0; + BACNET_NPDU_DATA npdu_data; + + if (!dcc_communication_enabled()) + return; + + /* we could use unicast or broadcast */ + datalink_get_broadcast_address(&dest); + /* encode the APDU portion of the packet */ + pdu_len = timesync_utc_encode_apdu(&Handler_Transmit_Buffer[0], + bdate, btime); + bytes_sent = datalink_send_pdu(&dest, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, + "Failed to Send UTC-Time-Synchronization Request (%s)!\n", + strerror(errno)); +#endif +} diff --git a/bacnet-stack/demo/handler/s_whohas.c b/bacnet-stack/demo/handler/s_whohas.c index e69de29b..5ff4fef8 100644 --- a/bacnet-stack/demo/handler/s_whohas.c +++ b/bacnet-stack/demo/handler/s_whohas.c @@ -0,0 +1,118 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ +#include +#include +#include +#include "config.h" +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "address.h" +#include "tsm.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "datalink.h" +#include "dcc.h" +#include "whohas.h" +/* some demo stuff needed */ +#include "handlers.h" +#include "txbuf.h" + +/* find a specific device, or use -1 for limit if you want unlimited */ +void Send_WhoHas_Name(int32_t low_limit, + int32_t high_limit, char *object_name) +{ + int len = 0; + int pdu_len = 0; + BACNET_ADDRESS dest; + int bytes_sent = 0; + BACNET_WHO_HAS_DATA data; + BACNET_NPDU_DATA npdu_data; + + /* if we are forbidden to send, don't send! */ + if (!dcc_communication_enabled()) + return; + /* Who-Has is a global broadcast */ + datalink_get_broadcast_address(&dest); + /* encode the NPDU portion of the packet */ + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], &dest, + NULL, &npdu_data); + /* encode the APDU portion of the packet */ + data.low_limit = low_limit; + data.high_limit = high_limit; + data.object_name = true; + characterstring_init_ansi(&data.object.name, object_name); + len = whohas_encode_apdu(&Handler_Transmit_Buffer[pdu_len], &data); + pdu_len += len; + /* send the data */ + bytes_sent = datalink_send_pdu(&dest, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, "Failed to Send Who-Has Request (%s)!\n", + strerror(errno)); +#endif +} + +/* find a specific device, or use -1 for limit if you want unlimited */ +void Send_WhoHas_Object(int32_t low_limit, + int32_t high_limit, + BACNET_OBJECT_TYPE object_type, uint32_t object_instance) +{ + int len = 0; + int pdu_len = 0; + BACNET_ADDRESS dest; + int bytes_sent = 0; + BACNET_WHO_HAS_DATA data; + BACNET_NPDU_DATA npdu_data; + + /* if we are forbidden to send, don't send! */ + if (!dcc_communication_enabled()) + return; + /* Who-Has is a global broadcast */ + datalink_get_broadcast_address(&dest); + /* encode the NPDU portion of the packet */ + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], &dest, + NULL, &npdu_data); + /* encode the APDU portion of the packet */ + data.low_limit = low_limit; + data.high_limit = high_limit; + data.object_name = false; + data.object.identifier.type = object_type; + data.object.identifier.instance = object_instance; + len = whohas_encode_apdu(&Handler_Transmit_Buffer[pdu_len], &data); + pdu_len += len; + bytes_sent = datalink_send_pdu(&dest, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, "Failed to Send Who-Has Request (%s)!\n", + strerror(errno)); +#endif +} diff --git a/bacnet-stack/demo/handler/s_whois.c b/bacnet-stack/demo/handler/s_whois.c index e69de29b..e67e9a30 100644 --- a/bacnet-stack/demo/handler/s_whois.c +++ b/bacnet-stack/demo/handler/s_whois.c @@ -0,0 +1,75 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include "config.h" +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "address.h" +#include "tsm.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "datalink.h" +#include "dcc.h" +#include "whois.h" +/* some demo stuff needed */ +#include "handlers.h" +#include "txbuf.h" + +/* find a specific device, or use -1 for limit if you want unlimited */ +void Send_WhoIs(int32_t low_limit, int32_t high_limit) +{ + int len = 0; + int pdu_len = 0; + BACNET_ADDRESS dest; + int bytes_sent = 0; + BACNET_NPDU_DATA npdu_data; + + if (!dcc_communication_enabled()) + return; + + /* Who-Is is a global broadcast */ + datalink_get_broadcast_address(&dest); + /* encode the NPDU portion of the packet */ + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], &dest, + NULL, &npdu_data); + /* encode the APDU portion of the packet */ + len = whois_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + low_limit, high_limit); + pdu_len += len; + bytes_sent = datalink_send_pdu(&dest, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, "Failed to Send Who-Is Request (%s)!\n", + strerror(errno)); +#endif +} diff --git a/bacnet-stack/demo/handler/s_wp.c b/bacnet-stack/demo/handler/s_wp.c index e69de29b..9d7e7ec8 100644 --- a/bacnet-stack/demo/handler/s_wp.c +++ b/bacnet-stack/demo/handler/s_wp.c @@ -0,0 +1,154 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include "config.h" +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "address.h" +#include "tsm.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "datalink.h" +#include "dcc.h" +#include "whois.h" +/* some demo stuff needed */ +#include "handlers.h" +#include "txbuf.h" + +/* returns the invoke ID for confirmed request, or zero on failure */ +uint8_t Send_Write_Property_Request_Data(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + uint8_t * application_data, + int application_data_len, uint8_t priority, int32_t array_index) +{ + BACNET_ADDRESS dest; + BACNET_ADDRESS my_address; + unsigned max_apdu = 0; + uint8_t invoke_id = 0; + bool status = false; + int len = 0; + int pdu_len = 0; + int bytes_sent = 0; + BACNET_WRITE_PROPERTY_DATA data; + BACNET_NPDU_DATA npdu_data; + + if (!dcc_communication_enabled()) + return 0; + + /* is the device bound? */ + status = address_get_by_device(device_id, &max_apdu, &dest); + /* is there a tsm available? */ + if (status) + invoke_id = tsm_next_free_invokeID(); + if (invoke_id) { + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, true, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], &dest, + &my_address, &npdu_data); + /* encode the APDU portion of the packet */ + data.object_type = object_type; + data.object_instance = object_instance; + data.object_property = object_property; + data.array_index = array_index; + data.application_data_len = application_data_len; + memcpy(&data.application_data[0], &application_data[0], + application_data_len); + data.priority = priority; + len = wp_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + invoke_id, &data); + pdu_len += len; + /* will it fit in the sender? + note: if there is a bottleneck router in between + us and the destination, we won't know unless + we have a way to check for that and update the + max_apdu in the address binding table. */ + if ((unsigned) pdu_len < max_apdu) { + tsm_set_confirmed_unsegmented_transaction(invoke_id, &dest, + &npdu_data, &Handler_Transmit_Buffer[0], + (uint16_t) pdu_len); + bytes_sent = + datalink_send_pdu(&dest, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, + "Failed to Send WriteProperty Request (%s)!\n", + strerror(errno)); +#endif + } else { + tsm_free_invoke_id(invoke_id); + invoke_id = 0; +#if PRINT_ENABLED + fprintf(stderr, "Failed to Send WriteProperty Request " + "(exceeds destination maximum APDU)!\n"); +#endif + } + } + + return invoke_id; +} + +uint8_t Send_Write_Property_Request(uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_ID object_property, + BACNET_APPLICATION_DATA_VALUE * object_value, + uint8_t priority, int32_t array_index) +{ + uint8_t application_data[MAX_APDU] = { 0 }; + int apdu_len = 0, len = 0; + + while (object_value) { +#if PRINT_ENABLED_DEBUG + fprintf(stderr, "WriteProperty service: " + "%s tag=%d\n", + (object_value->context_specific?"context":"application"), + (int)(object_value->context_specific?object_value->context_tag:object_value->tag) ); +#endif + len = bacapp_encode_data(&application_data[apdu_len], + object_value); + if ((len + apdu_len) < MAX_APDU) { + apdu_len += len; + } else { + return 0; + } + object_value = object_value->next; + } + + return Send_Write_Property_Request_Data(device_id, + object_type, + object_instance, + object_property, + &application_data[0], apdu_len, priority, array_index); +} diff --git a/bacnet-stack/demo/handler/txbuf.c b/bacnet-stack/demo/handler/txbuf.c index e69de29b..25a50b53 100644 --- a/bacnet-stack/demo/handler/txbuf.c +++ b/bacnet-stack/demo/handler/txbuf.c @@ -0,0 +1,30 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include "config.h" +#include "datalink.h" + +uint8_t Handler_Transmit_Buffer[MAX_MPDU] = { 0 }; diff --git a/bacnet-stack/demo/handler/txbuf.h b/bacnet-stack/demo/handler/txbuf.h index e69de29b..4b12ea63 100644 --- a/bacnet-stack/demo/handler/txbuf.h +++ b/bacnet-stack/demo/handler/txbuf.h @@ -0,0 +1,35 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#ifndef TXBUF_H +#define TXBUF_H + +#include +#include +#include "config.h" +#include "datalink.h" + +extern uint8_t Handler_Transmit_Buffer[MAX_MPDU]; + +#endif diff --git a/bacnet-stack/demo/object/ai.c b/bacnet-stack/demo/object/ai.c index e69de29b..308ecf60 100644 --- a/bacnet-stack/demo/object/ai.c +++ b/bacnet-stack/demo/object/ai.c @@ -0,0 +1,197 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ + +/* Analog Input Objects customize for your use */ + +#include +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "config.h" /* the custom stuff */ + +#define MAX_ANALOG_INPUTS 7 + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need validate that the */ +/* given instance exists */ +bool Analog_Input_Valid_Instance(uint32_t object_instance) +{ + if (object_instance < MAX_ANALOG_INPUTS) + return true; + + return false; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then count how many you have */ +unsigned Analog_Input_Count(void) +{ + return MAX_ANALOG_INPUTS; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the instance */ +/* that correlates to the correct index */ +uint32_t Analog_Input_Index_To_Instance(unsigned index) +{ + return index; +} + +char *Analog_Input_Name(uint32_t object_instance) +{ + static char text_string[32] = ""; /* okay for single thread */ + + if (object_instance < MAX_ANALOG_INPUTS) { + sprintf(text_string, "ANALOG INPUT %u", object_instance); + return text_string; + } + + return NULL; +} + +/* return apdu length, or -1 on error */ +/* assumption - object has already exists */ +int Analog_Input_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + float value = 3.14159F; + + (void) array_index; + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_ANALOG_INPUT, + object_instance); + break; + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, + Analog_Input_Name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_tagged_enumerated(&apdu[0], OBJECT_ANALOG_INPUT); + break; + case PROP_PRESENT_VALUE: + apdu_len = encode_tagged_real(&apdu[0], value); + break; + case PROP_STATUS_FLAGS: + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: + apdu_len = encode_tagged_boolean(&apdu[0], false); + break; + case PROP_UNITS: + apdu_len = encode_tagged_enumerated(&apdu[0], UNITS_PERCENT); + break; + case 9997: + apdu_len = encode_tagged_real(&apdu[0], (float) 90.510); + break; + case 9998: + apdu_len = encode_tagged_unsigned(&apdu[0], 90); + break; + /* test case for signed encoding-decoding negative value correctly */ + case 9999: + apdu_len = encode_tagged_signed(&apdu[0], -200); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testAnalogInput(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_ANALOG_OUTPUT; + uint32_t decoded_instance = 0; + uint32_t instance = 123; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + + /* FIXME: we should do a lot more testing here... */ + len = Analog_Input_Encode_Property_APDU(&apdu[0], + instance, + PROP_OBJECT_IDENTIFIER, + BACNET_ARRAY_ALL, &error_class, &error_code); + ct_test(pTest, len >= 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == OBJECT_ANALOG_INPUT); + ct_test(pTest, decoded_instance == instance); + + return; +} + +#ifdef TEST_ANALOG_INPUT +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Analog Input", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testAnalogInput); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_ANALOG_INPUT */ +#endif /* TEST */ diff --git a/bacnet-stack/demo/object/ai.h b/bacnet-stack/demo/object/ai.h index e69de29b..d940be71 100644 --- a/bacnet-stack/demo/object/ai.h +++ b/bacnet-stack/demo/object/ai.h @@ -0,0 +1,55 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#ifndef AI_H +#define AI_H + +#include +#include +#include "bacdef.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + bool Analog_Input_Valid_Instance(uint32_t object_instance); + unsigned Analog_Input_Count(void); + uint32_t Analog_Input_Index_To_Instance(unsigned index); + char *Analog_Input_Name(uint32_t object_instance); + + int Analog_Input_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + +#ifdef TEST +#include "ctest.h" + void testAnalogInput(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/demo/object/ao.c b/bacnet-stack/demo/object/ao.c index e69de29b..b753c480 100644 --- a/bacnet-stack/demo/object/ao.c +++ b/bacnet-stack/demo/object/ao.c @@ -0,0 +1,469 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ + +/* Analog Output Objects - customize for your use */ + +#include +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bacapp.h" +#include "config.h" /* the custom stuff */ +#include "wp.h" + +#define MAX_ANALOG_OUTPUTS 4 + +/* we choose to have a NULL level in our system represented by */ +/* a particular value. When the priorities are not in use, they */ +/* will be relinquished (i.e. set to the NULL level). */ +#define AO_LEVEL_NULL 255 +/* When all the priorities are level null, the present value returns */ +/* the Relinquish Default value */ +#define AO_RELINQUISH_DEFAULT 0 +/* Here is our Priority Array. They are supposed to be Real, but */ +/* we don't have that kind of memory, so we will use a single byte */ +/* and load a Real for returning the value when asked. */ +static uint8_t + Analog_Output_Level[MAX_ANALOG_OUTPUTS][BACNET_MAX_PRIORITY]; +/* Writable out-of-service allows others to play with our Present Value */ +/* without changing the physical output */ +static bool Analog_Output_Out_Of_Service[MAX_ANALOG_OUTPUTS]; + +/* we need to have our arrays initialized before answering any calls */ +static bool Analog_Output_Initialized = false; + +void Analog_Output_Init(void) +{ + unsigned i, j; + + if (!Analog_Output_Initialized) { + Analog_Output_Initialized = true; + + /* initialize all the analog output priority arrays to NULL */ + for (i = 0; i < MAX_ANALOG_OUTPUTS; i++) { + for (j = 0; j < BACNET_MAX_PRIORITY; j++) { + Analog_Output_Level[i][j] = AO_LEVEL_NULL; + } + } + } + + return; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need validate that the */ +/* given instance exists */ +bool Analog_Output_Valid_Instance(uint32_t object_instance) +{ + Analog_Output_Init(); + if (object_instance < MAX_ANALOG_OUTPUTS) + return true; + + return false; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then count how many you have */ +unsigned Analog_Output_Count(void) +{ + Analog_Output_Init(); + return MAX_ANALOG_OUTPUTS; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the instance */ +/* that correlates to the correct index */ +uint32_t Analog_Output_Index_To_Instance(unsigned index) +{ + Analog_Output_Init(); + return index; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the index */ +/* that correlates to the correct instance number */ +unsigned Analog_Output_Instance_To_Index(uint32_t object_instance) +{ + unsigned index = MAX_ANALOG_OUTPUTS; + + Analog_Output_Init(); + if (object_instance < MAX_ANALOG_OUTPUTS) + index = object_instance; + + return index; +} + +float Analog_Output_Present_Value(uint32_t object_instance) +{ + float value = AO_RELINQUISH_DEFAULT; + unsigned index = 0; + unsigned i = 0; + + Analog_Output_Init(); + index = Analog_Output_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_OUTPUTS) { + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + if (Analog_Output_Level[index][i] != AO_LEVEL_NULL) { + value = Analog_Output_Level[index][i]; + break; + } + } + } + + return value; +} + +unsigned Analog_Output_Present_Value_Priority(uint32_t object_instance) +{ + unsigned index = 0; /* instance to index conversion */ + unsigned i = 0; /* loop counter */ + unsigned priority = 0; /* return value */ + + Analog_Output_Init(); + index = Analog_Output_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_OUTPUTS) { + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + if (Analog_Output_Level[index][i] != AO_LEVEL_NULL) { + priority = i + 1; + break; + } + } + } + + return priority; +} + +bool Analog_Output_Present_Value_Set(uint32_t object_instance, + float value, unsigned priority) +{ + unsigned index = 0; + bool status = false; + + index = Analog_Output_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_OUTPUTS) { + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */ ) && + (value >= 0.0) && (value <= 100.0)) { + Analog_Output_Level[index][priority-1] = (uint8_t) value; + /* Note: you could set the physical output here to the next + highest priority, or to the relinquish default if no + priorities are set. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } + } + + return status; +} + +bool Analog_Output_Present_Value_Relinquish(uint32_t object_instance, + unsigned priority) +{ + unsigned index = 0; + bool status = false; + + index = Analog_Output_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_OUTPUTS) { + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */ )) { + Analog_Output_Level[index][priority-1] = AO_LEVEL_NULL; + /* Note: you could set the physical output here to the next + highest priority, or to the relinquish default if no + priorities are set. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } + } + + return status; +} + +/* note: the object name must be unique within this device */ +char *Analog_Output_Name(uint32_t object_instance) +{ + static char text_string[32] = ""; /* okay for single thread */ + + if (object_instance < MAX_ANALOG_OUTPUTS) { + sprintf(text_string, "ANALOG OUTPUT %u", object_instance); + return text_string; + } + + return NULL; +} + +/* return apdu len, or -1 on error */ +int Analog_Output_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int len = 0; + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + float real_value = (float) 1.414; + unsigned object_index = 0; + unsigned i = 0; + bool state = false; + + Analog_Output_Init(); + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_ANALOG_OUTPUT, + object_instance); + break; + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, + Analog_Output_Name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = + encode_tagged_enumerated(&apdu[0], OBJECT_ANALOG_OUTPUT); + break; + case PROP_PRESENT_VALUE: + real_value = Analog_Output_Present_Value(object_instance); + apdu_len = encode_tagged_real(&apdu[0], real_value); + break; + case PROP_STATUS_FLAGS: + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: + object_index = Analog_Output_Instance_To_Index(object_instance); + state = Analog_Output_Out_Of_Service[object_index]; + apdu_len = encode_tagged_boolean(&apdu[0], state); + break; + case PROP_UNITS: + apdu_len = encode_tagged_enumerated(&apdu[0], UNITS_PERCENT); + break; + case PROP_PRIORITY_ARRAY: + /* Array element zero is the number of elements in the array */ + if (array_index == 0) + apdu_len = + encode_tagged_unsigned(&apdu[0], BACNET_MAX_PRIORITY); + /* if no index was specified, then try to encode the entire list */ + /* into one packet. */ + else if (array_index == BACNET_ARRAY_ALL) { + object_index = + Analog_Output_Instance_To_Index(object_instance); + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + /* FIXME: check if we have room before adding it to APDU */ + if (Analog_Output_Level[object_index][i] == AO_LEVEL_NULL) + len = encode_tagged_null(&apdu[apdu_len]); + else { + real_value = Analog_Output_Level[object_index][i]; + len = encode_tagged_real(&apdu[apdu_len], real_value); + } + /* add it if we have room */ + if ((apdu_len + len) < MAX_APDU) + apdu_len += len; + else { + *error_class = ERROR_CLASS_SERVICES; + *error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; + apdu_len = -1; + break; + } + } + } else { + object_index = + Analog_Output_Instance_To_Index(object_instance); + if (array_index <= BACNET_MAX_PRIORITY) { + if (Analog_Output_Level[object_index][array_index - 1] == + AO_LEVEL_NULL) + apdu_len = encode_tagged_null(&apdu[0]); + else { + real_value = + Analog_Output_Level[object_index][array_index - 1]; + apdu_len = encode_tagged_real(&apdu[0], real_value); + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = -1; + } + } + + break; + case PROP_RELINQUISH_DEFAULT: + real_value = AO_RELINQUISH_DEFAULT; + apdu_len = encode_tagged_real(&apdu[0], real_value); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +/* returns true if successful */ +bool Analog_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + bool status = false; /* return value */ + unsigned int object_index = 0; + uint8_t level = AO_LEVEL_NULL; + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + Analog_Output_Init(); + if (!Analog_Output_Valid_Instance(wp_data->object_instance)) { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + /* decode the some of the request */ + len = bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + switch (wp_data->object_property) { + case PROP_PRESENT_VALUE: + if (value.tag == BACNET_APPLICATION_TAG_REAL) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + status = + Analog_Output_Present_Value_Set(wp_data->object_instance, + value.type.Real, wp_data->priority); + if (wp_data->priority == 6) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else if (!status) { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else if (value.tag == BACNET_APPLICATION_TAG_NULL) { + level = AO_LEVEL_NULL; + object_index = + Analog_Output_Instance_To_Index(wp_data->object_instance); + status = + Analog_Output_Present_Value_Relinquish(wp_data-> + object_instance, wp_data->priority); + if (!status) { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_OUT_OF_SERVICE: + if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { + object_index = + Analog_Output_Instance_To_Index(wp_data->object_instance); + Analog_Output_Out_Of_Service[object_index] = + value.type.Boolean; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} + + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testAnalogOutput(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_ANALOG_OUTPUT; + uint32_t decoded_instance = 0; + uint32_t instance = 123; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + + len = Analog_Output_Encode_Property_APDU(&apdu[0], + instance, + PROP_OBJECT_IDENTIFIER, + BACNET_ARRAY_ALL, &error_class, &error_code); + ct_test(pTest, len != 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == OBJECT_ANALOG_OUTPUT); + ct_test(pTest, decoded_instance == instance); + + return; +} + +#ifdef TEST_ANALOG_OUTPUT +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Analog Output", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testAnalogOutput); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_ANALOG_INPUT */ +#endif /* TEST */ diff --git a/bacnet-stack/demo/object/ao.h b/bacnet-stack/demo/object/ao.h index e69de29b..1169ea21 100644 --- a/bacnet-stack/demo/object/ao.h +++ b/bacnet-stack/demo/object/ao.h @@ -0,0 +1,68 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#ifndef AO_H +#define AO_H + +#include +#include +#include "bacdef.h" +#include "bacerror.h" +#include "wp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + bool Analog_Output_Valid_Instance(uint32_t object_instance); + unsigned Analog_Output_Count(void); + uint32_t Analog_Output_Index_To_Instance(unsigned index); + char *Analog_Output_Name(uint32_t object_instance); + float Analog_Output_Present_Value(uint32_t object_instance); + unsigned Analog_Output_Present_Value_Priority(uint32_t + object_instance); + bool Analog_Output_Present_Value_Set(uint32_t object_instance, + float value, unsigned priority); + bool Analog_Output_Present_Value_Relinquish(uint32_t object_instance, + int priority); + + + int Analog_Output_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + + bool Analog_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + +#ifdef TEST +#include "ctest.h" + void testAnalogOutput(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/demo/object/av.c b/bacnet-stack/demo/object/av.c index e69de29b..022ce72c 100644 --- a/bacnet-stack/demo/object/av.c +++ b/bacnet-stack/demo/object/av.c @@ -0,0 +1,418 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* Analog Value Objects - customize for your use */ + +#include +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bacapp.h" +#include "config.h" /* the custom stuff */ +#include "wp.h" + +#define MAX_ANALOG_VALUES 4 + +/* we choose to have a NULL level in our system represented by */ +/* a particular value. When the priorities are not in use, they */ +/* will be relinquished (i.e. set to the NULL level). */ +#define ANALOG_LEVEL_NULL 255 +/* When all the priorities are level null, the present value returns */ +/* the Relinquish Default value */ +#define ANALOG_RELINQUISH_DEFAULT 0 +/* Here is our Priority Array. They are supposed to be Real, but */ +/* we don't have that kind of memory, so we will use a single byte */ +/* and load a Real for returning the value when asked. */ +static uint8_t Analog_Value_Level[MAX_ANALOG_VALUES][BACNET_MAX_PRIORITY]; +/* Writable out-of-service allows others to play with our Present Value */ +/* without changing the physical output */ +static bool Analog_Value_Out_Of_Service[MAX_ANALOG_VALUES]; + +/* we need to have our arrays initialized before answering any calls */ +static bool Analog_Value_Initialized = false; + +void Analog_Value_Init(void) +{ + unsigned i, j; + + if (!Analog_Value_Initialized) { + Analog_Value_Initialized = true; + + /* initialize all the analog output priority arrays to NULL */ + for (i = 0; i < MAX_ANALOG_VALUES; i++) { + for (j = 0; j < BACNET_MAX_PRIORITY; j++) { + Analog_Value_Level[i][j] = ANALOG_LEVEL_NULL; + } + } + } + + return; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need validate that the */ +/* given instance exists */ +bool Analog_Value_Valid_Instance(uint32_t object_instance) +{ + Analog_Value_Init(); + if (object_instance < MAX_ANALOG_VALUES) + return true; + + return false; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then count how many you have */ +unsigned Analog_Value_Count(void) +{ + Analog_Value_Init(); + return MAX_ANALOG_VALUES; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the instance */ +/* that correlates to the correct index */ +uint32_t Analog_Value_Index_To_Instance(unsigned index) +{ + Analog_Value_Init(); + return index; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the index */ +/* that correlates to the correct instance number */ +unsigned Analog_Value_Instance_To_Index(uint32_t object_instance) +{ + unsigned index = MAX_ANALOG_VALUES; + + Analog_Value_Init(); + if (object_instance < MAX_ANALOG_VALUES) + index = object_instance; + + return index; +} + +static float Analog_Value_Present_Value(uint32_t object_instance) +{ + float value = ANALOG_RELINQUISH_DEFAULT; + unsigned index = 0; + unsigned i = 0; + + Analog_Value_Init(); + index = Analog_Value_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_VALUES) { + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + if (Analog_Value_Level[index][i] != ANALOG_LEVEL_NULL) { + value = Analog_Value_Level[index][i]; + break; + } + } + } + + return value; +} + +/* note: the object name must be unique within this device */ +char *Analog_Value_Name(uint32_t object_instance) +{ + static char text_string[32] = ""; /* okay for single thread */ + + if (object_instance < MAX_ANALOG_VALUES) { + sprintf(text_string, "ANALOG VALUE %u", object_instance); + return text_string; + } + + return NULL; +} + +/* return apdu len, or -1 on error */ +int Analog_Value_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int len = 0; + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + float real_value = (float) 1.414; + unsigned object_index = 0; + unsigned i = 0; + bool state = false; + + Analog_Value_Init(); + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_ANALOG_VALUE, + object_instance); + break; + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, + Analog_Value_Name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_tagged_enumerated(&apdu[0], OBJECT_ANALOG_VALUE); + break; + case PROP_PRESENT_VALUE: + real_value = Analog_Value_Present_Value(object_instance); + apdu_len = encode_tagged_real(&apdu[0], real_value); + break; + case PROP_STATUS_FLAGS: + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: + object_index = Analog_Value_Instance_To_Index(object_instance); + state = Analog_Value_Out_Of_Service[object_index]; + apdu_len = encode_tagged_boolean(&apdu[0], state); + break; + case PROP_UNITS: + apdu_len = encode_tagged_enumerated(&apdu[0], UNITS_PERCENT); + break; + case PROP_PRIORITY_ARRAY: + /* Array element zero is the number of elements in the array */ + if (array_index == 0) + apdu_len = + encode_tagged_unsigned(&apdu[0], BACNET_MAX_PRIORITY); + /* if no index was specified, then try to encode the entire list */ + /* into one packet. */ + else if (array_index == BACNET_ARRAY_ALL) { + object_index = Analog_Value_Instance_To_Index(object_instance); + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + /* FIXME: check if we have room before adding it to APDU */ + if (Analog_Value_Level[object_index][i] == + ANALOG_LEVEL_NULL) + len = encode_tagged_null(&apdu[apdu_len]); + else { + real_value = Analog_Value_Level[object_index][i]; + len = encode_tagged_real(&apdu[apdu_len], real_value); + } + /* add it if we have room */ + if ((apdu_len + len) < MAX_APDU) + apdu_len += len; + else { + *error_class = ERROR_CLASS_SERVICES; + *error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; + apdu_len = -1; + break; + } + } + } else { + object_index = Analog_Value_Instance_To_Index(object_instance); + if (array_index <= BACNET_MAX_PRIORITY) { + if (Analog_Value_Level[object_index][array_index - 1] == + ANALOG_LEVEL_NULL) + apdu_len = encode_tagged_null(&apdu[0]); + else { + real_value = + Analog_Value_Level[object_index][array_index - 1]; + apdu_len = encode_tagged_real(&apdu[0], real_value); + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = -1; + } + } + + break; + case PROP_RELINQUISH_DEFAULT: + real_value = ANALOG_RELINQUISH_DEFAULT; + apdu_len = encode_tagged_real(&apdu[0], real_value); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +/* returns true if successful */ +bool Analog_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + bool status = false; /* return value */ + unsigned int object_index = 0; + unsigned int priority = 0; + uint8_t level = ANALOG_LEVEL_NULL; + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + Analog_Value_Init(); + if (!Analog_Value_Valid_Instance(wp_data->object_instance)) { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + /* decode the some of the request */ + len = bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + switch (wp_data->object_property) { + case PROP_PRESENT_VALUE: + if (value.tag == BACNET_APPLICATION_TAG_REAL) { + priority = wp_data->priority; + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */ ) && + (value.type.Real >= 0.0) && (value.type.Real <= 100.0)) { + level = (uint8_t) value.type.Real; + object_index = + Analog_Value_Instance_To_Index(wp_data-> + object_instance); + priority--; + Analog_Value_Level[object_index][priority] = level; + /* Note: you could set the physical output here if we + are the highest priority. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } else if (priority == 6) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else if (value.tag == BACNET_APPLICATION_TAG_NULL) { + level = ANALOG_LEVEL_NULL; + object_index = + Analog_Value_Instance_To_Index(wp_data->object_instance); + priority = wp_data->priority; + if (priority && (priority <= BACNET_MAX_PRIORITY)) { + priority--; + Analog_Value_Level[object_index][priority] = level; + /* Note: you could set the physical output here to the next + highest priority, or to the relinquish default if no + priorities are set. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_OUT_OF_SERVICE: + if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { + object_index = + Analog_Value_Instance_To_Index(wp_data->object_instance); + Analog_Value_Out_Of_Service[object_index] = value.type.Boolean; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} + + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testAnalog_Value(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_ANALOG_VALUE; + uint32_t decoded_instance = 0; + uint32_t instance = 123; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + + len = Analog_Value_Encode_Property_APDU(&apdu[0], + instance, + PROP_OBJECT_IDENTIFIER, + BACNET_ARRAY_ALL, &error_class, &error_code); + ct_test(pTest, len != 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == OBJECT_ANALOG_VALUE); + ct_test(pTest, decoded_instance == instance); + + return; +} + +#ifdef TEST_ANALOG_VALUE +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Analog Value", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testAnalog_Value); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_ANALOG_VALUE */ +#endif /* TEST */ diff --git a/bacnet-stack/demo/object/av.h b/bacnet-stack/demo/object/av.h index e69de29b..d89a52d1 100644 --- a/bacnet-stack/demo/object/av.h +++ b/bacnet-stack/demo/object/av.h @@ -0,0 +1,60 @@ +/************************************************************************** +* +* Copyright (C) 2006 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 AV_H +#define AV_H + +#include +#include +#include "bacdef.h" +#include "bacerror.h" +#include "wp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + bool Analog_Value_Valid_Instance(uint32_t object_instance); + unsigned Analog_Value_Count(void); + uint32_t Analog_Value_Index_To_Instance(unsigned index); + char *Analog_Value_Name(uint32_t object_instance); + + int Analog_Value_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + + bool Analog_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + +#ifdef TEST +#include "ctest.h" + void testAnalog_Value(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/demo/object/bacfile.c b/bacnet-stack/demo/object/bacfile.c index e69de29b..aa56a07b 100644 --- a/bacnet-stack/demo/object/bacfile.c +++ b/bacnet-stack/demo/object/bacfile.c @@ -0,0 +1,403 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include "config.h" +#include "address.h" +#include "bacdef.h" +#include "bacapp.h" +#include "datalink.h" +#include "bacdcode.h" +#include "npdu.h" +#include "apdu.h" +#include "tsm.h" +#include "device.h" +#include "arf.h" +#include "awf.h" + +typedef struct { + uint32_t instance; + char *filename; +} BACNET_FILE_LISTING; + +static BACNET_FILE_LISTING BACnet_File_Listing[] = { + {0, "temp_0.txt"}, + {1, "temp_1.txt"}, + {2, "temp_2.txt"}, + {0, NULL} /* last file indication */ +}; + +char *bacfile_name(uint32_t instance) +{ + uint32_t index = 0; + char *filename = NULL; + + /* linear search for file instance match */ + while (BACnet_File_Listing[index].filename) { + if (BACnet_File_Listing[index].instance == instance) { + filename = BACnet_File_Listing[index].filename; + break; + } + index++; + } + + return filename; +} + +bool bacfile_valid_instance(uint32_t object_instance) +{ + return bacfile_name(object_instance) ? true : false; +} + +uint32_t bacfile_count(void) +{ + uint32_t index = 0; + + /* linear search for file instance match */ + while (BACnet_File_Listing[index].filename) { + index++; + } + + return index; +} + +uint32_t bacfile_index_to_instance(unsigned find_index) +{ + uint32_t instance = BACNET_MAX_INSTANCE + 1; + uint32_t index = 0; + + /* bounds checking... */ + while (BACnet_File_Listing[index].filename) { + if (index == find_index) { + instance = BACnet_File_Listing[index].instance; + break; + } + index++; + } + + return instance; +} + +static long fsize(FILE * pFile) +{ + long size = 0; + long origin = 0; + + if (pFile) { + origin = ftell(pFile); + fseek(pFile, 0L, SEEK_END); + size = ftell(pFile); + fseek(pFile, origin, SEEK_SET); + } + return (size); +} + +static unsigned bacfile_file_size(uint32_t object_instance) +{ + char *pFilename = NULL; + FILE *pFile = NULL; + unsigned file_size = 0; + + pFilename = bacfile_name(object_instance); + if (pFilename) { + pFile = fopen(pFilename, "rb"); + if (pFile) { + file_size = fsize(pFile); + fclose(pFile); + } + } + + return file_size; +} + +/* return the number of bytes used, or -1 on error */ +int bacfile_encode_property_apdu(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int apdu_len = 0; /* return value */ + char text_string[32] = { "" }; + BACNET_CHARACTER_STRING char_string; + BACNET_DATE bdate; + BACNET_TIME btime; + + (void) array_index; + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], + OBJECT_FILE, object_instance); + break; + case PROP_OBJECT_NAME: + sprintf(text_string, "FILE %d", object_instance); + characterstring_init_ansi(&char_string, text_string); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_tagged_enumerated(&apdu[0], OBJECT_FILE); + break; + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, + bacfile_name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_FILE_TYPE: + characterstring_init_ansi(&char_string, "TEXT"); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_FILE_SIZE: + apdu_len = encode_tagged_unsigned(&apdu[0], + bacfile_file_size(object_instance)); + break; + case PROP_MODIFICATION_DATE: + /* FIXME: get the actual value instead of April Fool's Day */ + bdate.year = 2006; /* AD */ + bdate.month = 4; /* 1=Jan */ + bdate.day = 1; /* 1..31 */ + bdate.wday = 6; /* 1=Monday */ + apdu_len = encode_tagged_date(&apdu[0], &bdate); + /* FIXME: get the actual value */ + btime.hour = 7; + btime.min = 0; + btime.sec = 3; + btime.hundredths = 1; + apdu_len += encode_tagged_time(&apdu[apdu_len], &btime); + break; + case PROP_ARCHIVE: + /* 12.13.8 Archive + This property, of type BOOLEAN, indicates whether the File + object has been saved for historical or backup purposes. This + property shall be logical TRUE only if no changes have been + made to the file data by internal processes or through File + Access Services since the last time the object was archived. + */ + /* FIXME: get the actual value: note it may be inverse... */ + apdu_len = encode_tagged_boolean(&apdu[0], true); + break; + case PROP_READ_ONLY: + /* FIXME: get the actual value */ + apdu_len = encode_tagged_boolean(&apdu[0], true); + break; + case PROP_FILE_ACCESS_METHOD: + apdu_len = encode_tagged_enumerated(&apdu[0], FILE_STREAM_ACCESS); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +/* returns true if successful */ +bool bacfile_write_property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + if (!bacfile_valid_instance(wp_data->object_instance)) { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + + /* decode the some of the request */ + len = bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + switch (wp_data->object_property) { + case PROP_ARCHIVE: + /* 12.13.8 Archive + This property, of type BOOLEAN, indicates whether the File + object has been saved for historical or backup purposes. This + property shall be logical TRUE only if no changes have been + made to the file data by internal processes or through File + Access Services since the last time the object was archived. */ + if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { + if (value.type.Boolean) { + /* FIXME: do something to wp_data->object_instance */ + } else { + /* FIXME: do something to wp_data->object_instance */ + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_FILE_SIZE: + /* If the file size can be changed by writing to the file, + and File_Access_Method is STREAM_ACCESS, then this property + shall be writable. */ + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + /* FIXME: do something with value.type.Unsigned + to wp_data->object_instance */ + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} + + + +uint32_t bacfile_instance(char *filename) +{ + uint32_t index = 0; + uint32_t instance = BACNET_MAX_INSTANCE + 1; + + /* linear search for filename match */ + while (BACnet_File_Listing[index].filename) { + if (strcmp(BACnet_File_Listing[index].filename, filename) == 0) { + instance = BACnet_File_Listing[index].instance; + break; + } + index++; + } + + return instance; +} + +#if TSM_ENABLED +/* this is one way to match up the invoke ID with */ +/* the file ID from the AtomicReadFile request. */ +/* Another way would be to store the */ +/* invokeID and file instance in a list or table */ +/* when the request was sent */ +uint32_t bacfile_instance_from_tsm(uint8_t invokeID) +{ + BACNET_NPDU_DATA npdu_data = { 0 }; /* dummy for getting npdu length */ + BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 }; + uint8_t service_choice = 0; + uint8_t *service_request = NULL; + uint16_t service_request_len = 0; + BACNET_ADDRESS dest; /* where the original packet was destined */ + uint8_t apdu[MAX_PDU] = { 0 }; /* original APDU packet */ + uint16_t apdu_len = 0; /* original APDU packet length */ + uint16_t len = 0; /* apdu header length */ + BACNET_ATOMIC_READ_FILE_DATA data = { 0 }; + uint32_t object_instance = BACNET_MAX_INSTANCE + 1; /* return value */ + bool found = false; + + found = tsm_get_transaction_pdu(invokeID, &dest, &npdu_data, &apdu[0], + &apdu_len); + if (found) { + if (!npdu_data.network_layer_message + && npdu_data.data_expecting_reply + && (apdu[0] == PDU_TYPE_CONFIRMED_SERVICE_REQUEST)) { + len = + apdu_decode_confirmed_service_request(&apdu[0], + apdu_len, &service_data, &service_choice, + &service_request, &service_request_len); + if (service_choice == SERVICE_CONFIRMED_ATOMIC_READ_FILE) { + len = arf_decode_service_request(service_request, + service_request_len, &data); + if (len > 0) { + if (data.object_type == OBJECT_FILE) + object_instance = data.object_instance; + } + } + } + } + + return object_instance; +} +#endif + +bool bacfile_read_data(BACNET_ATOMIC_READ_FILE_DATA * data) +{ + char *pFilename = NULL; + bool found = false; + FILE *pFile = NULL; + size_t len = 0; + + pFilename = bacfile_name(data->object_instance); + if (pFilename) { + found = true; + pFile = fopen(pFilename, "rb"); + if (pFile) { + (void) fseek(pFile, + data->type.stream.fileStartPosition, SEEK_SET); + len = fread(octetstring_value(&data->fileData), 1, + data->type.stream.requestedOctetCount, pFile); + if (len < data->type.stream.requestedOctetCount) + data->endOfFile = true; + else + data->endOfFile = false; + octetstring_truncate(&data->fileData, len); + fclose(pFile); + } else { + octetstring_truncate(&data->fileData, 0); + data->endOfFile = true; + } + } else { + octetstring_truncate(&data->fileData, 0); + data->endOfFile = true; + } + + return found; +} + +bool bacfile_write_stream_data(BACNET_ATOMIC_WRITE_FILE_DATA * data) +{ + char *pFilename = NULL; + bool found = false; + FILE *pFile = NULL; + + pFilename = bacfile_name(data->object_instance); + if (pFilename) { + found = true; + /* open the file as a clean slate when starting at 0 */ + if (data->type.stream.fileStartPosition == 0) + pFile = fopen(pFilename, "wb"); + else + pFile = fopen(pFilename, "rb+"); + if (pFile) { + (void)fseek(pFile, data->type.stream.fileStartPosition, SEEK_SET); + if (fwrite(octetstring_value(&data->fileData), + octetstring_length(&data->fileData),1,pFile) != 1) { + + } + fclose(pFile); + } + } + + return found; +} diff --git a/bacnet-stack/demo/object/bacfile.h b/bacnet-stack/demo/object/bacfile.h index e69de29b..82cefe94 100644 --- a/bacnet-stack/demo/object/bacfile.h +++ b/bacnet-stack/demo/object/bacfile.h @@ -0,0 +1,81 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef BACFILE_H +#define BACFILE_H + +#include +#include +#include "bacdef.h" +#include "bacenum.h" +#include "apdu.h" +#include "arf.h" +#include "awf.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + char *bacfile_name(uint32_t instance); + bool bacfile_valid_instance(uint32_t object_instance); + uint32_t bacfile_count(void); + uint32_t bacfile_index_to_instance(unsigned find_index); + uint32_t bacfile_instance(char *filename); +#if TSM_ENABLED + /* this is one way to match up the invoke ID with */ + /* the file ID from the AtomicReadFile request. */ + /* Another way would be to store the */ + /* invokeID and file instance in a list or table */ + /* when the request was sent */ + uint32_t bacfile_instance_from_tsm(uint8_t invokeID); +#endif + + /* handler ACK helper */ + bool bacfile_read_data(BACNET_ATOMIC_READ_FILE_DATA * data); + bool bacfile_write_stream_data(BACNET_ATOMIC_WRITE_FILE_DATA * data); + +/* handling for read property service */ + int bacfile_encode_property_apdu(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + +/* handling for write property service */ + bool bacfile_write_property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/demo/object/bi.c b/bacnet-stack/demo/object/bi.c index e69de29b..8e7211bc 100644 --- a/bacnet-stack/demo/object/bi.c +++ b/bacnet-stack/demo/object/bi.c @@ -0,0 +1,243 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* Analog Input Objects customize for your use */ + +#include +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "config.h" /* the custom stuff */ + +#define MAX_BINARY_INPUTS 5 + +static BACNET_BINARY_PV Present_Value[MAX_BINARY_INPUTS]; + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need validate that the */ +/* given instance exists */ +bool Binary_Input_Valid_Instance(uint32_t object_instance) +{ + if (object_instance < MAX_BINARY_INPUTS) + return true; + + return false; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then count how many you have */ +unsigned Binary_Input_Count(void) +{ + return MAX_BINARY_INPUTS; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the instance */ +/* that correlates to the correct index */ +uint32_t Binary_Input_Index_To_Instance(unsigned index) +{ + return index; +} + +void Binary_Input_Init(void) +{ + static bool initialized = false; + unsigned i; + + if (!initialized) { + initialized = true; + + /* initialize all the values */ + for (i = 0; i < MAX_BINARY_INPUTS; i++) { + Present_Value[i] = BINARY_INACTIVE; + } + } + + return; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the index */ +/* that correlates to the correct instance number */ +unsigned Binary_Input_Instance_To_Index(uint32_t object_instance) +{ + unsigned index = MAX_BINARY_INPUTS; + + Binary_Input_Init(); + if (object_instance < MAX_BINARY_INPUTS) + index = object_instance; + + return index; +} + +static BACNET_BINARY_PV Binary_Input_Present_Value(uint32_t + object_instance) +{ + BACNET_BINARY_PV value = BINARY_INACTIVE; + unsigned index = 0; + + Binary_Input_Init(); + index = Binary_Input_Instance_To_Index(object_instance); + if (index < MAX_BINARY_INPUTS) + value = Present_Value[index]; + + return value; +} + +char *Binary_Input_Name(uint32_t object_instance) +{ + static char text_string[32] = ""; /* okay for single thread */ + + Binary_Input_Init(); + if (object_instance < MAX_BINARY_INPUTS) { + sprintf(text_string, "BINARY INPUT %u", object_instance); + return text_string; + } + + return NULL; +} + +/* return apdu length, or -1 on error */ +/* assumption - object already exists, and has been bounds checked */ +int Binary_Input_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + BACNET_POLARITY polarity = POLARITY_NORMAL; + + + (void) array_index; + Binary_Input_Init(); + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_BINARY_INPUT, + object_instance); + break; + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + /* note: object name must be unique in our device */ + characterstring_init_ansi(&char_string, + Binary_Input_Name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_tagged_enumerated(&apdu[0], OBJECT_BINARY_INPUT); + break; + case PROP_PRESENT_VALUE: + /* note: you need to look up the actual value */ + apdu_len = + encode_tagged_enumerated(&apdu[0], + Binary_Input_Present_Value(object_instance)); + break; + case PROP_STATUS_FLAGS: + /* note: see the details in the standard on how to use these */ + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + /* note: see the details in the standard on how to use this */ + apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: + apdu_len = encode_tagged_boolean(&apdu[0], false); + break; + case PROP_POLARITY: + apdu_len = encode_tagged_enumerated(&apdu[0], polarity); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testBinaryInput(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_BINARY_OUTPUT; + uint32_t decoded_instance = 0; + uint32_t instance = 123; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + + /* FIXME: we should do a lot more testing here... */ + len = Binary_Input_Encode_Property_APDU(&apdu[0], + instance, + PROP_OBJECT_IDENTIFIER, + BACNET_ARRAY_ALL, &error_class, &error_code); + ct_test(pTest, len >= 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == OBJECT_BINARY_INPUT); + ct_test(pTest, decoded_instance == instance); + + return; +} + +#ifdef TEST_BINARY_INPUT +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Binary Input", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testBinaryInput); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_BINARY_INPUT */ +#endif /* TEST */ diff --git a/bacnet-stack/demo/object/bi.h b/bacnet-stack/demo/object/bi.h index e69de29b..9b5d5187 100644 --- a/bacnet-stack/demo/object/bi.h +++ b/bacnet-stack/demo/object/bi.h @@ -0,0 +1,55 @@ +/************************************************************************** +* +* Copyright (C) 2006 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 BI_H +#define BI_H + +#include +#include +#include "bacdef.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + bool Binary_Input_Valid_Instance(uint32_t object_instance); + unsigned Binary_Input_Count(void); + uint32_t Binary_Input_Index_To_Instance(unsigned index); + char *Binary_Input_Name(uint32_t object_instance); + + int Binary_Input_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + +#ifdef TEST +#include "ctest.h" + void testBinaryInput(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/demo/object/bo.c b/bacnet-stack/demo/object/bo.c index e69de29b..a323b33b 100644 --- a/bacnet-stack/demo/object/bo.c +++ b/bacnet-stack/demo/object/bo.c @@ -0,0 +1,421 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ + +/* Binary Output Objects - customize for your use */ + +#include +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bacapp.h" +#include "config.h" /* the custom stuff */ +#include "wp.h" + +#define MAX_BINARY_OUTPUTS 6 + +/* When all the priorities are level null, the present value returns */ +/* the Relinquish Default value */ +#define RELINQUISH_DEFAULT BINARY_INACTIVE +/* Here is our Priority Array.*/ +static BACNET_BINARY_PV + Binary_Output_Level[MAX_BINARY_OUTPUTS][BACNET_MAX_PRIORITY]; +/* Writable out-of-service allows others to play with our Present Value */ +/* without changing the physical output */ +static bool Binary_Output_Out_Of_Service[MAX_BINARY_OUTPUTS]; + +void Binary_Output_Init(void) +{ + unsigned i, j; + static bool initialized = false; + + if (!initialized) { + initialized = true; + + /* initialize all the analog output priority arrays to NULL */ + for (i = 0; i < MAX_BINARY_OUTPUTS; i++) { + for (j = 0; j < BACNET_MAX_PRIORITY; j++) { + Binary_Output_Level[i][j] = BINARY_NULL; + } + } + } + + return; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need validate that the */ +/* given instance exists */ +bool Binary_Output_Valid_Instance(uint32_t object_instance) +{ + if (object_instance < MAX_BINARY_OUTPUTS) + return true; + + return false; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then count how many you have */ +unsigned Binary_Output_Count(void) +{ + return MAX_BINARY_OUTPUTS; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the instance */ +/* that correlates to the correct index */ +uint32_t Binary_Output_Index_To_Instance(unsigned index) +{ + return index; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the index */ +/* that correlates to the correct instance number */ +unsigned Binary_Output_Instance_To_Index(uint32_t object_instance) +{ + unsigned index = MAX_BINARY_OUTPUTS; + + if (object_instance < MAX_BINARY_OUTPUTS) + index = object_instance; + + return index; +} + +static BACNET_BINARY_PV Binary_Output_Present_Value(uint32_t + object_instance) +{ + BACNET_BINARY_PV value = RELINQUISH_DEFAULT; + unsigned index = 0; + unsigned i = 0; + + Binary_Output_Init(); + index = Binary_Output_Instance_To_Index(object_instance); + if (index < MAX_BINARY_OUTPUTS) { + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + if (Binary_Output_Level[index][i] != BINARY_NULL) { + value = Binary_Output_Level[index][i]; + break; + } + } + } + + return value; +} + +/* note: the object name must be unique within this device */ +char *Binary_Output_Name(uint32_t object_instance) +{ + static char text_string[32] = ""; /* okay for single thread */ + + if (object_instance < MAX_BINARY_OUTPUTS) { + sprintf(text_string, "BINARY OUTPUT %u", object_instance); + return text_string; + } + + return NULL; +} + +/* return apdu len, or -1 on error */ +int Binary_Output_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int len = 0; + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + BACNET_BINARY_PV present_value = BINARY_INACTIVE; + BACNET_POLARITY polarity = POLARITY_NORMAL; + unsigned object_index = 0; + unsigned i = 0; + bool state = false; + + Binary_Output_Init(); + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_BINARY_OUTPUT, + object_instance); + break; + /* note: Name and Description don't have to be the same. + You could make Description writable and different */ + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, + Binary_Output_Name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = + encode_tagged_enumerated(&apdu[0], OBJECT_BINARY_OUTPUT); + break; + case PROP_PRESENT_VALUE: + present_value = Binary_Output_Present_Value(object_instance); + apdu_len = encode_tagged_enumerated(&apdu[0], present_value); + break; + case PROP_STATUS_FLAGS: + /* note: see the details in the standard on how to use these */ + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + /* note: see the details in the standard on how to use this */ + apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: + object_index = Binary_Output_Instance_To_Index(object_instance); + state = Binary_Output_Out_Of_Service[object_index]; + apdu_len = encode_tagged_boolean(&apdu[0], state); + break; + case PROP_POLARITY: + apdu_len = encode_tagged_enumerated(&apdu[0], polarity); + break; + case PROP_PRIORITY_ARRAY: + /* Array element zero is the number of elements in the array */ + if (array_index == 0) + apdu_len = + encode_tagged_unsigned(&apdu[0], BACNET_MAX_PRIORITY); + /* if no index was specified, then try to encode the entire list */ + /* into one packet. */ + else if (array_index == BACNET_ARRAY_ALL) { + object_index = + Binary_Output_Instance_To_Index(object_instance); + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + /* FIXME: check if we have room before adding it to APDU */ + if (Binary_Output_Level[object_index][i] == BINARY_NULL) + len = encode_tagged_null(&apdu[apdu_len]); + else { + present_value = Binary_Output_Level[object_index][i]; + len = + encode_tagged_enumerated(&apdu[apdu_len], + present_value); + } + /* add it if we have room */ + if ((apdu_len + len) < MAX_APDU) + apdu_len += len; + else { + *error_class = ERROR_CLASS_SERVICES; + *error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; + apdu_len = -1; + break; + } + } + } else { + object_index = + Binary_Output_Instance_To_Index(object_instance); + if (array_index <= BACNET_MAX_PRIORITY) { + if (Binary_Output_Level[object_index][array_index] == + BINARY_NULL) + len = encode_tagged_null(&apdu[apdu_len]); + else { + present_value = + Binary_Output_Level[object_index][array_index]; + len = + encode_tagged_enumerated(&apdu[apdu_len], + present_value); + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = -1; + } + } + + break; + case PROP_RELINQUISH_DEFAULT: + present_value = RELINQUISH_DEFAULT; + apdu_len = encode_tagged_enumerated(&apdu[0], present_value); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +/* returns true if successful */ +bool Binary_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + bool status = false; /* return value */ + unsigned int object_index = 0; + unsigned int priority = 0; + BACNET_BINARY_PV level = BINARY_NULL; + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + Binary_Output_Init(); + if (!Binary_Output_Valid_Instance(wp_data->object_instance)) { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + /* decode the some of the request */ + len = bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + switch (wp_data->object_property) { + case PROP_PRESENT_VALUE: + if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) { + priority = wp_data->priority; + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */ ) && + (value.type.Enumerated >= MIN_BINARY_PV) && + (value.type.Enumerated <= MAX_BINARY_PV)) { + level = value.type.Enumerated; + object_index = + Binary_Output_Instance_To_Index(wp_data-> + object_instance); + priority--; + Binary_Output_Level[object_index][priority] = level; + /* Note: you could set the physical output here if we + are the highest priority. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } else if (priority == 6) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else if (value.tag == BACNET_APPLICATION_TAG_NULL) { + level = BINARY_NULL; + object_index = + Binary_Output_Instance_To_Index(wp_data->object_instance); + priority = wp_data->priority; + if (priority && (priority <= BACNET_MAX_PRIORITY)) { + priority--; + Binary_Output_Level[object_index][priority] = level; + /* Note: you could set the physical output here to the next + highest priority, or to the relinquish default if no + priorities are set. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_OUT_OF_SERVICE: + if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { + object_index = + Binary_Output_Instance_To_Index(wp_data->object_instance); + Binary_Output_Out_Of_Service[object_index] = + value.type.Boolean; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} + + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testBinaryOutput(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_BINARY_OUTPUT; + uint32_t decoded_instance = 0; + uint32_t instance = 123; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + + len = Binary_Output_Encode_Property_APDU(&apdu[0], + instance, + PROP_OBJECT_IDENTIFIER, + BACNET_ARRAY_ALL, &error_class, &error_code); + ct_test(pTest, len != 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == OBJECT_BINARY_OUTPUT); + ct_test(pTest, decoded_instance == instance); + + return; +} + +#ifdef TEST_BINARY_OUTPUT +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Binary Output", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testBinaryOutput); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_BINARY_INPUT */ +#endif /* TEST */ diff --git a/bacnet-stack/demo/object/bo.h b/bacnet-stack/demo/object/bo.h index e69de29b..7898186e 100644 --- a/bacnet-stack/demo/object/bo.h +++ b/bacnet-stack/demo/object/bo.h @@ -0,0 +1,60 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#ifndef BO_H +#define BO_H + +#include +#include +#include "bacdef.h" +#include "bacerror.h" +#include "wp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + bool Binary_Output_Valid_Instance(uint32_t object_instance); + unsigned Binary_Output_Count(void); + uint32_t Binary_Output_Index_To_Instance(unsigned index); + char *Binary_Output_Name(uint32_t object_instance); + + int Binary_Output_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + + bool Binary_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + +#ifdef TEST +#include "ctest.h" + void testBinaryOutput(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/demo/object/bv.c b/bacnet-stack/demo/object/bv.c index e69de29b..b915d8dc 100644 --- a/bacnet-stack/demo/object/bv.c +++ b/bacnet-stack/demo/object/bv.c @@ -0,0 +1,417 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* Binary Output Objects - customize for your use */ + +#include +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bacapp.h" +#include "config.h" /* the custom stuff */ +#include "wp.h" + +#define MAX_BINARY_VALUES 2 + +/* When all the priorities are level null, the present value returns */ +/* the Relinquish Default value */ +#define RELINQUISH_DEFAULT BINARY_INACTIVE +/* Here is our Priority Array.*/ +static BACNET_BINARY_PV + Binary_Value_Level[MAX_BINARY_VALUES][BACNET_MAX_PRIORITY]; +/* Writable out-of-service allows others to play with our Present Value */ +/* without changing the physical output */ +static bool Binary_Value_Out_Of_Service[MAX_BINARY_VALUES]; + +void Binary_Value_Init(void) +{ + unsigned i, j; + static bool initialized = false; + + if (!initialized) { + initialized = true; + + /* initialize all the analog output priority arrays to NULL */ + for (i = 0; i < MAX_BINARY_VALUES; i++) { + for (j = 0; j < BACNET_MAX_PRIORITY; j++) { + Binary_Value_Level[i][j] = BINARY_NULL; + } + } + } + + return; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need validate that the */ +/* given instance exists */ +bool Binary_Value_Valid_Instance(uint32_t object_instance) +{ + if (object_instance < MAX_BINARY_VALUES) + return true; + + return false; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then count how many you have */ +unsigned Binary_Value_Count(void) +{ + return MAX_BINARY_VALUES; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the instance */ +/* that correlates to the correct index */ +uint32_t Binary_Value_Index_To_Instance(unsigned index) +{ + return index; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the index */ +/* that correlates to the correct instance number */ +unsigned Binary_Value_Instance_To_Index(uint32_t object_instance) +{ + unsigned index = MAX_BINARY_VALUES; + + if (object_instance < MAX_BINARY_VALUES) + index = object_instance; + + return index; +} + +static BACNET_BINARY_PV Binary_Value_Present_Value(uint32_t + object_instance) +{ + BACNET_BINARY_PV value = RELINQUISH_DEFAULT; + unsigned index = 0; + unsigned i = 0; + + Binary_Value_Init(); + index = Binary_Value_Instance_To_Index(object_instance); + if (index < MAX_BINARY_VALUES) { + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + if (Binary_Value_Level[index][i] != BINARY_NULL) { + value = Binary_Value_Level[index][i]; + break; + } + } + } + + return value; +} + +/* note: the object name must be unique within this device */ +char *Binary_Value_Name(uint32_t object_instance) +{ + static char text_string[32] = ""; /* okay for single thread */ + + if (object_instance < MAX_BINARY_VALUES) { + sprintf(text_string, "BINARY VALUE %u", object_instance); + return text_string; + } + + return NULL; +} + +/* return apdu len, or -1 on error */ +int Binary_Value_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int len = 0; + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + BACNET_BINARY_PV present_value = BINARY_INACTIVE; + BACNET_POLARITY polarity = POLARITY_NORMAL; + unsigned object_index = 0; + unsigned i = 0; + bool state = false; + + Binary_Value_Init(); + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_BINARY_VALUE, + object_instance); + break; + /* note: Name and Description don't have to be the same. + You could make Description writable and different */ + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, + Binary_Value_Name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_tagged_enumerated(&apdu[0], OBJECT_BINARY_VALUE); + break; + case PROP_PRESENT_VALUE: + present_value = Binary_Value_Present_Value(object_instance); + apdu_len = encode_tagged_enumerated(&apdu[0], present_value); + break; + case PROP_STATUS_FLAGS: + /* note: see the details in the standard on how to use these */ + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + /* note: see the details in the standard on how to use this */ + apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: + object_index = Binary_Value_Instance_To_Index(object_instance); + state = Binary_Value_Out_Of_Service[object_index]; + apdu_len = encode_tagged_boolean(&apdu[0], state); + break; + case PROP_POLARITY: + apdu_len = encode_tagged_enumerated(&apdu[0], polarity); + break; + case PROP_PRIORITY_ARRAY: + /* Array element zero is the number of elements in the array */ + if (array_index == 0) + apdu_len = + encode_tagged_unsigned(&apdu[0], BACNET_MAX_PRIORITY); + /* if no index was specified, then try to encode the entire list */ + /* into one packet. */ + else if (array_index == BACNET_ARRAY_ALL) { + object_index = Binary_Value_Instance_To_Index(object_instance); + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + /* FIXME: check if we have room before adding it to APDU */ + if (Binary_Value_Level[object_index][i] == BINARY_NULL) + len = encode_tagged_null(&apdu[apdu_len]); + else { + present_value = Binary_Value_Level[object_index][i]; + len = + encode_tagged_enumerated(&apdu[apdu_len], + present_value); + } + /* add it if we have room */ + if ((apdu_len + len) < MAX_APDU) + apdu_len += len; + else { + *error_class = ERROR_CLASS_SERVICES; + *error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; + apdu_len = -1; + break; + } + } + } else { + object_index = Binary_Value_Instance_To_Index(object_instance); + if (array_index <= BACNET_MAX_PRIORITY) { + if (Binary_Value_Level[object_index][array_index] == + BINARY_NULL) + len = encode_tagged_null(&apdu[apdu_len]); + else { + present_value = + Binary_Value_Level[object_index][array_index]; + len = + encode_tagged_enumerated(&apdu[apdu_len], + present_value); + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = -1; + } + } + + break; + case PROP_RELINQUISH_DEFAULT: + present_value = RELINQUISH_DEFAULT; + apdu_len = encode_tagged_enumerated(&apdu[0], present_value); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +/* returns true if successful */ +bool Binary_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + bool status = false; /* return value */ + unsigned int object_index = 0; + unsigned int priority = 0; + BACNET_BINARY_PV level = BINARY_NULL; + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + Binary_Value_Init(); + if (!Binary_Value_Valid_Instance(wp_data->object_instance)) { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + /* decode the some of the request */ + len = bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + switch (wp_data->object_property) { + case PROP_PRESENT_VALUE: + if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) { + priority = wp_data->priority; + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */ ) && + (value.type.Enumerated >= MIN_BINARY_PV) && + (value.type.Enumerated <= MAX_BINARY_PV)) { + level = value.type.Enumerated; + object_index = + Binary_Value_Instance_To_Index(wp_data-> + object_instance); + priority--; + Binary_Value_Level[object_index][priority] = level; + /* Note: you could set the physical output here if we + are the highest priority. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } else if (priority == 6) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else if (value.tag == BACNET_APPLICATION_TAG_NULL) { + level = BINARY_NULL; + object_index = + Binary_Value_Instance_To_Index(wp_data->object_instance); + priority = wp_data->priority; + if (priority && (priority <= BACNET_MAX_PRIORITY)) { + priority--; + Binary_Value_Level[object_index][priority] = level; + /* Note: you could set the physical output here to the next + highest priority, or to the relinquish default if no + priorities are set. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_OUT_OF_SERVICE: + if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { + object_index = + Binary_Value_Instance_To_Index(wp_data->object_instance); + Binary_Value_Out_Of_Service[object_index] = value.type.Boolean; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} + + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testBinary_Value(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_BINARY_VALUE; + uint32_t decoded_instance = 0; + uint32_t instance = 123; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + + len = Binary_Value_Encode_Property_APDU(&apdu[0], + instance, + PROP_OBJECT_IDENTIFIER, + BACNET_ARRAY_ALL, &error_class, &error_code); + ct_test(pTest, len != 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == OBJECT_BINARY_VALUE); + ct_test(pTest, decoded_instance == instance); + + return; +} + +#ifdef TEST_BINARY_VALUE +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Binary_Value", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testBinary_Value); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_BINARY_VALUE */ +#endif /* TEST */ diff --git a/bacnet-stack/demo/object/bv.h b/bacnet-stack/demo/object/bv.h index e69de29b..30c0184d 100644 --- a/bacnet-stack/demo/object/bv.h +++ b/bacnet-stack/demo/object/bv.h @@ -0,0 +1,60 @@ +/************************************************************************** +* +* Copyright (C) 2006 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 BV_H +#define BV_H + +#include +#include +#include "bacdef.h" +#include "bacerror.h" +#include "wp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + bool Binary_Value_Valid_Instance(uint32_t object_instance); + unsigned Binary_Value_Count(void); + uint32_t Binary_Value_Index_To_Instance(unsigned index); + char *Binary_Value_Name(uint32_t object_instance); + + int Binary_Value_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + + bool Binary_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + +#ifdef TEST +#include "ctest.h" + void testBinary_Value(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/demo/object/device.c b/bacnet-stack/demo/object/device.c index e69de29b..fa542d92 100644 --- a/bacnet-stack/demo/object/device.c +++ b/bacnet-stack/demo/object/device.c @@ -0,0 +1,1117 @@ +/************************************************************************** +* +* Copyright (C) 2005,2006 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. +* +*********************************************************************/ + +#include +#include +#include /* for memmove */ +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bacapp.h" +#include "config.h" /* the custom stuff */ +#include "apdu.h" +#include "ai.h" /* object list dependency */ +#include "ao.h" /* object list dependency */ +#include "av.h" /* object list dependency */ +#include "bi.h" /* object list dependency */ +#include "bo.h" /* object list dependency */ +#include "bv.h" /* object list dependency */ +#include "lc.h" /* object list dependency */ +#include "lsp.h" /* object list dependency */ +#include "mso.h" /* object list dependency */ +#include "wp.h" /* write property handling */ +#include "device.h" /* me */ +#if BACFILE +#include "bacfile.h" /* object list dependency */ +#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 = 0; +static char Object_Name[16] = "SimpleServer"; +static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL; +static char Vendor_Name[16] = "ASHRAE"; +/* FIXME: your vendor id assigned by ASHRAE */ +static uint16_t Vendor_Identifier = 0; +static char Model_Name[16] = "GNU"; +static char Firmware_Revision[16] = "1.0"; +static char Application_Software_Version[16] = "1.0"; +static char Location[16] = "USA"; +static char Description[16] = "server"; +/* 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 uint16_t Max_APDU_Length_Accepted = MAX_APDU; - constant */ +/* static BACNET_SEGMENTATION Segmentation_Supported = SEGMENTATION_NONE; */ +/* static uint8_t Max_Segments_Accepted = 0; */ +/* VT_Classes_Supported */ +/* Active_VT_Sessions */ +BACNET_TIME Local_Time; /* rely on OS, if there is one */ +BACNET_DATE Local_Date; /* rely on OS, if there is one */ +/* BACnet UTC is inverse of standard offset - i.e. relative to local */ +static int UTC_Offset = 5; +static bool Daylight_Savings_Status = false; /* rely on OS */ +/* APDU_Segment_Timeout */ +static uint16_t APDU_Timeout = 3000; +static uint8_t Number_Of_APDU_Retries = 3; +/* 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 uint8_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 */ + +/* methods to manipulate the data */ +uint32_t Device_Object_Instance_Number(void) +{ + return Object_Instance_Number; +} + +bool Device_Set_Object_Instance_Number(uint32_t object_id) +{ + bool status = true; /* return value */ + + if (object_id <= BACNET_MAX_INSTANCE) + Object_Instance_Number = object_id; + else + status = false; + + return status; +} + +bool Device_Valid_Object_Instance_Number(uint32_t object_id) +{ + /* BACnet allows for a wildcard instance number */ + return ((Object_Instance_Number == object_id) || + (object_id == BACNET_MAX_INSTANCE)); +} + +const char *Device_Object_Name(void) +{ + return Object_Name; +} + +bool Device_Set_Object_Name(const char *name, size_t length) +{ + bool status = false; /*return value */ + + /* FIXME: All the object names in a device must be unique. + Disallow setting the Device Object Name to any objects in + the device. */ + if (length < sizeof(Object_Name)) { + memmove(Object_Name, name, length); + Object_Name[length] = 0; + status = true; + } + + return status; +} + +BACNET_DEVICE_STATUS Device_System_Status(void) +{ + return System_Status; +} + +void Device_Set_System_Status(BACNET_DEVICE_STATUS status) +{ + /* FIXME: bounds check? */ + System_Status = status; +} + +const char *Device_Vendor_Name(void) +{ + return Vendor_Name; +} + +bool Device_Set_Vendor_Name(const char *name, size_t length) +{ + bool status = false; /*return value */ + + if (length < sizeof(Vendor_Name)) { + memmove(Vendor_Name, name, length); + Vendor_Name[length] = 0; + status = true; + } + + return status; +} + +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 Firmware_Revision; +} + +bool Device_Set_Firmware_Revision(const char *name, size_t length) +{ + bool status = false; /*return value */ + + if (length < sizeof(Firmware_Revision)) { + memmove(Firmware_Revision, name, length); + Firmware_Revision[length] = 0; + status = true; + } + + return status; +} + +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; +} + +uint16_t Device_Max_APDU_Length_Accepted(void) +{ + return MAX_APDU; +} + +BACNET_SEGMENTATION Device_Segmentation_Supported(void) +{ + return SEGMENTATION_NONE; +} + +uint16_t Device_APDU_Timeout(void) +{ + return APDU_Timeout; +} + +/* in milliseconds */ +void Device_Set_APDU_Timeout(uint16_t timeout) +{ + APDU_Timeout = timeout; +} + +uint8_t Device_Number_Of_APDU_Retries(void) +{ + return Number_Of_APDU_Retries; +} + +void Device_Set_Number_Of_APDU_Retries(uint8_t retries) +{ + Number_Of_APDU_Retries = retries; +} + +uint8_t Device_Database_Revision(void) +{ + return Database_Revision; +} + +void Device_Set_Database_Revision(uint8_t revision) +{ + Database_Revision = revision; +} + +/* Since many network clients depend on the object list */ +/* for discovery, it must be consistent! */ +unsigned Device_Object_List_Count(void) +{ + unsigned count = 1; + + count += Analog_Input_Count(); + count += Analog_Output_Count(); + count += Analog_Value_Count(); + count += Binary_Input_Count(); + count += Binary_Output_Count(); + count += Binary_Value_Count(); + count += Life_Safety_Point_Count(); + count += Load_Control_Count(); + count += Multistate_Output_Count(); +#if BACFILE + count += bacfile_count(); +#endif + + return count; +} + +bool Device_Object_List_Identifier(unsigned array_index, + int *object_type, uint32_t * instance) +{ + bool status = false; + unsigned object_index = 0; + unsigned object_count = 0; + + /* device object */ + if (array_index == 1) { + *object_type = OBJECT_DEVICE; + *instance = Object_Instance_Number; + status = true; + } + /* analog input objects */ + if (!status) { + /* array index starts at 1, and 1 for the device object */ + object_index = array_index - 2; + object_count = Analog_Input_Count(); + if (object_index < object_count) { + *object_type = OBJECT_ANALOG_INPUT; + *instance = Analog_Input_Index_To_Instance(object_index); + status = true; + } + } + /* analog output objects */ + if (!status) { + /* normalize the index since + we know it is not the previous objects */ + object_index -= object_count; + object_count = Analog_Output_Count(); + /* is it a valid index for this object? */ + if (object_index < object_count) { + *object_type = OBJECT_ANALOG_OUTPUT; + *instance = Analog_Output_Index_To_Instance(object_index); + status = true; + } + } + /* analog value objects */ + if (!status) { + /* normalize the index since + we know it is not the previous objects */ + object_index -= object_count; + object_count = Analog_Value_Count(); + /* is it a valid index for this object? */ + if (object_index < object_count) { + *object_type = OBJECT_ANALOG_VALUE; + *instance = Analog_Value_Index_To_Instance(object_index); + status = true; + } + } + /* binary input objects */ + if (!status) { + /* normalize the index since + we know it is not the previous objects */ + object_index -= object_count; + object_count = Binary_Input_Count(); + /* is it a valid index for this object? */ + if (object_index < object_count) { + *object_type = OBJECT_BINARY_INPUT; + *instance = Binary_Input_Index_To_Instance(object_index); + status = true; + } + } + /* binary output objects */ + if (!status) { + /* normalize the index since + we know it is not the previous objects */ + object_index -= object_count; + object_count = Binary_Output_Count(); + /* is it a valid index for this object? */ + if (object_index < object_count) { + *object_type = OBJECT_BINARY_OUTPUT; + *instance = Binary_Output_Index_To_Instance(object_index); + status = true; + } + } + /* binary value objects */ + if (!status) { + /* normalize the index since + we know it is not the previous objects */ + object_index -= object_count; + object_count = Binary_Value_Count(); + /* is it a valid index for this object? */ + if (object_index < object_count) { + *object_type = OBJECT_BINARY_VALUE; + *instance = Binary_Value_Index_To_Instance(object_index); + status = true; + } + } + /* life safety point objects */ + if (!status) { + /* normalize the index since + we know it is not the previous objects */ + object_index -= object_count; + object_count = Life_Safety_Point_Count(); + /* is it a valid index for this object? */ + if (object_index < object_count) { + *object_type = OBJECT_LIFE_SAFETY_POINT; + *instance = Life_Safety_Point_Index_To_Instance(object_index); + status = true; + } + } + /* load control objects */ + if (!status) { + /* normalize the index since + we know it is not the previous objects */ + object_index -= object_count; + object_count = Load_Control_Count(); + /* is it a valid index for this object? */ + if (object_index < object_count) { + *object_type = OBJECT_LOAD_CONTROL; + *instance = Load_Control_Index_To_Instance(object_index); + status = true; + } + } + /* multi-state output objects */ + if (!status) { + /* normalize the index since + we know it is not the previous objects */ + object_index -= object_count; + object_count = Multistate_Output_Count(); + /* is it a valid index for this object? */ + if (object_index < object_count) { + *object_type = OBJECT_MULTI_STATE_OUTPUT; + *instance = Multistate_Output_Index_To_Instance(object_index); + status = true; + } + } + /* file objects */ +#if BACFILE + if (!status) { + /* normalize the index since + we know it is not the previous objects */ + object_index -= object_count; + object_count = bacfile_count(); + /* is it a valid index for this object? */ + if (object_index < object_count) { + *object_type = OBJECT_FILE; + *instance = bacfile_index_to_instance(object_index); + status = true; + } + } +#endif + + return status; +} + +bool Device_Valid_Object_Name(const char *object_name, + 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; + char *name = NULL; + + max_objects = Device_Object_List_Count(); + for (i = 0; i < max_objects; i++) { + check_id = Device_Object_List_Identifier(i, &type, &instance); + if (check_id) { + name = Device_Valid_Object_Id(type, instance); + if (strcmp(name, object_name) == 0) { + found = true; + if (object_type) + *object_type = type; + if (object_instance) + *object_instance = instance; + break; + } + } + } + + return found; +} + +/* returns the name or NULL if not found */ +char *Device_Valid_Object_Id(int object_type, uint32_t object_instance) +{ + char *name = NULL; /* return value */ + + switch (object_type) { + case OBJECT_ANALOG_INPUT: + name = Analog_Input_Name(object_instance); + break; + case OBJECT_ANALOG_OUTPUT: + name = Analog_Output_Name(object_instance); + break; + case OBJECT_ANALOG_VALUE: + name = Analog_Value_Name(object_instance); + break; + case OBJECT_BINARY_INPUT: + name = Binary_Input_Name(object_instance); + break; + case OBJECT_BINARY_OUTPUT: + name = Binary_Output_Name(object_instance); + break; + case OBJECT_BINARY_VALUE: + name = Binary_Value_Name(object_instance); + break; + case OBJECT_LIFE_SAFETY_POINT: + name = Life_Safety_Point_Name(object_instance); + break; + case OBJECT_LOAD_CONTROL: + name = Load_Control_Name(object_instance); + break; + case OBJECT_MULTI_STATE_OUTPUT: + name = Multistate_Output_Name(object_instance); + break; +#if BACFILE + case OBJECT_FILE: + name = bacfile_name(object_instance); + break; +#endif + case OBJECT_DEVICE: + if (object_instance == Object_Instance_Number) + name = Object_Name; + break; + default: + break; + } + + return name; +} + +/* return the length of the apdu encoded or -1 for error or + -2 for abort message */ +int Device_Encode_Property_APDU(uint8_t * apdu, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + 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; + + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_DEVICE, + Object_Instance_Number); + break; + case PROP_OBJECT_NAME: + characterstring_init_ansi(&char_string, Object_Name); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_tagged_enumerated(&apdu[0], OBJECT_DEVICE); + break; + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, Description); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_SYSTEM_STATUS: + apdu_len = encode_tagged_enumerated(&apdu[0], System_Status); + break; + case PROP_VENDOR_NAME: + characterstring_init_ansi(&char_string, Vendor_Name); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_VENDOR_IDENTIFIER: + apdu_len = encode_tagged_unsigned(&apdu[0], Vendor_Identifier); + break; + case PROP_MODEL_NAME: + characterstring_init_ansi(&char_string, Model_Name); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_FIRMWARE_REVISION: + characterstring_init_ansi(&char_string, Firmware_Revision); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_APPLICATION_SOFTWARE_VERSION: + characterstring_init_ansi(&char_string, + Application_Software_Version); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + /* FIXME: if you support time */ + case PROP_LOCAL_TIME: + /* FIXME: get the actual value */ + Local_Time.hour = 7; + Local_Time.min = 0; + Local_Time.sec = 3; + Local_Time.hundredths = 1; + apdu_len = encode_tagged_time(&apdu[0], &Local_Time); + break; + /* FIXME: if you support time */ + case PROP_UTC_OFFSET: + /* NOTE: if your UTC offset is -5, then BACnet UTC offset is 5 */ + apdu_len = encode_tagged_signed(&apdu[0], UTC_Offset); + break; + /* FIXME: if you support date */ + case PROP_LOCAL_DATE: + /* FIXME: get the actual value instead of April Fool's Day */ + Local_Date.year = 2006; /* AD */ + Local_Date.month = 4; /* 1=Jan */ + Local_Date.day = 1; /* 1..31 */ + Local_Date.wday = 6; /* 1=Monday */ + apdu_len = encode_tagged_date(&apdu[0], &Local_Date); + break; + case PROP_DAYLIGHT_SAVINGS_STATUS: + apdu_len = + encode_tagged_boolean(&apdu[0], Daylight_Savings_Status); + break; + case PROP_PROTOCOL_VERSION: + apdu_len = + encode_tagged_unsigned(&apdu[0], Device_Protocol_Version()); + break; + case PROP_PROTOCOL_REVISION: + apdu_len = + encode_tagged_unsigned(&apdu[0], Device_Protocol_Revision()); + break; + /* BACnet Legacy Support */ + case PROP_PROTOCOL_CONFORMANCE_CLASS: + apdu_len = encode_tagged_unsigned(&apdu[0], 1); + 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(i)); + } + apdu_len = encode_tagged_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); + } + /* FIXME: indicate the objects that YOU support */ + bitstring_set_bit(&bit_string, OBJECT_DEVICE, true); + if (Analog_Input_Count()) + bitstring_set_bit(&bit_string, OBJECT_ANALOG_INPUT, true); + if (Analog_Output_Count()) + bitstring_set_bit(&bit_string, OBJECT_ANALOG_OUTPUT, true); + if (Analog_Value_Count()) + bitstring_set_bit(&bit_string, OBJECT_ANALOG_VALUE, true); + if (Binary_Input_Count()) + bitstring_set_bit(&bit_string, OBJECT_BINARY_INPUT, true); + if (Binary_Output_Count()) + bitstring_set_bit(&bit_string, OBJECT_BINARY_OUTPUT, true); + if (Binary_Value_Count()) + bitstring_set_bit(&bit_string, OBJECT_BINARY_VALUE, true); + if (Life_Safety_Point_Count()) + bitstring_set_bit(&bit_string, OBJECT_LIFE_SAFETY_POINT, true); + if (Load_Control_Count()) + bitstring_set_bit(&bit_string, OBJECT_LOAD_CONTROL, true); + if (Multistate_Output_Count()) + bitstring_set_bit(&bit_string, OBJECT_MULTI_STATE_OUTPUT, + true); +#if BACFILE + if (bacfile_count()) + bitstring_set_bit(&bit_string, OBJECT_FILE, true); +#endif + apdu_len = encode_tagged_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 (array_index == 0) + apdu_len = encode_tagged_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 (array_index == BACNET_ARRAY_ALL) { + for (i = 1; i <= count; i++) { + if (Device_Object_List_Identifier(i, &object_type, + &instance)) { + len = + encode_tagged_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? */ + if ((apdu_len + len) >= MAX_APDU) { + /* reject message */ + apdu_len = -2; + break; + } + } else { + /* error: internal error? */ + *error_class = ERROR_CLASS_SERVICES; + *error_code = ERROR_CODE_OTHER; + apdu_len = -1; + break; + } + } + } else { + if (Device_Object_List_Identifier(array_index, &object_type, + &instance)) + apdu_len = + encode_tagged_object_id(&apdu[0], object_type, + instance); + else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = -1; + } + } + break; + case PROP_MAX_APDU_LENGTH_ACCEPTED: + apdu_len = encode_tagged_unsigned(&apdu[0], + Device_Max_APDU_Length_Accepted()); + break; + case PROP_SEGMENTATION_SUPPORTED: + apdu_len = encode_tagged_enumerated(&apdu[0], + Device_Segmentation_Supported()); + break; + case PROP_APDU_TIMEOUT: + apdu_len = encode_tagged_unsigned(&apdu[0], APDU_Timeout); + break; + case PROP_NUMBER_OF_APDU_RETRIES: + apdu_len = + encode_tagged_unsigned(&apdu[0], Number_Of_APDU_Retries); + break; + case PROP_DEVICE_ADDRESS_BINDING: + /* FIXME: encode the list here, if it exists */ + break; + case PROP_DATABASE_REVISION: + apdu_len = encode_tagged_unsigned(&apdu[0], Database_Revision); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +/* returns true if successful */ +bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + if (!Device_Valid_Object_Instance_Number(wp_data->object_instance)) { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + /* decode the some of the request */ + len = bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + switch (wp_data->object_property) { + case PROP_OBJECT_IDENTIFIER: + if (value.tag == BACNET_APPLICATION_TAG_OBJECT_ID) { + if ((value.type.Object_Id.type == OBJECT_DEVICE) && + (Device_Set_Object_Instance_Number(value.type.Object_Id. + instance))) { + /* FIXME: we could send an I-Am broadcast to let the world know */ + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + + case PROP_NUMBER_OF_APDU_RETRIES: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + /* FIXME: bounds check? */ + Device_Set_Number_Of_APDU_Retries((uint8_t) value. + type.Unsigned_Int); + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + + case PROP_APDU_TIMEOUT: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + /* FIXME: bounds check? */ + Device_Set_APDU_Timeout((uint16_t) value.type.Unsigned_Int); + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + + case PROP_VENDOR_IDENTIFIER: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + /* FIXME: bounds check? */ + Device_Set_Vendor_Identifier((uint16_t) value.type. + Unsigned_Int); + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + + case PROP_SYSTEM_STATUS: + if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) { + Device_Set_System_Status(value.type.Enumerated); + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + + case PROP_OBJECT_NAME: + if (value.tag == BACNET_APPLICATION_TAG_CHARACTER_STRING) { + uint8_t encoding; + encoding = + characterstring_encoding(&value.type.Character_String); + if (encoding == CHARACTER_ANSI_X34) { + status = + Device_Set_Object_Name(characterstring_value(&value. + type.Character_String), + characterstring_length(&value.type.Character_String)); + if (!status) { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_CHARACTER_SET_NOT_SUPPORTED; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testDevice(Test * pTest) +{ + bool status = false; + const char *name = "Patricia"; + + status = Device_Set_Object_Instance_Number(0); + ct_test(pTest, Device_Object_Instance_Number() == 0); + ct_test(pTest, status == true); + status = Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + ct_test(pTest, Device_Object_Instance_Number() == BACNET_MAX_INSTANCE); + ct_test(pTest, status == true); + status = Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE / 2); + ct_test(pTest, + Device_Object_Instance_Number() == (BACNET_MAX_INSTANCE / 2)); + ct_test(pTest, status == true); + status = Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE + 1); + ct_test(pTest, + Device_Object_Instance_Number() != (BACNET_MAX_INSTANCE + 1)); + ct_test(pTest, status == false); + + + Device_Set_System_Status(STATUS_NON_OPERATIONAL); + ct_test(pTest, Device_System_Status() == STATUS_NON_OPERATIONAL); + + Device_Set_Vendor_Name(name, strlen(name)); + ct_test(pTest, strcmp(Device_Vendor_Name(), name) == 0); + + Device_Set_Vendor_Identifier(42); + ct_test(pTest, Device_Vendor_Identifier() == 42); + + Device_Set_Model_Name(name, strlen(name)); + ct_test(pTest, strcmp(Device_Model_Name(), name) == 0); + + return; +} + +#ifdef TEST_DEVICE +/* stubs to dependencies to keep unit test simple */ +char *Analog_Input_Name(uint32_t object_instance) +{ + (void) object_instance; + return ""; +} + +unsigned Analog_Input_Count(void) +{ + return 0; +} + +uint32_t Analog_Input_Index_To_Instance(unsigned index) +{ + return index; +} + +char *Analog_Output_Name(uint32_t object_instance) +{ + (void) object_instance; + return ""; +} + +unsigned Analog_Output_Count(void) +{ + return 0; +} + +uint32_t Analog_Output_Index_To_Instance(unsigned index) +{ + return index; +} + +char *Analog_Value_Name(uint32_t object_instance) +{ + (void) object_instance; + return ""; +} + +unsigned Analog_Value_Count(void) +{ + return 0; +} + +uint32_t Analog_Value_Index_To_Instance(unsigned index) +{ + return index; +} + +char *Binary_Input_Name(uint32_t object_instance) +{ + (void) object_instance; + return ""; +} + +unsigned Binary_Input_Count(void) +{ + return 0; +} + +uint32_t Binary_Input_Index_To_Instance(unsigned index) +{ + return index; +} + +char *Binary_Output_Name(uint32_t object_instance) +{ + (void) object_instance; + return ""; +} + +unsigned Binary_Output_Count(void) +{ + return 0; +} + +uint32_t Binary_Output_Index_To_Instance(unsigned index) +{ + return index; +} + +char *Binary_Value_Name(uint32_t object_instance) +{ + (void) object_instance; + return ""; +} + +unsigned Binary_Value_Count(void) +{ + return 0; +} + +uint32_t Binary_Value_Index_To_Instance(unsigned index) +{ + return index; +} + +char *Life_Safety_Point_Name(uint32_t object_instance) +{ + (void) object_instance; + return ""; +} + +unsigned Life_Safety_Point_Count(void) +{ + return 0; +} + +uint32_t Life_Safety_Point_Index_To_Instance(unsigned index) +{ + return index; +} + +char *Load_Control_Name(uint32_t object_instance) +{ + (void) object_instance; + return ""; +} + +unsigned Load_Control_Count(void) +{ + return 0; +} + +uint32_t Load_Control_Index_To_Instance(unsigned index) +{ + return index; +} + +char *Multistate_Output_Name(uint32_t object_instance) +{ + (void) object_instance; + return ""; +} + +unsigned Multistate_Output_Count(void) +{ + return 0; +} + +uint32_t Multistate_Output_Index_To_Instance(unsigned index) +{ + return index; +} + +uint32_t bacfile_count(void) +{ + return 0; +} + +uint32_t bacfile_index_to_instance(unsigned find_index) +{ + return find_index; +} + +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Device", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testDevice); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_DEVICE */ +#endif /* TEST */ diff --git a/bacnet-stack/demo/object/device.h b/bacnet-stack/demo/object/device.h index e69de29b..c59bac37 100644 --- a/bacnet-stack/demo/object/device.h +++ b/bacnet-stack/demo/object/device.h @@ -0,0 +1,110 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef DEVICE_H +#define DEVICE_H + +#include +#include +#include "bacdef.h" +#include "bacenum.h" +#include "wp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + 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); + + BACNET_DEVICE_STATUS Device_System_Status(void); + void Device_Set_System_Status(BACNET_DEVICE_STATUS status); + + const char *Device_Vendor_Name(void); + bool Device_Set_Vendor_Name(const char *name, size_t length); + + 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); + bool Device_Set_Firmware_Revision(const char *name, size_t length); + + 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); + uint16_t Device_Max_APDU_Length_Accepted(void); + BACNET_SEGMENTATION Device_Segmentation_Supported(void); + + uint16_t Device_APDU_Timeout(void); + void Device_Set_APDU_Timeout(uint16_t timeout); + + uint8_t Device_Number_Of_APDU_Retries(void); + void Device_Set_Number_Of_APDU_Retries(uint8_t retries); + + uint8_t Device_Database_Revision(void); + void Device_Set_Database_Revision(uint8_t revision); + + bool Device_Valid_Object_Name(const char *object_name, + int *object_type, uint32_t * object_instance); + char *Device_Valid_Object_Id(int object_type, + uint32_t object_instance); + + int Device_Encode_Property_APDU(uint8_t * apdu, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + + bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/demo/object/lc.c b/bacnet-stack/demo/object/lc.c index e69de29b..1174792d 100644 --- a/bacnet-stack/demo/object/lc.c +++ b/bacnet-stack/demo/object/lc.c @@ -0,0 +1,1411 @@ +/************************************************************************** +* +* Copyright (C) 2007 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. +* +*********************************************************************/ + +/* Load Control Objects - customize for your use */ + +#include +#include +#include +#include /* for memcpy */ +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "datetime.h" +#include "bacenum.h" +#include "config.h" /* the custom stuff */ +#include "lc.h" +#include "ao.h" +#include "wp.h" + +/* number of demo objects */ +#define MAX_LOAD_CONTROLS 4 + +/* indicates the current load shedding state of the object */ +static BACNET_SHED_STATE Present_Value[MAX_LOAD_CONTROLS]; + +/* load control objects are required to support LEVEL */ +typedef enum BACnetShedLevelType { + BACNET_SHED_TYPE_PERCENT, /* Unsigned */ + BACNET_SHED_TYPE_LEVEL, /* Unsigned */ + BACNET_SHED_TYPE_AMOUNT /* REAL */ +} BACNET_SHED_LEVEL_TYPE; + +#define DEFAULT_VALUE_PERCENT 100 +#define DEFAULT_VALUE_LEVEL 0 +#define DEFAULT_VALUE_AMOUNT 0 + +/* The shed levels for the LEVEL choice of BACnetShedLevel + that have meaning for this particular Load Control object. */ +typedef struct { + BACNET_SHED_LEVEL_TYPE type; + union { + unsigned level; + unsigned percent; + float amount; + } value; +} BACNET_SHED_LEVEL; +/* indicates the desired load shedding */ +static BACNET_SHED_LEVEL Requested_Shed_Level[MAX_LOAD_CONTROLS]; +/* Indicates the amount of power that the object expects + to be able to shed in response to a load shed request. */ +static BACNET_SHED_LEVEL Expected_Shed_Level[MAX_LOAD_CONTROLS]; +/* Indicates the actual amount of power being shed in response + to a load shed request. */ +static BACNET_SHED_LEVEL Actual_Shed_Level[MAX_LOAD_CONTROLS]; + +/* indicates the start of the duty window in which the load controlled + by the Load Control object must be compliant with the requested shed. */ +static BACNET_DATE_TIME Start_Time[MAX_LOAD_CONTROLS]; +static BACNET_DATE_TIME End_Time[MAX_LOAD_CONTROLS]; +static BACNET_DATE_TIME Current_Time; + +/* indicates the duration of the load shed action, + starting at Start_Time in minutes */ +static uint32_t Shed_Duration[MAX_LOAD_CONTROLS]; + +/* indicates the time window used for load shed accounting in minutes */ +static uint32_t Duty_Window[MAX_LOAD_CONTROLS]; + +/* indicates and controls whether the Load Control object is + currently enabled to respond to load shed requests. */ +static bool Load_Control_Enable[MAX_LOAD_CONTROLS]; + +/* indicates when the object receives a write to any of the properties + Requested_Shed_Level, Shed_Duration, Duty_Window */ +static bool Load_Control_Request_Written[MAX_LOAD_CONTROLS]; +/* indicates when the object receives a write to Start_Time */ +static bool Start_Time_Property_Written[MAX_LOAD_CONTROLS]; + +/* optional: indicates the baseline power consumption value + for the sheddable load controlled by this object, + if a fixed baseline is used. + The units of Full_Duty_Baseline are kilowatts.*/ +static float Full_Duty_Baseline[MAX_LOAD_CONTROLS]; + +#define MAX_SHED_LEVELS 3 +/* Represents the shed levels for the LEVEL choice of + BACnetShedLevel that have meaning for this particular + Load Control object. */ +/* The elements of the array are required to be writable, + allowing local configuration of how this Load Control + object will participate in load shedding for the + facility. This array is not required to be resizable + through BACnet write services. The size of this array + shall be equal to the size of the Shed_Level_Descriptions + array. The behavior of this object when the Shed_Levels + array contains duplicate entries is a local matter. */ +static unsigned Shed_Levels[MAX_LOAD_CONTROLS][MAX_SHED_LEVELS]; + +/* represents a description of the shed levels that the + Load Control object can take on. It is the same for + all the load control objects in this example device. */ +static char *Shed_Level_Descriptions[MAX_SHED_LEVELS] = { + "dim lights 10%", + "dim lights 20%", + "dim lights 30%" +}; +static float Shed_Level_Values[MAX_SHED_LEVELS] = { + 90.0, + 80.0, + 70.0 +}; + + +/* we need to have our arrays initialized before answering any calls */ +static bool Load_Control_Initialized = false; + +void Load_Control_Init(void) +{ + unsigned i, j; + + if (!Load_Control_Initialized) { + Load_Control_Initialized = true; + for (i = 0; i < MAX_LOAD_CONTROLS; i++) { + /* FIXME: load saved data? */ + Present_Value[i] = BACNET_SHED_INACTIVE; + Requested_Shed_Level[i].type = BACNET_SHED_TYPE_LEVEL; + Requested_Shed_Level[i].value.level = 0; + datetime_wildcard_set(&Start_Time[i]); + datetime_wildcard_set(&End_Time[i]); + datetime_wildcard_set(&Current_Time); + Shed_Duration[i] = 0; + Duty_Window[i] = 0; + Load_Control_Enable[i] = true; + Full_Duty_Baseline[i] = 1.500; /* kilowatts */ + Expected_Shed_Level[i].type = BACNET_SHED_TYPE_LEVEL; + Expected_Shed_Level[i].value.level = 0; + Actual_Shed_Level[i].type = BACNET_SHED_TYPE_LEVEL; + Actual_Shed_Level[i].value.level = 0; + Load_Control_Request_Written[i] = false; + Start_Time_Property_Written[i] = false; + for (j = 0; j < MAX_SHED_LEVELS; j++) { + Shed_Levels[i][j] = j + 1; + } + } + } + + return; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need validate that the */ +/* given instance exists */ +bool Load_Control_Valid_Instance(uint32_t object_instance) +{ + Load_Control_Init(); + if (object_instance < MAX_LOAD_CONTROLS) + return true; + + return false; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then count how many you have */ +unsigned Load_Control_Count(void) +{ + Load_Control_Init(); + return MAX_LOAD_CONTROLS; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the instance */ +/* that correlates to the correct index */ +uint32_t Load_Control_Index_To_Instance(unsigned index) +{ + Load_Control_Init(); + return index; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the index */ +/* that correlates to the correct instance number */ +unsigned Load_Control_Instance_To_Index(uint32_t object_instance) +{ + unsigned index = MAX_LOAD_CONTROLS; + + Load_Control_Init(); + if (object_instance < MAX_LOAD_CONTROLS) + index = object_instance; + + return index; +} + +static BACNET_SHED_STATE Load_Control_Present_Value(uint32_t + object_instance) +{ + BACNET_SHED_STATE value = BACNET_SHED_INACTIVE; + unsigned index = 0; + + Load_Control_Init(); + index = Load_Control_Instance_To_Index(object_instance); + if (index < MAX_LOAD_CONTROLS) { + value = Present_Value[index]; + } + + return value; +} + +/* note: the object name must be unique within this device */ +char *Load_Control_Name(uint32_t object_instance) +{ + static char text_string[32] = ""; /* okay for single thread */ + + if (object_instance < MAX_LOAD_CONTROLS) { + sprintf(text_string, "LOAD CONTROL %u", object_instance); + return text_string; + } + + return NULL; +} + +static void Update_Current_Time(BACNET_DATE_TIME * bdatetime) +{ + time_t timer; + struct tm *tblock; + +/* +struct tm { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; +}; +*/ + + timer = time(NULL); + tblock = localtime(&timer); + datetime_set_values(bdatetime, + (uint16_t) tblock->tm_year, + (uint8_t) tblock->tm_mon, + (uint8_t) tblock->tm_mday, + (uint8_t) tblock->tm_hour, + (uint8_t) tblock->tm_min, (uint8_t) tblock->tm_sec, 0); +} + +/* convert the shed level request into an Analog Output Present_Value */ +static float Requested_Shed_Level_Value(int object_index) +{ + unsigned shed_level_index = 0; + unsigned i = 0; + float requested_level = 0.0; + + switch (Requested_Shed_Level[object_index].type) { + case BACNET_SHED_TYPE_PERCENT: + requested_level = + (float) Requested_Shed_Level[object_index].value.percent; + break; + case BACNET_SHED_TYPE_AMOUNT: + /* Assumptions: wattage is linear with analog output level */ + requested_level = + Full_Duty_Baseline[object_index] - + Requested_Shed_Level[object_index].value.amount; + requested_level /= Full_Duty_Baseline[object_index]; + requested_level *= 100.0; + break; + case BACNET_SHED_TYPE_LEVEL: + default: + for (i = 0; i < MAX_SHED_LEVELS; i++) { + if (Shed_Levels[object_index][i] <= + Requested_Shed_Level[object_index].value.level) + shed_level_index = i; + } + requested_level = Shed_Level_Values[shed_level_index]; + break; + } + + return requested_level; +} + +static void Shed_Level_Copy(BACNET_SHED_LEVEL * dest, + BACNET_SHED_LEVEL * src) +{ + if (dest && src) { + dest->type = src->type; + switch (src->type) { + case BACNET_SHED_TYPE_PERCENT: + dest->value.percent = src->value.percent; + break; + case BACNET_SHED_TYPE_AMOUNT: + dest->value.amount = src->value.amount; + break; + case BACNET_SHED_TYPE_LEVEL: + default: + dest->value.level = src->value.level; + break; + } + } +} + +static void Shed_Level_Default_Set(BACNET_SHED_LEVEL * dest, + BACNET_SHED_LEVEL_TYPE type) +{ + if (dest) { + dest->type = type; + switch (type) { + case BACNET_SHED_TYPE_PERCENT: + dest->value.percent = 100; + break; + case BACNET_SHED_TYPE_AMOUNT: + dest->value.amount = 0.0; + break; + case BACNET_SHED_TYPE_LEVEL: + default: + dest->value.level = 0; + break; + } + } +} + +static bool Able_To_Meet_Shed_Request(int object_index) +{ + float level = 0.0; + float requested_level = 0.0; + unsigned priority = 0; + bool status = false; + int object_instance = 0; + + /* This demo is going to use the Analog Outputs as their Load */ + object_instance = object_index; + priority = Analog_Output_Present_Value_Priority(object_instance); + /* we are controlling at Priority 4 - can we control the output? */ + if (priority >= 4) { + /* is the level able to be lowered? */ + requested_level = Requested_Shed_Level_Value(object_index); + level = Analog_Output_Present_Value(object_instance); + if (level >= requested_level) { + status = true; + } + } + + return status; +} + +typedef enum load_control_state { + SHED_INACTIVE, + SHED_REQUEST_PENDING, + SHED_NON_COMPLIANT, + SHED_COMPLIANT, + MAX_LOAD_CONTROL_STATE +} LOAD_CONTROL_STATE; +static LOAD_CONTROL_STATE Load_Control_State[MAX_LOAD_CONTROLS]; +static LOAD_CONTROL_STATE Load_Control_State_Previously[MAX_LOAD_CONTROLS]; + +#if PRINT_ENABLED_DEBUG +static void Print_Load_Control_State(int object_index) +{ + char *Load_Control_State_Text[MAX_LOAD_CONTROLS] = { + "SHED_INACTIVE", + "SHED_REQUEST_PENDING", + "SHED_NON_COMPLIANT", + "SHED_COMPLIANT" + }; + + if (object_index < MAX_LOAD_CONTROLS) { + if (Load_Control_State[object_index] < MAX_LOAD_CONTROL_STATE) { + printf("Load Control[%d]=%s\n", object_index, + Load_Control_State_Text[Load_Control_State[object_index]]); + } + } +} +#endif + +void Load_Control_State_Machine(int object_index) +{ + unsigned i = 0; /* loop counter */ + int diff = 0; /* used for datetime comparison */ + + /* is the state machine enabled? */ + if (!Load_Control_Enable[object_index]) { + Load_Control_State[object_index] = SHED_INACTIVE; + return; + } + + switch (Load_Control_State[object_index]) { + case SHED_REQUEST_PENDING: + if (Load_Control_Request_Written[object_index]) { + Load_Control_Request_Written[object_index] = false; + /* request to cancel using default values? */ + switch (Requested_Shed_Level[object_index].type) { + case BACNET_SHED_TYPE_PERCENT: + if (Requested_Shed_Level[object_index].value.percent == + DEFAULT_VALUE_PERCENT) + Load_Control_State[object_index] = SHED_INACTIVE; + break; + case BACNET_SHED_TYPE_AMOUNT: + if (Requested_Shed_Level[object_index].value.amount == + DEFAULT_VALUE_AMOUNT) + Load_Control_State[object_index] = SHED_INACTIVE; + break; + case BACNET_SHED_TYPE_LEVEL: + default: + if (Requested_Shed_Level[object_index].value.level == + DEFAULT_VALUE_LEVEL) + Load_Control_State[object_index] = SHED_INACTIVE; + break; + } + if (Load_Control_State[object_index] == SHED_INACTIVE) { +#if PRINT_ENABLED_DEBUG + printf("Load Control[%d]:Requested Shed Level=Default\n", + object_index); +#endif + break; + } + } + /* clear the flag for Start time if it is written */ + if (Start_Time_Property_Written[object_index]) { + Start_Time_Property_Written[object_index] = false; + /* request to cancel using wildcards in start time? */ + if (datetime_wildcard(&Start_Time[object_index])) { + Load_Control_State[object_index] = SHED_INACTIVE; +#if PRINT_ENABLED_DEBUG + printf("Load Control[%d]:Start Time=Wildcard\n", + object_index); +#endif + break; + } + } + /* cancel because current time is after start time + duration? */ + datetime_copy(&End_Time[object_index], &Start_Time[object_index]); + datetime_add_minutes(&End_Time[object_index], + Shed_Duration[object_index]); + diff = datetime_compare(&End_Time[object_index], &Current_Time); + if (diff < 0) { + /* CancelShed */ + /* FIXME: stop shedding! i.e. relinquish */ +#if PRINT_ENABLED_DEBUG + printf("Load Control[%d]:Current Time" + " is after Start Time + Duration\n", + object_index); +#endif + Load_Control_State[object_index] = SHED_INACTIVE; + break; + } + diff = datetime_compare(&Current_Time, &Start_Time[object_index]); + if (diff < 0) { + /* current time prior to start time */ + /* ReconfigurePending */ + Shed_Level_Copy(&Expected_Shed_Level[object_index], + &Requested_Shed_Level[object_index]); + Shed_Level_Default_Set(&Actual_Shed_Level[object_index], + Requested_Shed_Level[object_index].type); + } else if (diff > 0) { + /* current time after to start time */ +#if PRINT_ENABLED_DEBUG + printf("Load Control[%d]:Current Time" + " is after Start Time\n", + object_index); +#endif + /* AbleToMeetShed */ + if (Able_To_Meet_Shed_Request(object_index)) { + Shed_Level_Copy(&Expected_Shed_Level[object_index], + &Requested_Shed_Level[object_index]); + Analog_Output_Present_Value_Set(object_index, + Requested_Shed_Level_Value(object_index), 4); + Shed_Level_Copy(&Actual_Shed_Level[object_index], + &Requested_Shed_Level[object_index]); + Load_Control_State[object_index] = SHED_COMPLIANT; + } else { + /* CannotMeetShed */ + Shed_Level_Default_Set(&Expected_Shed_Level[object_index], + Requested_Shed_Level[object_index].type); + Shed_Level_Default_Set(&Actual_Shed_Level[object_index], + Requested_Shed_Level[object_index].type); + Load_Control_State[object_index] = SHED_NON_COMPLIANT; + } + } + break; + case SHED_NON_COMPLIANT: + datetime_copy(&End_Time[object_index], &Start_Time[object_index]); + datetime_add_minutes(&End_Time[object_index], + Shed_Duration[object_index]); + diff = datetime_compare(&End_Time[object_index], &Current_Time); + if (diff < 0) { + /* FinishedUnsuccessfulShed */ +#if PRINT_ENABLED_DEBUG + printf + ("Load Control[%d]:Current Time is after Start Time + Duration\n", + object_index); +#endif + Load_Control_State[object_index] = SHED_INACTIVE; + break; + } + if (Load_Control_Request_Written[object_index] || + Start_Time_Property_Written[object_index]) { + /* UnsuccessfulShedReconfigured */ +#if PRINT_ENABLED_DEBUG + printf("Load Control[%d]:Control Property written\n", + object_index); +#endif + /* The Written flags will cleared in the next state */ + Load_Control_State[object_index] = SHED_REQUEST_PENDING; + break; + } + if (Able_To_Meet_Shed_Request(object_index)) { + /* CanNowComplyWithShed */ +#if PRINT_ENABLED_DEBUG + printf("Load Control[%d]:Able to meet Shed Request\n", + object_index); +#endif + Shed_Level_Copy(&Expected_Shed_Level[object_index], + &Requested_Shed_Level[object_index]); + Analog_Output_Present_Value_Set(object_index, + Requested_Shed_Level_Value(object_index), 4); + Shed_Level_Copy(&Actual_Shed_Level[object_index], + &Requested_Shed_Level[object_index]); + Load_Control_State[object_index] = SHED_COMPLIANT; + } + break; + case SHED_COMPLIANT: + datetime_copy(&End_Time[object_index], &Start_Time[object_index]); + datetime_add_minutes(&End_Time[object_index], + Shed_Duration[object_index]); + diff = datetime_compare(&End_Time[object_index], &Current_Time); + if (diff < 0) { + /* FinishedSuccessfulShed */ +#if PRINT_ENABLED_DEBUG + printf + ("Load Control[%d]:Current Time is after Start Time + Duration\n", + object_index); +#endif + datetime_wildcard_set(&Start_Time[i]); + Analog_Output_Present_Value_Relinquish(object_index, 4); + Load_Control_State[object_index] = SHED_INACTIVE; + break; + } + if (Load_Control_Request_Written[object_index] || + Start_Time_Property_Written[object_index]) { + /* UnsuccessfulShedReconfigured */ +#if PRINT_ENABLED_DEBUG + printf("Load Control[%d]:Control Property written\n", + object_index); +#endif + /* The Written flags will cleared in the next state */ + Load_Control_State[object_index] = SHED_REQUEST_PENDING; + break; + } + if (!Able_To_Meet_Shed_Request(object_index)) { + /* CanNoLongerComplyWithShed */ +#if PRINT_ENABLED_DEBUG + printf("Load Control[%d]:Not able to meet Shed Request\n", + object_index); +#endif + Shed_Level_Default_Set(&Expected_Shed_Level[object_index], + Requested_Shed_Level[object_index].type); + Shed_Level_Default_Set(&Actual_Shed_Level[object_index], + Requested_Shed_Level[object_index].type); + Load_Control_State[object_index] = SHED_NON_COMPLIANT; + } + break; + case SHED_INACTIVE: + default: + if (Start_Time_Property_Written[object_index]) { +#if PRINT_ENABLED_DEBUG + printf("Load Control[%d]:Start Time written\n", object_index); +#endif + /* The Written flag will cleared in the next state */ + Shed_Level_Copy(&Expected_Shed_Level[object_index], + &Requested_Shed_Level[object_index]); + Shed_Level_Default_Set(&Actual_Shed_Level[object_index], + Requested_Shed_Level[object_index].type); + Load_Control_State[object_index] = SHED_REQUEST_PENDING; + } + break; + } + + return; +} + +/* call every second or so */ +void Load_Control_State_Machine_Handler(void) +{ + unsigned i = 0; + static bool initialized = false; + + Load_Control_Init(); + if (!initialized) { + initialized = true; + for (i = 0; i < MAX_LOAD_CONTROLS; i++) { + Load_Control_State[i] = SHED_INACTIVE; + Load_Control_State_Previously[i] = SHED_INACTIVE; + } + } + Update_Current_Time(&Current_Time); + for (i = 0; i < MAX_LOAD_CONTROLS; i++) { + Load_Control_State_Machine(i); + if (Load_Control_State[i] != Load_Control_State_Previously[i]) { +#if PRINT_ENABLED_DEBUG + Print_Load_Control_State(i); +#endif + Load_Control_State_Previously[i] = Load_Control_State[i]; + } + + + } +} + +/* return apdu len, or -1 on error */ +int Load_Control_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int len = 0; + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + int enumeration = 0; + unsigned object_index = 0; + unsigned i = 0; + bool state = false; + + Load_Control_Init(); + object_index = Load_Control_Instance_To_Index(object_instance); + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_LOAD_CONTROL, + object_instance); + break; + case PROP_OBJECT_NAME: + characterstring_init_ansi(&char_string, + Load_Control_Name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_tagged_enumerated(&apdu[0], OBJECT_LOAD_CONTROL); + break; + /* optional property + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string,"optional description"); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + */ + case PROP_PRESENT_VALUE: + enumeration = Load_Control_Present_Value(object_instance); + apdu_len = encode_tagged_enumerated(&apdu[0], enumeration); + break; + case PROP_STATUS_FLAGS: + bitstring_init(&bit_string); + /* IN_ALARM - Logical FALSE (0) if the Event_State property + has a value of NORMAL, otherwise logical TRUE (1). */ + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + /* FAULT - Logical TRUE (1) if the Reliability property is + present and does not have a value of NO_FAULT_DETECTED, + otherwise logical FALSE (0). */ + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + /* OVERRIDDEN - Logical TRUE (1) if the point has been + overridden by some mechanism local to the BACnet Device, + otherwise logical FALSE (0). */ + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + /* OUT_OF_SERVICE - This bit shall always be Logical FALSE (0). */ + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_REQUESTED_SHED_LEVEL: + switch (Requested_Shed_Level[object_index].type) { + case BACNET_SHED_TYPE_PERCENT: + apdu_len = encode_context_unsigned(&apdu[0], 0, + Requested_Shed_Level[object_index].value.percent); + break; + case BACNET_SHED_TYPE_AMOUNT: + apdu_len = encode_context_real(&apdu[0], 2, + Requested_Shed_Level[object_index].value.amount); + break; + case BACNET_SHED_TYPE_LEVEL: + default: + apdu_len = encode_context_unsigned(&apdu[0], 1, + Requested_Shed_Level[object_index].value.level); + break; + } + break; + case PROP_START_TIME: + len = encode_tagged_date(&apdu[0], &Start_Time[object_index].date); + apdu_len = len; + len = encode_tagged_time(&apdu[apdu_len], + &Start_Time[object_index].time); + apdu_len += len; + break; + case PROP_SHED_DURATION: + apdu_len = encode_tagged_unsigned(&apdu[0], + Shed_Duration[object_index]); + break; + case PROP_DUTY_WINDOW: + apdu_len = encode_tagged_unsigned(&apdu[0], + Duty_Window[object_index]); + break; + case PROP_ENABLE: + state = Load_Control_Enable[object_index]; + apdu_len = encode_tagged_boolean(&apdu[0], state); + break; + case PROP_FULL_DUTY_BASELINE: /* optional */ + apdu_len = encode_tagged_real(&apdu[0], + Full_Duty_Baseline[object_index]); + break; + case PROP_EXPECTED_SHED_LEVEL: + switch (Expected_Shed_Level[object_index].type) { + case BACNET_SHED_TYPE_PERCENT: + apdu_len = encode_context_unsigned(&apdu[0], 0, + Expected_Shed_Level[object_index].value.percent); + break; + case BACNET_SHED_TYPE_AMOUNT: + apdu_len = encode_context_real(&apdu[0], 2, + Expected_Shed_Level[object_index].value.amount); + break; + case BACNET_SHED_TYPE_LEVEL: + default: + apdu_len = encode_context_unsigned(&apdu[0], 1, + Expected_Shed_Level[object_index].value.level); + break; + } + break; + case PROP_ACTUAL_SHED_LEVEL: + switch (Actual_Shed_Level[object_index].type) { + case BACNET_SHED_TYPE_PERCENT: + apdu_len = encode_context_unsigned(&apdu[0], 0, + Actual_Shed_Level[object_index].value.percent); + break; + case BACNET_SHED_TYPE_AMOUNT: + apdu_len = encode_context_real(&apdu[0], 2, + Actual_Shed_Level[object_index].value.amount); + break; + case BACNET_SHED_TYPE_LEVEL: + default: + apdu_len = encode_context_unsigned(&apdu[0], 1, + Actual_Shed_Level[object_index].value.level); + break; + } + break; + case PROP_SHED_LEVELS: + /* Array element zero is the number of elements in the array */ + if (array_index == 0) + apdu_len = encode_tagged_unsigned(&apdu[0], MAX_SHED_LEVELS); + /* if no index was specified, then try to encode the entire list */ + /* into one packet. */ + else if (array_index == BACNET_ARRAY_ALL) { + apdu_len = 0; + for (i = 0; i < MAX_SHED_LEVELS; i++) { + /* FIXME: check if we have room before adding it to APDU */ + len = + encode_tagged_unsigned(&apdu[apdu_len], + Shed_Levels[object_index][i]); + /* add it if we have room */ + if ((apdu_len + len) < MAX_APDU) + apdu_len += len; + else { + *error_class = ERROR_CLASS_SERVICES; + *error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; + apdu_len = -1; + break; + } + } + } else { + if (array_index <= MAX_SHED_LEVELS) { + apdu_len = encode_tagged_unsigned(&apdu[0], + Shed_Levels[object_index][array_index - 1]); + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = -1; + } + } + break; + case PROP_SHED_LEVEL_DESCRIPTIONS: + /* Array element zero is the number of elements in the array */ + if (array_index == 0) + apdu_len = encode_tagged_unsigned(&apdu[0], MAX_SHED_LEVELS); + /* if no index was specified, then try to encode the entire list */ + /* into one packet. */ + else if (array_index == BACNET_ARRAY_ALL) { + apdu_len = 0; + for (i = 0; i < MAX_SHED_LEVELS; i++) { + /* FIXME: check if we have room before adding it to APDU */ + characterstring_init_ansi(&char_string, + Shed_Level_Descriptions[i]); + len = encode_tagged_character_string(&apdu[apdu_len], + &char_string); + /* add it if we have room */ + if ((apdu_len + len) < MAX_APDU) + apdu_len += len; + else { + *error_class = ERROR_CLASS_SERVICES; + *error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; + apdu_len = -1; + break; + } + } + } else { + if (array_index <= MAX_SHED_LEVELS) { + characterstring_init_ansi(&char_string, + Shed_Level_Descriptions[array_index - 1]); + apdu_len = encode_tagged_character_string(&apdu[0], + &char_string); + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = -1; + } + } + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +/* returns true if successful */ +bool Load_Control_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + bool status = false; /* return value */ + unsigned int object_index = 0; + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + Load_Control_Init(); + if (!Load_Control_Valid_Instance(wp_data->object_instance)) { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + /* decode the some of the request */ + len = bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + object_index = + Load_Control_Instance_To_Index(wp_data->object_instance); + switch (wp_data->object_property) { + case PROP_REQUESTED_SHED_LEVEL: + len = bacapp_decode_context_data(wp_data->application_data, + wp_data->application_data_len, + &value, PROP_REQUESTED_SHED_LEVEL); + if (value.context_tag == 0) { + /* percent - Unsigned */ + Requested_Shed_Level[object_index].type = + BACNET_SHED_TYPE_PERCENT; + Requested_Shed_Level[object_index].value.percent = + value.type.Unsigned_Int; + status = true; + } else if (value.context_tag == 1) { + /* level - Unsigned */ + Requested_Shed_Level[object_index].type = + BACNET_SHED_TYPE_LEVEL; + Requested_Shed_Level[object_index].value.level = + value.type.Unsigned_Int; + status = true; + } else if (value.context_tag == 2) { + /* amount - REAL */ + Requested_Shed_Level[object_index].type = + BACNET_SHED_TYPE_AMOUNT; + Requested_Shed_Level[object_index].value.amount = + value.type.Real; + status = true; + } else { + /* error! */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + if (status) { + Load_Control_Request_Written[object_index] = true; + } + break; + case PROP_START_TIME: + if (value.tag == BACNET_APPLICATION_TAG_DATE) { + memcpy(&Start_Time[object_index].date, + &value.type.Date, sizeof(value.type.Date)); + Start_Time_Property_Written[object_index] = true; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + if (!status) + break; + len = + bacapp_decode_application_data(wp_data->application_data + len, + wp_data->application_data_len - len, &value); + if (len && value.tag == BACNET_APPLICATION_TAG_TIME) { + memcpy(&Start_Time[object_index].time, + &value.type.Time, sizeof(value.type.Time)); + status = true; + } else { + status = false; + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_SHED_DURATION: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + Shed_Duration[object_index] = value.type.Unsigned_Int; + Load_Control_Request_Written[object_index] = true; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_DUTY_WINDOW: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + Duty_Window[object_index] = value.type.Unsigned_Int; + Load_Control_Request_Written[object_index] = true; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_SHED_LEVELS: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + /* re-write the size of the array? */ + if (wp_data->array_index == 0) { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else if (wp_data->array_index == BACNET_ARRAY_ALL) { + /* FIXME: write entire array */ + status = true; + } else if (wp_data->array_index <= MAX_SHED_LEVELS) { + Shed_Levels[object_index][wp_data->array_index - 1] = + value.type.Unsigned_Int; + status = true; + } else { + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_ENABLE: + if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { + Load_Control_Enable[object_index] = value.type.Boolean; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +#if 0 +static void Load_Control_WriteProperty_Request_Shed_Percent(Test * pTest, + int instance, unsigned percent) +{ + bool status = false; + BACNET_APPLICATION_DATA_VALUE value; + BACNET_WRITE_PROPERTY_DATA wp_data; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + wp_data.object_type = OBJECT_LOAD_CONTROL; + wp_data.object_instance = instance; + wp_data.array_index = BACNET_ARRAY_ALL; + wp_data.priority = BACNET_NO_PRIORITY; + wp_data.object_property = PROP_REQUESTED_SHED_LEVEL; + value.context_specific = true; + value.context_tag = 0; + value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; + value.type.Unsigned_Int = percent; + wp_data.application_data_len = + bacapp_encode_data(&wp_data.application_data[0], &value); + ct_test(pTest, wp_data.application_data_len > 0); + status = Load_Control_Write_Property(&wp_data, + &error_class, &error_code); + ct_test(pTest, status == true); +} +#endif + +static void Load_Control_WriteProperty_Request_Shed_Level(Test * pTest, + int instance, unsigned level) +{ + bool status = false; + BACNET_APPLICATION_DATA_VALUE value; + BACNET_WRITE_PROPERTY_DATA wp_data; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + wp_data.object_type = OBJECT_LOAD_CONTROL; + wp_data.object_instance = instance; + wp_data.array_index = BACNET_ARRAY_ALL; + wp_data.priority = BACNET_NO_PRIORITY; + wp_data.object_property = PROP_REQUESTED_SHED_LEVEL; + value.context_specific = true; + value.context_tag = 1; + value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; + value.type.Unsigned_Int = level; + wp_data.application_data_len = + bacapp_encode_data(&wp_data.application_data[0], &value); + ct_test(pTest, wp_data.application_data_len > 0); + status = Load_Control_Write_Property(&wp_data, + &error_class, &error_code); + ct_test(pTest, status == true); +} + +#if 0 +static void Load_Control_WriteProperty_Request_Shed_Amount(Test * pTest, + int instance, float amount) +{ + bool status = false; + BACNET_APPLICATION_DATA_VALUE value; + BACNET_WRITE_PROPERTY_DATA wp_data; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + wp_data.object_type = OBJECT_LOAD_CONTROL; + wp_data.object_instance = instance; + wp_data.array_index = BACNET_ARRAY_ALL; + wp_data.priority = BACNET_NO_PRIORITY; + wp_data.object_property = PROP_REQUESTED_SHED_LEVEL; + value.context_specific = true; + value.context_tag = 2; + value.tag = BACNET_APPLICATION_TAG_REAL; + value.type.Real = amount; + wp_data.application_data_len = + bacapp_encode_data(&wp_data.application_data[0], &value); + ct_test(pTest, wp_data.application_data_len > 0); + status = Load_Control_Write_Property(&wp_data, + &error_class, &error_code); + ct_test(pTest, status == true); +} +#endif + +static void Load_Control_WriteProperty_Enable(Test * pTest, int instance, + bool enable) +{ + bool status = false; + BACNET_APPLICATION_DATA_VALUE value; + BACNET_WRITE_PROPERTY_DATA wp_data; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + wp_data.object_type = OBJECT_LOAD_CONTROL; + wp_data.object_instance = instance; + wp_data.array_index = BACNET_ARRAY_ALL; + wp_data.priority = BACNET_NO_PRIORITY; + /* Set Enable=TRUE */ + wp_data.object_property = PROP_ENABLE; + value.context_specific = false; + value.context_tag = 0; + value.tag = BACNET_APPLICATION_TAG_BOOLEAN; + value.type.Boolean = enable; + wp_data.application_data_len = + bacapp_encode_data(&wp_data.application_data[0], &value); + ct_test(pTest, wp_data.application_data_len > 0); + status = Load_Control_Write_Property(&wp_data, + &error_class, &error_code); + ct_test(pTest, status == true); +} + +static void Load_Control_WriteProperty_Shed_Duration(Test * pTest, + int instance, unsigned duration) +{ + bool status = false; + BACNET_APPLICATION_DATA_VALUE value; + BACNET_WRITE_PROPERTY_DATA wp_data; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + wp_data.object_type = OBJECT_LOAD_CONTROL; + wp_data.object_instance = instance; + wp_data.array_index = BACNET_ARRAY_ALL; + wp_data.priority = BACNET_NO_PRIORITY; + wp_data.object_property = PROP_SHED_DURATION; + value.context_specific = false; + value.context_tag = 0; + value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; + value.type.Unsigned_Int = duration; + wp_data.application_data_len = + bacapp_encode_data(&wp_data.application_data[0], &value); + ct_test(pTest, wp_data.application_data_len > 0); + status = Load_Control_Write_Property(&wp_data, + &error_class, &error_code); + ct_test(pTest, status == true); +} + +static void Load_Control_WriteProperty_Duty_Window(Test * pTest, + int instance, unsigned duration) +{ + bool status = false; + BACNET_APPLICATION_DATA_VALUE value; + BACNET_WRITE_PROPERTY_DATA wp_data; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + wp_data.object_type = OBJECT_LOAD_CONTROL; + wp_data.object_instance = instance; + wp_data.array_index = BACNET_ARRAY_ALL; + wp_data.priority = BACNET_NO_PRIORITY; + wp_data.object_property = PROP_DUTY_WINDOW; + value.context_specific = false; + value.context_tag = 0; + value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; + value.type.Unsigned_Int = duration; + wp_data.application_data_len = + bacapp_encode_data(&wp_data.application_data[0], &value); + ct_test(pTest, wp_data.application_data_len > 0); + status = Load_Control_Write_Property(&wp_data, + &error_class, &error_code); + ct_test(pTest, status == true); +} + +static void Load_Control_WriteProperty_Start_Time_Wildcards(Test * pTest, + int instance) +{ + int len = 0; + bool status = false; + BACNET_APPLICATION_DATA_VALUE value; + BACNET_WRITE_PROPERTY_DATA wp_data; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + wp_data.object_type = OBJECT_LOAD_CONTROL; + wp_data.object_instance = instance; + wp_data.array_index = BACNET_ARRAY_ALL; + wp_data.priority = BACNET_NO_PRIORITY; + wp_data.object_property = PROP_START_TIME; + value.context_specific = false; + value.context_tag = 0; + value.tag = BACNET_APPLICATION_TAG_DATE; + datetime_date_wildcard_set(&value.type.Date); + wp_data.application_data_len = + bacapp_encode_data(&wp_data.application_data[0], &value); + ct_test(pTest, wp_data.application_data_len > 0); + len = wp_data.application_data_len; + value.tag = BACNET_APPLICATION_TAG_TIME; + datetime_time_wildcard_set(&value.type.Time); + wp_data.application_data_len = + bacapp_encode_data(&wp_data.application_data[len], &value); + ct_test(pTest, wp_data.application_data_len > 0); + wp_data.application_data_len += len; + status = Load_Control_Write_Property(&wp_data, + &error_class, &error_code); + ct_test(pTest, status == true); +} + +static void Load_Control_WriteProperty_Start_Time(Test * pTest, int instance, + uint16_t year, uint8_t month, uint8_t day, + uint8_t hour, uint8_t minute, uint8_t seconds, uint8_t hundredths) +{ + int len = 0; + bool status = false; + BACNET_APPLICATION_DATA_VALUE value; + BACNET_WRITE_PROPERTY_DATA wp_data; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + wp_data.object_type = OBJECT_LOAD_CONTROL; + wp_data.object_instance = instance; + wp_data.array_index = BACNET_ARRAY_ALL; + wp_data.priority = BACNET_NO_PRIORITY; + wp_data.object_property = PROP_START_TIME; + value.context_specific = false; + value.context_tag = 0; + value.tag = BACNET_APPLICATION_TAG_DATE; + datetime_set_date(&value.type.Date, year, month, day); + wp_data.application_data_len = + bacapp_encode_data(&wp_data.application_data[0], &value); + ct_test(pTest, wp_data.application_data_len > 0); + len = wp_data.application_data_len; + value.tag = BACNET_APPLICATION_TAG_TIME; + datetime_set_time(&value.type.Time, hour, minute, seconds, hundredths); + wp_data.application_data_len = + bacapp_encode_data(&wp_data.application_data[len], &value); + ct_test(pTest, wp_data.application_data_len > 0); + wp_data.application_data_len += len; + status = Load_Control_Write_Property(&wp_data, + &error_class, &error_code); + ct_test(pTest, status == true); +} + +void testLoadControlStateMachine(Test * pTest) +{ + unsigned i = 0, j = 0; + uint8_t level = 0; + + Load_Control_Init(); + /* validate the triggers for each state change */ + for (j = 0; j < 20; j++) { + Load_Control_State_Machine(0); + for (i = 0; i < MAX_LOAD_CONTROLS; i++) { + ct_test(pTest, Load_Control_State[i] == SHED_INACTIVE); + } + } + /* SHED_REQUEST_PENDING */ + /* CancelShed - Start time has wildcards */ + Load_Control_WriteProperty_Enable(pTest, 0, true); + Load_Control_WriteProperty_Shed_Duration(pTest, 0, 60); + Load_Control_WriteProperty_Start_Time_Wildcards(pTest, 0); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_INACTIVE); + + /* CancelShed - Requested_Shed_Level equal to default value */ + Load_Control_Init(); + Load_Control_WriteProperty_Request_Shed_Level(pTest, 0, 0); + Load_Control_WriteProperty_Start_Time(pTest, 0, 2007,2,27,15,0,0,0); + Load_Control_WriteProperty_Shed_Duration(pTest, 0, 5); + datetime_set_values(&Current_Time, 2007,2,27,15,0,0,0); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_INACTIVE); + + /* CancelShed - Non-default values, but Start time is passed */ + Load_Control_Init(); + Load_Control_WriteProperty_Enable(pTest, 0, true); + Load_Control_WriteProperty_Request_Shed_Level(pTest, 0, 1); + Load_Control_WriteProperty_Shed_Duration(pTest, 0, 5); + Load_Control_WriteProperty_Start_Time(pTest, 0, 2007,2,27,15,0,0,0); + datetime_set_values(&Current_Time, 2007,2,28,15,0,0,0); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_INACTIVE); + + /* ReconfigurePending - new write received while pending */ + Load_Control_Init(); + Load_Control_WriteProperty_Enable(pTest, 0, true); + Load_Control_WriteProperty_Request_Shed_Level(pTest, 0, 1); + Load_Control_WriteProperty_Shed_Duration(pTest, 0, 5); + Load_Control_WriteProperty_Start_Time(pTest, 0, 2007,2,27,15,0,0,0); + datetime_set_values(&Current_Time, 2007,2,27,5,0,0,0); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + Load_Control_WriteProperty_Request_Shed_Level(pTest, 0, 2); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + Load_Control_WriteProperty_Shed_Duration(pTest, 0, 6); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + Load_Control_WriteProperty_Duty_Window(pTest, 0, 60); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + Load_Control_WriteProperty_Start_Time(pTest, 0, 2007,2,27,15,0,0,1); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + + /* CannotMeetShed -> FinishedUnsuccessfulShed */ + Load_Control_Init(); + Load_Control_WriteProperty_Enable(pTest, 0, true); + Load_Control_WriteProperty_Request_Shed_Level(pTest, 0, 1); + Load_Control_WriteProperty_Shed_Duration(pTest, 0, 120); + Load_Control_WriteProperty_Start_Time(pTest, 0, 2007,2,27,15,0,0,0); + datetime_set_values(&Current_Time, 2007,2,27,5,0,0,0); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + /* set to lowest value so we cannot meet the shed level */ + datetime_set_values(&Current_Time, 2007,2,27,16,0,0,0); + Analog_Output_Present_Value_Set(0, 0, 16); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_NON_COMPLIANT); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_NON_COMPLIANT); + /* FinishedUnsuccessfulShed */ + datetime_set_values(&Current_Time, 2007,2,27,23,0,0,0); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_INACTIVE); + + /* CannotMeetShed -> UnsuccessfulShedReconfigured */ + Load_Control_Init(); + Load_Control_WriteProperty_Enable(pTest, 0, true); + Load_Control_WriteProperty_Request_Shed_Level(pTest, 0, 1); + Load_Control_WriteProperty_Shed_Duration(pTest, 0, 120); + Load_Control_WriteProperty_Start_Time(pTest, 0, 2007,2,27,15,0,0,0); + datetime_set_values(&Current_Time, 2007,2,27,5,0,0,0); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + /* set to lowest value so we cannot meet the shed level */ + datetime_set_values(&Current_Time, 2007,2,27,16,0,0,0); + Analog_Output_Present_Value_Set(0, 0, 16); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_NON_COMPLIANT); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_NON_COMPLIANT); + /* FinishedUnsuccessfulShed */ + Load_Control_WriteProperty_Start_Time(pTest, 0, 2007,2,27,16,0,0,0); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_REQUEST_PENDING); + datetime_set_values(&Current_Time, 2007,2,27,16,0,1,0); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_NON_COMPLIANT); + /* CanNowComplyWithShed */ + Analog_Output_Present_Value_Set(0, 100, 16); + datetime_set_values(&Current_Time, 2007,2,27,16,0,2,0); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_COMPLIANT); + level = Analog_Output_Present_Value(0); + ct_test(pTest, level == 90); + /* FinishedSuccessfulShed */ + datetime_set_values(&Current_Time, 2007,2,27,23,0,0,0); + Load_Control_State_Machine(0); + ct_test(pTest, Load_Control_State[0] == SHED_INACTIVE); + level = Analog_Output_Present_Value(0); + ct_test(pTest, level == 100); +} + +void testLoadControl(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_LOAD_CONTROL; + uint32_t decoded_instance = 0; + uint32_t instance = 123; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + len = Load_Control_Encode_Property_APDU(&apdu[0], + instance, + PROP_OBJECT_IDENTIFIER, + BACNET_ARRAY_ALL, &error_class, &error_code); + ct_test(pTest, len != 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == OBJECT_LOAD_CONTROL); + ct_test(pTest, decoded_instance == instance); + + return; +} + +#ifdef TEST_LOAD_CONTROL +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Load Control", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testLoadControl); + assert(rc); + rc = ct_addTestFunction(pTest, testLoadControlStateMachine); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_LOAD_CONTROL */ +#endif /* TEST */ diff --git a/bacnet-stack/demo/object/lc.h b/bacnet-stack/demo/object/lc.h index e69de29b..532c76da 100644 --- a/bacnet-stack/demo/object/lc.h +++ b/bacnet-stack/demo/object/lc.h @@ -0,0 +1,62 @@ +/************************************************************************** +* +* Copyright (C) 2007 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 LOADCONTROL_H +#define LOADCONTROL_H + +#include +#include +#include "bacdef.h" +#include "bacerror.h" +#include "wp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + void Load_Control_State_Machine_Handler(void); + + bool Load_Control_Valid_Instance(uint32_t object_instance); + unsigned Load_Control_Count(void); + uint32_t Load_Control_Index_To_Instance(unsigned index); + char *Load_Control_Name(uint32_t object_instance); + + int Load_Control_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + + bool Load_Control_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + +#ifdef TEST +#include "ctest.h" + void testLoadControl(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/demo/object/lo.c b/bacnet-stack/demo/object/lo.c index e69de29b..b8d7d62d 100644 --- a/bacnet-stack/demo/object/lo.c +++ b/bacnet-stack/demo/object/lo.c @@ -0,0 +1,625 @@ +/************************************************************************** +* +* Copyright (C) 2007 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. +* +*********************************************************************/ + +/* Lighting Output Objects - customize for your use */ + +#include +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bacapp.h" +#include "config.h" /* the custom stuff */ +#include "wp.h" + +#define MAX_LIGHTING_OUTPUTS 5 + +/* we choose to have a NULL level in our system represented by */ +/* a particular value. When the priorities are not in use, they */ +/* will be relinquished (i.e. set to the NULL level). */ +#define LIGHTING_LEVEL_NULL 255 +/* When all the priorities are level null, the present value returns */ +/* the Relinquish Default value */ +#define LIGHTING_RELINQUISH_DEFAULT 0 + +/* note: although the standard specifies REAL values for some + of the optional parameters, we represent them interally as + integers. */ +typedef struct LightingCommand { + BACNET_LIGHTING_OPERATION operation; + uint8_t level; /* 0..100 percent, 255=not used */ + uint8_t ramp_rate; /* 0..100 percent-per-second, 255=not used */ + uint8_t step_increment; /* 0..100 amount to step, 255=not used */ + uint16_t fade_time; /* 1..65535 seconds to transition, 0=not used */ + uint16_t duration; /* 1..65535 minutes until relinquish, 0=not used */ +} BACNET_LIGHTING_COMMAND; + +/* Here is our Priority Array. They are supposed to be Real, but */ +/* we might not have that kind of memory, so we will use a single byte */ +/* and load a Real for returning the value when asked. */ +static uint8_t + Lighting_Output_Level[MAX_LIGHTING_OUTPUTS][BACNET_MAX_PRIORITY]; +/* The Progress_Value tracks changes such as ramp and fade */ +static uint8_t Lighting_Output_Progress[MAX_LIGHTING_OUTPUTS]; +/* The minimum and maximum present values are used for clamping */ +static uint8_t Lighting_Output_Min_Present_Value[MAX_LIGHTING_OUTPUTS]; +static uint8_t Lighting_Output_Max_Present_Value[MAX_LIGHTING_OUTPUTS]; +/* Writable out-of-service allows others to play with our Present Value */ +/* without changing the physical output */ +static bool Lighting_Output_Out_Of_Service[MAX_LIGHTING_OUTPUTS]; +/* the lighting command is what we are doing */ +static uint8_t Lighting_Command_Priority = 16; +static BACNET_LIGHTING_COMMAND Lighting_Command[MAX_LIGHTING_OUTPUTS]; +/* we need to have our arrays initialized before answering any calls */ +static bool Lighting_Output_Initialized = false; + +int Lighting_Output_Encode_Lighting_Command(uint8_t * apdu, + BACNET_LIGHTING_COMMAND * data) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + int len = 0; /* total length of the apdu, return value */ + float real_value = 0.0; + uint32_t unsigned_value = 0; + + if (apdu) { + len = encode_context_enumerated(&apdu[apdu_len], 0, + data->operation); + apdu_len += len; + /* optional level? */ + if (data->level != 255) { + real_value = data->level; + len = encode_context_real(&apdu[apdu_len], 1, + real_value); + apdu_len += len; + } + /* optional ramp-rate */ + if (data->ramp_rate != 255) { + real_value = data->ramp_rate; + len = encode_context_real(&apdu[apdu_len], 2, + real_value); + apdu_len += len; + } + /* optional step increment */ + if (data->step_increment != 255) { + real_value = data->step_increment; + len = encode_context_real(&apdu[apdu_len], 3, + real_value); + apdu_len += len; + } + /* optional fade time */ + if (data->fade_time != 0) { + real_value = data->fade_time; + len = encode_context_real(&apdu[apdu_len], 4, + real_value); + apdu_len += len; + } + /* optional duration */ + if (data->duration != 0) { + unsigned_value = data->duration; + len = encode_context_unsigned(&apdu[apdu_len], 5, + unsigned_value); + apdu_len += len; + } + } + + return apdu_len; +} + +int Lighting_Output_Decode_Lighting_Command(uint8_t * apdu, + unsigned apdu_max_len, BACNET_LIGHTING_COMMAND * data) +{ + int len = 0; + int apdu_len = 0; + int tag_len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + int type = 0; /* for decoding */ + int property = 0; /* for decoding */ + uint32_t unsigned_value = 0; + int i = 0; /* loop counter */ + float real_value = 0.0; + + /* check for value pointers */ + if (apdu_len && data) { + /* Tag 0: operation */ + if (!decode_is_context_tag(&apdu[apdu_len], 0)) + return -1; + len = decode_tag_number_and_value(&apdu[apdu_len], + &tag_number, &len_value_type); + apdu_len += len; + len = decode_enumerated(&apdu[apdu_len], len_value_type, &data->operation); + apdu_len += len; + /* Tag 1: level - OPTIONAL */ + if (decode_is_context_tag(&apdu[apdu_len], 1)) { + len = decode_tag_number_and_value(&apdu[apdu_len], + &tag_number, &len_value_type); + apdu_len += len; + len = decode_real(&apdu[apdu_len], &real_value); + apdu_len += len; + data->level = real_value; + /* FIXME: are we going to flag errors in decoding values here? */ + } + /* FIXME: finish me! */ + /* Tag 2: */ + + } + + return len; +} + + +void Lighting_Output_Init(void) +{ + unsigned i, j; + + if (!Lighting_Output_Initialized) { + Lighting_Output_Initialized = true; + + /* initialize all the analog output priority arrays to NULL */ + for (i = 0; i < MAX_LIGHTING_OUTPUTS; i++) { + for (j = 0; j < BACNET_MAX_PRIORITY; j++) { + Lighting_Output_Level[i][j] = LIGHTING_LEVEL_NULL; + } + Lighting_Command[i].operation = BACNET_LIGHTS_STOP; + Lighting_Output_Out_Of_Service[i] = false; + Lighting_Output_Progress[i] = LIGHTING_RELINQUISH_DEFAULT; + Lighting_Output_Min_Present_Value[i] = 0; + Lighting_Output_Max_Present_Value[i] = 100; + } + } + + return; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need validate that the */ +/* given instance exists */ +bool Lighting_Output_Valid_Instance(uint32_t object_instance) +{ + Lighting_Output_Init(); + if (object_instance < MAX_LIGHTING_OUTPUTS) + return true; + + return false; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then count how many you have */ +unsigned Lighting_Output_Count(void) +{ + Lighting_Output_Init(); + return MAX_LIGHTING_OUTPUTS; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the instance */ +/* that correlates to the correct index */ +uint32_t Lighting_Output_Index_To_Instance(unsigned index) +{ + Lighting_Output_Init(); + return index; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the index */ +/* that correlates to the correct instance number */ +unsigned Lighting_Output_Instance_To_Index(uint32_t object_instance) +{ + unsigned index = MAX_LIGHTING_OUTPUTS; + + Lighting_Output_Init(); + if (object_instance < MAX_LIGHTING_OUTPUTS) + index = object_instance; + + return index; +} + +float Lighting_Output_Present_Value(uint32_t object_instance) +{ + float value = LIGHTING_RELINQUISH_DEFAULT; + unsigned index = 0; + unsigned i = 0; + + Lighting_Output_Init(); + index = Lighting_Output_Instance_To_Index(object_instance); + if (index < MAX_LIGHTING_OUTPUTS) { + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + if (Lighting_Output_Level[index][i] != LIGHTING_LEVEL_NULL) { + value = Lighting_Output_Level[index][i]; + break; + } + } + } + + return value; +} + +unsigned Lighting_Output_Present_Value_Priority(uint32_t object_instance) +{ + unsigned index = 0; /* instance to index conversion */ + unsigned i = 0; /* loop counter */ + unsigned priority = 0; /* return value */ + + Lighting_Output_Init(); + index = Lighting_Output_Instance_To_Index(object_instance); + if (index < MAX_LIGHTING_OUTPUTS) { + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + if (Lighting_Output_Level[index][i] != LIGHTING_LEVEL_NULL) { + priority = i + 1; + break; + } + } + } + + return priority; +} + +bool Lighting_Output_Present_Value_Set(uint32_t object_instance, + float value, unsigned priority) +{ + unsigned index = 0; + bool status = false; + + index = Lighting_Output_Instance_To_Index(object_instance); + if (index < MAX_LIGHTING_OUTPUTS) { + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */ ) && + (value >= 0.0) && (value <= 100.0)) { + Lighting_Output_Level[index][priority-1] = (uint8_t) value; + /* Note: you could set the physical output here to the next + highest priority, or to the relinquish default if no + priorities are set. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } + } + + return status; +} + +bool Lighting_Output_Present_Value_Relinquish(uint32_t object_instance, + int priority) +{ + unsigned index = 0; + bool status = false; + + index = Lighting_Output_Instance_To_Index(object_instance); + if (index < MAX_LIGHTING_OUTPUTS) { + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */ )) { + Lighting_Output_Level[index][priority-1] = LIGHTING_LEVEL_NULL; + /* Note: you could set the physical output here to the next + highest priority, or to the relinquish default if no + priorities are set. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } + } + + return status; +} + +float Lighting_Output_Progress_Value(uint32_t object_instance) +{ + float value = LIGHTING_RELINQUISH_DEFAULT; + unsigned index = 0; + + Lighting_Output_Init(); + index = Lighting_Output_Instance_To_Index(object_instance); + if (index < MAX_LIGHTING_OUTPUTS) { + value = Lighting_Output_Progress[index]; + } + + return value; +} + +/* note: the object name must be unique within this device */ +char *Lighting_Output_Name(uint32_t object_instance) +{ + static char text_string[32] = ""; /* okay for single thread */ + + if (object_instance < MAX_LIGHTING_OUTPUTS) { + sprintf(text_string, "LIGHTING OUTPUT %u", object_instance); + return text_string; + } + + return NULL; +} + +/* return apdu len, or -1 on error */ +int Lighting_Output_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int len = 0; + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + float real_value = (float) 1.414; + unsigned object_index = 0; + unsigned i = 0; + bool state = false; + + Lighting_Output_Init(); + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_LIGHTING_OUTPUT, + object_instance); + break; + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + /* object name must be unique in this device. */ + /* FIXME: description could be writable and different than object name */ + characterstring_init_ansi(&char_string, + Lighting_Output_Name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = + encode_tagged_enumerated(&apdu[0], OBJECT_LIGHTING_OUTPUT); + break; + case PROP_PRESENT_VALUE: + real_value = Lighting_Output_Present_Value(object_instance); + apdu_len = encode_tagged_real(&apdu[0], real_value); + break; + case PROP_PROGRESS_VALUE: + real_value = Lighting_Output_Progress_Value(object_instance); + apdu_len = encode_tagged_real(&apdu[0], real_value); + break; + case PROP_LIGHTING_COMMAND: + apdu_len = Lighting_Output_Encode_Lighting_Command(&apdu[0], + &Lighting_Command[object_instance]); + break; + case PROP_STATUS_FLAGS: + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: + object_index = Lighting_Output_Instance_To_Index(object_instance); + state = Lighting_Output_Out_Of_Service[object_index]; + apdu_len = encode_tagged_boolean(&apdu[0], state); + break; + case PROP_UNITS: + apdu_len = encode_tagged_enumerated(&apdu[0], UNITS_PERCENT); + break; + case PROP_PRIORITY_ARRAY: + /* Array element zero is the number of elements in the array */ + if (array_index == 0) + apdu_len = + encode_tagged_unsigned(&apdu[0], BACNET_MAX_PRIORITY); + /* if no index was specified, then try to encode the entire list */ + /* into one packet. */ + else if (array_index == BACNET_ARRAY_ALL) { + object_index = + Lighting_Output_Instance_To_Index(object_instance); + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + /* FIXME: check if we have room before adding it to APDU */ + if (Lighting_Output_Level[object_index][i] == LIGHTING_LEVEL_NULL) + len = encode_tagged_null(&apdu[apdu_len]); + else { + real_value = Lighting_Output_Level[object_index][i]; + len = encode_tagged_real(&apdu[apdu_len], real_value); + } + /* add it if we have room */ + if ((apdu_len + len) < MAX_APDU) + apdu_len += len; + else { + *error_class = ERROR_CLASS_SERVICES; + *error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; + apdu_len = -1; + break; + } + } + } else { + object_index = + Lighting_Output_Instance_To_Index(object_instance); + if (array_index <= BACNET_MAX_PRIORITY) { + if (Lighting_Output_Level[object_index][array_index - 1] == + LIGHTING_LEVEL_NULL) + apdu_len = encode_tagged_null(&apdu[0]); + else { + real_value = + Lighting_Output_Level[object_index][array_index - 1]; + apdu_len = encode_tagged_real(&apdu[0], real_value); + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = -1; + } + } + + break; + case PROP_RELINQUISH_DEFAULT: + real_value = LIGHTING_RELINQUISH_DEFAULT; + apdu_len = encode_tagged_real(&apdu[0], real_value); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +/* returns true if successful */ +bool Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + bool status = false; /* return value */ + unsigned int object_index = 0; + uint8_t level = LIGHTING_LEVEL_NULL; + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + Lighting_Output_Init(); + if (!Lighting_Output_Valid_Instance(wp_data->object_instance)) { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + /* decode the some of the request */ + len = bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + switch (wp_data->object_property) { + case PROP_PRESENT_VALUE: + if (value.tag == BACNET_APPLICATION_TAG_REAL) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + status = + Lighting_Output_Present_Value_Set(wp_data->object_instance, + value.type.Real, wp_data->priority); + if (wp_data->priority == 6) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else if (!status) { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else if (value.tag == BACNET_APPLICATION_TAG_NULL) { + level = LIGHTING_LEVEL_NULL; + object_index = + Lighting_Output_Instance_To_Index(wp_data->object_instance); + status = + Lighting_Output_Present_Value_Relinquish(wp_data-> + object_instance, wp_data->priority); + if (wp_data->priority == 6) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else if (!status) { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_LIGHTING_COMMAND: + /* FIXME: error checking? */ + Lighting_Output_Decode_Lighting_Command(wp_data->application_data, + wp_data->application_data_len, + &Lighting_Command[wp_data->object_instance]); + break; + case PROP_OUT_OF_SERVICE: + if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { + object_index = + Lighting_Output_Instance_To_Index(wp_data->object_instance); + Lighting_Output_Out_Of_Service[object_index] = + value.type.Boolean; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} + + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testLightingOutput(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_LIGHTING_OUTPUT; + uint32_t decoded_instance = 0; + uint32_t instance = 123; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + + len = Lighting_Output_Encode_Property_APDU(&apdu[0], + instance, + PROP_OBJECT_IDENTIFIER, + BACNET_ARRAY_ALL, &error_class, &error_code); + ct_test(pTest, len != 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == OBJECT_LIGHTING_OUTPUT); + ct_test(pTest, decoded_instance == instance); + + return; +} + +#ifdef TEST_LIGHTING_OUTPUT +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Lighting Output", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testLightingOutput); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_LIGHTING_INPUT */ +#endif /* TEST */ diff --git a/bacnet-stack/demo/object/lo.h b/bacnet-stack/demo/object/lo.h index e69de29b..81fe3a6b 100644 --- a/bacnet-stack/demo/object/lo.h +++ b/bacnet-stack/demo/object/lo.h @@ -0,0 +1,69 @@ +/************************************************************************** +* +* Copyright (C) 2007 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 LO_H +#define LO_H + +#include +#include +#include "bacdef.h" +#include "bacerror.h" +#include "wp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + bool Analog_Output_Valid_Instance(uint32_t object_instance); + unsigned Lighting_Output_Count(void); + uint32_t Lighting_Output_Index_To_Instance(unsigned index); + char *Lighting_Output_Name(uint32_t object_instance); + float Lighting_Output_Present_Value(uint32_t object_instance); + unsigned Lighting_Output_Present_Value_Priority(uint32_t + object_instance); + bool Lighting_Output_Present_Value_Set(uint32_t object_instance, + float value, unsigned priority); + bool Lighting_Output_Present_Value_Relinquish(uint32_t object_instance, + int priority); + + + /* ReadProperty service support */ + int Lighting_Output_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + /* WriteProperty service support */ + bool Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + +#ifdef TEST +#include "ctest.h" + void testLightingOutput(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/demo/object/lsp.c b/bacnet-stack/demo/object/lsp.c index e69de29b..703764f6 100644 --- a/bacnet-stack/demo/object/lsp.c +++ b/bacnet-stack/demo/object/lsp.c @@ -0,0 +1,356 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ + +/* Life Safety Point Objects - customize for your use */ + +#include +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bacapp.h" +#include "config.h" /* the custom stuff */ +#include "wp.h" + +#define MAX_LIFE_SAFETY_POINTS 7 + +/* Here are our stored levels.*/ +static BACNET_LIFE_SAFETY_MODE + Life_Safety_Point_Mode[MAX_LIFE_SAFETY_POINTS]; +static BACNET_LIFE_SAFETY_STATE + Life_Safety_Point_State[MAX_LIFE_SAFETY_POINTS]; +static BACNET_SILENCED_STATE + Life_Safety_Point_Silenced_State[MAX_LIFE_SAFETY_POINTS]; +static BACNET_LIFE_SAFETY_OPERATION + Life_Safety_Point_Operation[MAX_LIFE_SAFETY_POINTS]; +/* Writable out-of-service allows others to play with our Present Value */ +/* without changing the physical output */ +static bool Life_Safety_Point_Out_Of_Service[MAX_LIFE_SAFETY_POINTS]; + +void Life_Safety_Point_Init(void) +{ + static bool initialized = false; + unsigned i; + + if (!initialized) { + initialized = true; + + /* initialize all the analog output priority arrays to NULL */ + for (i = 0; i < MAX_LIFE_SAFETY_POINTS; i++) { + Life_Safety_Point_Mode[i] = LIFE_SAFETY_MODE_DEFAULT; + Life_Safety_Point_State[i] = LIFE_SAFETY_STATE_QUIET; + Life_Safety_Point_Silenced_State[i] = + SILENCED_STATE_UNSILENCED; + Life_Safety_Point_Operation[i] = LIFE_SAFETY_OPERATION_NONE; + } + } + + return; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need validate that the */ +/* given instance exists */ +bool Life_Safety_Point_Valid_Instance(uint32_t object_instance) +{ + Life_Safety_Point_Init(); + if (object_instance < MAX_LIFE_SAFETY_POINTS) + return true; + + return false; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then count how many you have */ +unsigned Life_Safety_Point_Count(void) +{ + Life_Safety_Point_Init(); + return MAX_LIFE_SAFETY_POINTS; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the instance */ +/* that correlates to the correct index */ +uint32_t Life_Safety_Point_Index_To_Instance(unsigned index) +{ + Life_Safety_Point_Init(); + return index; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the index */ +/* that correlates to the correct instance number */ +unsigned Life_Safety_Point_Instance_To_Index(uint32_t object_instance) +{ + unsigned index = MAX_LIFE_SAFETY_POINTS; + + Life_Safety_Point_Init(); + if (object_instance < MAX_LIFE_SAFETY_POINTS) + index = object_instance; + + return index; +} + +static BACNET_LIFE_SAFETY_STATE Life_Safety_Point_Present_Value(uint32_t + object_instance) +{ + BACNET_LIFE_SAFETY_STATE present_value = LIFE_SAFETY_STATE_QUIET; + unsigned index = 0; + + Life_Safety_Point_Init(); + index = Life_Safety_Point_Instance_To_Index(object_instance); + if (index < MAX_LIFE_SAFETY_POINTS) + present_value = Life_Safety_Point_State[index]; + + return present_value; +} + +/* note: the object name must be unique within this device */ +char *Life_Safety_Point_Name(uint32_t object_instance) +{ + static char text_string[32] = ""; /* okay for single thread */ + + if (object_instance < MAX_LIFE_SAFETY_POINTS) { + sprintf(text_string, "LS POINT %u", object_instance); + return text_string; + } + + return NULL; +} + +/* return apdu len, or -1 on error */ +int Life_Safety_Point_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int len = 0; + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + BACNET_LIFE_SAFETY_STATE present_value = LIFE_SAFETY_STATE_QUIET; + BACNET_LIFE_SAFETY_MODE mode = LIFE_SAFETY_MODE_DEFAULT; + BACNET_SILENCED_STATE silenced_state = SILENCED_STATE_UNSILENCED; + BACNET_LIFE_SAFETY_OPERATION operation = LIFE_SAFETY_OPERATION_NONE; + unsigned object_index = 0; + bool state = false; + BACNET_RELIABILITY reliability = RELIABILITY_NO_FAULT_DETECTED; + (void) array_index; /* currently not used */ + Life_Safety_Point_Init(); + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = + encode_tagged_object_id(&apdu[0], OBJECT_LIFE_SAFETY_POINT, + object_instance); + break; + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, + Life_Safety_Point_Name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = + encode_tagged_enumerated(&apdu[0], OBJECT_LIFE_SAFETY_POINT); + break; + case PROP_PRESENT_VALUE: + present_value = Life_Safety_Point_Present_Value(object_instance); + apdu_len = encode_tagged_enumerated(&apdu[0], present_value); + break; + case PROP_STATUS_FLAGS: + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: + object_index = + Life_Safety_Point_Instance_To_Index(object_instance); + state = Life_Safety_Point_Out_Of_Service[object_index]; + apdu_len = encode_tagged_boolean(&apdu[0], state); + break; + case PROP_RELIABILITY: + /* see standard for details about this property */ + reliability = RELIABILITY_NO_FAULT_DETECTED; + apdu_len = encode_tagged_enumerated(&apdu[0], reliability); + break; + case PROP_MODE: + object_index = + Life_Safety_Point_Instance_To_Index(object_instance); + mode = Life_Safety_Point_Mode[object_index]; + apdu_len = encode_tagged_enumerated(&apdu[0], mode); + break; + case PROP_ACCEPTED_MODES: + for (mode = MIN_LIFE_SAFETY_MODE; mode < MAX_LIFE_SAFETY_MODE; + mode++) { + len = encode_tagged_enumerated(&apdu[apdu_len], mode); + apdu_len += len; + } + break; + case PROP_SILENCED: + object_index = + Life_Safety_Point_Instance_To_Index(object_instance); + silenced_state = Life_Safety_Point_Silenced_State[object_index]; + apdu_len = encode_tagged_enumerated(&apdu[0], silenced_state); + break; + case PROP_OPERATION_EXPECTED: + object_index = + Life_Safety_Point_Instance_To_Index(object_instance); + operation = Life_Safety_Point_Operation[object_index]; + apdu_len = encode_tagged_enumerated(&apdu[0], operation); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +/* returns true if successful */ +bool Life_Safety_Point_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + bool status = false; /* return value */ + unsigned int object_index = 0; + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + Life_Safety_Point_Init(); + if (!Life_Safety_Point_Valid_Instance(wp_data->object_instance)) { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + /* decode the some of the request */ + len = bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + switch (wp_data->object_property) { + case PROP_MODE: + if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) { + if ((value.type.Enumerated >= MIN_LIFE_SAFETY_MODE) && + (value.type.Enumerated <= MIN_LIFE_SAFETY_MODE)) { + object_index = + Life_Safety_Point_Instance_To_Index(wp_data-> + object_instance); + Life_Safety_Point_Mode[object_index] = + value.type.Enumerated; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_OUT_OF_SERVICE: + if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { + object_index = + Life_Safety_Point_Instance_To_Index(wp_data-> + object_instance); + Life_Safety_Point_Out_Of_Service[object_index] = + value.type.Boolean; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} + + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testLifeSafetyPoint(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_LIFE_SAFETY_POINT; + uint32_t decoded_instance = 0; + uint32_t instance = 123; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + + len = Life_Safety_Point_Encode_Property_APDU(&apdu[0], + instance, + PROP_OBJECT_IDENTIFIER, + BACNET_ARRAY_ALL, &error_class, &error_code); + ct_test(pTest, len != 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == OBJECT_LIFE_SAFETY_POINT); + ct_test(pTest, decoded_instance == instance); + + return; +} + +#ifdef TEST_LIFE_SAFETY_POINT +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Life Safety Point", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testLifeSafetyPoint); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_LIFE_SAFETY_POINT */ +#endif /* TEST */ diff --git a/bacnet-stack/demo/object/lsp.h b/bacnet-stack/demo/object/lsp.h index e69de29b..4c3ef01f 100644 --- a/bacnet-stack/demo/object/lsp.h +++ b/bacnet-stack/demo/object/lsp.h @@ -0,0 +1,61 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#ifndef LSP_H +#define LSP_H + +#include +#include +#include "bacdef.h" +#include "bacerror.h" +#include "wp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + bool Life_Safety_Point_Valid_Instance(uint32_t object_instance); + unsigned Life_Safety_Point_Count(void); + uint32_t Life_Safety_Point_Index_To_Instance(unsigned index); + char *Life_Safety_Point_Name(uint32_t object_instance); + + int Life_Safety_Point_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + + bool Life_Safety_Point_Write_Property(BACNET_WRITE_PROPERTY_DATA * + wp_data, BACNET_ERROR_CLASS * error_class, + BACNET_ERROR_CODE * error_code); + +#ifdef TEST +#include "ctest.h" + void testLifeSafetyPoint(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/demo/object/mso.c b/bacnet-stack/demo/object/mso.c index e69de29b..4c0395e9 100644 --- a/bacnet-stack/demo/object/mso.c +++ b/bacnet-stack/demo/object/mso.c @@ -0,0 +1,433 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* Multi-state Output Objects - customize for your use */ + +#include +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bacapp.h" +#include "config.h" /* the custom stuff */ +#include "wp.h" + +#define MAX_MULTISTATE_OUTPUTS 4 + +/* When all the priorities are level null, the present value returns */ +/* the Relinquish Default value */ +#define MULTISTATE_RELINQUISH_DEFAULT 0 + +/* NULL part of the array */ +#define MULTISTATE_NULL (255) +/* how many states? 0-253 is 254 states */ +#define MULTISTATE_NUMBER_OF_STATES (254) +/* Here is our Priority Array.*/ +static uint8_t + Multistate_Output_Level[MAX_MULTISTATE_OUTPUTS][BACNET_MAX_PRIORITY]; +/* Writable out-of-service allows others to play with our Present Value */ +/* without changing the physical output */ +static bool Multistate_Output_Out_Of_Service[MAX_MULTISTATE_OUTPUTS]; + +void Multistate_Output_Init(void) +{ + unsigned i, j; + static bool initialized = false; + + if (!initialized) { + initialized = true; + + /* initialize all the analog output priority arrays to NULL */ + for (i = 0; i < MAX_MULTISTATE_OUTPUTS; i++) { + for (j = 0; j < BACNET_MAX_PRIORITY; j++) { + Multistate_Output_Level[i][j] = MULTISTATE_NULL; + } + } + } + + return; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need validate that the */ +/* given instance exists */ +bool Multistate_Output_Valid_Instance(uint32_t object_instance) +{ + if (object_instance < MAX_MULTISTATE_OUTPUTS) + return true; + + return false; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then count how many you have */ +unsigned Multistate_Output_Count(void) +{ + return MAX_MULTISTATE_OUTPUTS; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the instance */ +/* that correlates to the correct index */ +uint32_t Multistate_Output_Index_To_Instance(unsigned index) +{ + return index; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the index */ +/* that correlates to the correct instance number */ +unsigned Multistate_Output_Instance_To_Index(uint32_t object_instance) +{ + unsigned index = MAX_MULTISTATE_OUTPUTS; + + if (object_instance < MAX_MULTISTATE_OUTPUTS) + index = object_instance; + + return index; +} + +static uint32_t Multistate_Output_Present_Value(uint32_t object_instance) +{ + uint32_t value = MULTISTATE_RELINQUISH_DEFAULT; + unsigned index = 0; + unsigned i = 0; + + Multistate_Output_Init(); + index = Multistate_Output_Instance_To_Index(object_instance); + if (index < MAX_MULTISTATE_OUTPUTS) { + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + if (Multistate_Output_Level[index][i] != MULTISTATE_NULL) { + value = Multistate_Output_Level[index][i]; + break; + } + } + } + + return value; +} + +/* note: the object name must be unique within this device */ +char *Multistate_Output_Name(uint32_t object_instance) +{ + static char text_string[32] = ""; /* okay for single thread */ + + if (object_instance < MAX_MULTISTATE_OUTPUTS) { + sprintf(text_string, "MULTISTATE OUTPUT %u", object_instance); + return text_string; + } + + return NULL; +} + +/* return apdu len, or -1 on error */ +int Multistate_Output_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int len = 0; + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + uint32_t present_value = 0; + unsigned object_index = 0; + unsigned i = 0; + bool state = false; + + Multistate_Output_Init(); + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = + encode_tagged_object_id(&apdu[0], OBJECT_MULTI_STATE_OUTPUT, + object_instance); + break; + /* note: Name and Description don't have to be the same. + You could make Description writable and different */ + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, + Multistate_Output_Name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = + encode_tagged_enumerated(&apdu[0], OBJECT_MULTI_STATE_OUTPUT); + break; + case PROP_PRESENT_VALUE: + present_value = Multistate_Output_Present_Value(object_instance); + apdu_len = encode_tagged_unsigned(&apdu[0], present_value); + break; + case PROP_STATUS_FLAGS: + /* note: see the details in the standard on how to use these */ + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + /* note: see the details in the standard on how to use this */ + apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: + object_index = + Multistate_Output_Instance_To_Index(object_instance); + state = Multistate_Output_Out_Of_Service[object_index]; + apdu_len = encode_tagged_boolean(&apdu[0], state); + break; + case PROP_PRIORITY_ARRAY: + /* Array element zero is the number of elements in the array */ + if (array_index == 0) + apdu_len = + encode_tagged_unsigned(&apdu[0], BACNET_MAX_PRIORITY); + /* if no index was specified, then try to encode the entire list */ + /* into one packet. */ + else if (array_index == BACNET_ARRAY_ALL) { + object_index = + Multistate_Output_Instance_To_Index(object_instance); + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + /* FIXME: check if we have room before adding it to APDU */ + if (Multistate_Output_Level[object_index][i] == + MULTISTATE_NULL) + len = encode_tagged_null(&apdu[apdu_len]); + else { + present_value = + Multistate_Output_Level[object_index][i]; + len = + encode_tagged_unsigned(&apdu[apdu_len], + present_value); + } + /* add it if we have room */ + if ((apdu_len + len) < MAX_APDU) + apdu_len += len; + else { + *error_class = ERROR_CLASS_SERVICES; + *error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; + apdu_len = -1; + break; + } + } + } else { + object_index = + Multistate_Output_Instance_To_Index(object_instance); + if (array_index <= BACNET_MAX_PRIORITY) { + if (Multistate_Output_Level[object_index][array_index - + 1] == MULTISTATE_NULL) + apdu_len = encode_tagged_null(&apdu[0]); + else { + present_value = + Multistate_Output_Level[object_index][array_index - + 1]; + apdu_len = + encode_tagged_unsigned(&apdu[0], present_value); + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = -1; + } + } + + break; + case PROP_RELINQUISH_DEFAULT: + present_value = MULTISTATE_RELINQUISH_DEFAULT; + apdu_len = encode_tagged_enumerated(&apdu[0], present_value); + break; + case PROP_NUMBER_OF_STATES: + apdu_len = encode_tagged_unsigned(&apdu[apdu_len], + MULTISTATE_NUMBER_OF_STATES); + break; + + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +/* returns true if successful */ +bool Multistate_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + bool status = false; /* return value */ + unsigned int object_index = 0; + unsigned int priority = 0; + uint32_t level = 0; + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + Multistate_Output_Init(); + if (!Multistate_Output_Valid_Instance(wp_data->object_instance)) { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + /* decode the some of the request */ + len = bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + switch (wp_data->object_property) { + case PROP_PRESENT_VALUE: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + priority = wp_data->priority; + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */ ) && + (value.type.Unsigned_Int <= MULTISTATE_NUMBER_OF_STATES)) { + level = value.type.Unsigned_Int; + object_index = + Multistate_Output_Instance_To_Index(wp_data-> + object_instance); + priority--; + Multistate_Output_Level[object_index][priority] = + (uint8_t) level; + /* Note: you could set the physical output here if we + are the highest priority. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } else if (priority == 6) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else if (value.tag == BACNET_APPLICATION_TAG_NULL) { + level = MULTISTATE_NULL; + object_index = + Multistate_Output_Instance_To_Index(wp_data-> + object_instance); + priority = wp_data->priority; + if (priority && (priority <= BACNET_MAX_PRIORITY)) { + priority--; + Multistate_Output_Level[object_index][priority] = + (uint8_t) level; + /* Note: you could set the physical output here to the next + highest priority, or to the relinquish default if no + priorities are set. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_OUT_OF_SERVICE: + if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { + object_index = + Multistate_Output_Instance_To_Index(wp_data-> + object_instance); + Multistate_Output_Out_Of_Service[object_index] = + value.type.Boolean; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} + + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testMultistateOutput(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_MULTI_STATE_OUTPUT; + uint32_t decoded_instance = 0; + uint32_t instance = 123; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + + len = Multistate_Output_Encode_Property_APDU(&apdu[0], + instance, + PROP_OBJECT_IDENTIFIER, + BACNET_ARRAY_ALL, &error_class, &error_code); + ct_test(pTest, len != 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == OBJECT_MULTI_STATE_OUTPUT); + ct_test(pTest, decoded_instance == instance); + + return; +} + +#ifdef TEST_MULTISTATE_OUTPUT +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Multi-state Output", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testMultistateOutput); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_BINARY_INPUT */ +#endif /* TEST */ diff --git a/bacnet-stack/demo/object/mso.h b/bacnet-stack/demo/object/mso.h index e69de29b..d617ea8b 100644 --- a/bacnet-stack/demo/object/mso.h +++ b/bacnet-stack/demo/object/mso.h @@ -0,0 +1,61 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#ifndef MSO_H +#define MSO_H + +#include +#include +#include "bacdef.h" +#include "bacerror.h" +#include "wp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + bool Multistate_Output_Valid_Instance(uint32_t object_instance); + unsigned Multistate_Output_Count(void); + uint32_t Multistate_Output_Index_To_Instance(unsigned index); + char *Multistate_Output_Name(uint32_t object_instance); + + int Multistate_Output_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code); + + bool Multistate_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA * + wp_data, BACNET_ERROR_CLASS * error_class, + BACNET_ERROR_CODE * error_code); + +#ifdef TEST +#include "ctest.h" + void testMultistateOutput(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/demo/readfile/main.c b/bacnet-stack/demo/readfile/main.c index e69de29b..1315485a 100644 --- a/bacnet-stack/demo/readfile/main.c +++ b/bacnet-stack/demo/readfile/main.c @@ -0,0 +1,313 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* command line tool that sends a BACnet service, and displays the reply */ +#include +#include +#include +#include +#include /* for time */ +#include +#include "bactext.h" +#include "iam.h" +#include "arf.h" +#include "tsm.h" +#include "address.h" +#include "config.h" +#include "bacdef.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "net.h" +#include "datalink.h" +#include "whois.h" +/* some demo stuff needed */ +#include "filename.h" +#include "handlers.h" +#include "client.h" +#include "txbuf.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/* global variables used in this file */ +static uint32_t Target_File_Object_Instance = BACNET_MAX_INSTANCE; +static uint32_t Target_Device_Object_Instance = BACNET_MAX_INSTANCE; +static BACNET_ADDRESS Target_Address; +static char *Local_File_Name = NULL; +static bool End_Of_File_Detected = false; +static bool Error_Detected = false; +static uint8_t Current_Invoke_ID = 0; + +static void Atomic_Read_File_Error_Handler(BACNET_ADDRESS * src, + uint8_t invoke_id, + BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + printf("\r\nBACnet Error!\r\n"); + printf("Error Class: %s\r\n", bactext_error_class_name(error_class)); + printf("Error Code: %s\r\n", bactext_error_code_name(error_code)); + Error_Detected = true; +} + +void MyAbortHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + (void) server; + printf("\r\nBACnet Abort!\r\n"); + printf("Abort Reason: %s\r\n", + bactext_abort_reason_name(abort_reason)); + Error_Detected = true; +} + +void MyRejectHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t reject_reason) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + printf("\r\nBACnet Reject!\r\n"); + printf("Reject Reason: %s\r\n", + bactext_reject_reason_name(reject_reason)); + Error_Detected = true; +} + +static void AtomicReadFileAckHandler(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_ACK_DATA * service_data) +{ + int len = 0; + BACNET_ATOMIC_READ_FILE_DATA data; + FILE *pFile = NULL; /* stream pointer */ + size_t octets_written = 0; + + (void) src; /* FIXME: validate the source address matches */ + len = arf_ack_decode_service_request(service_request, + service_len, &data); + if (len > 0) { + /* validate the parameters before storing data */ + if ((data.access == FILE_STREAM_ACCESS) && + (service_data->invoke_id == Current_Invoke_ID)) { + if (data.type.stream.fileStartPosition == 0) + pFile = fopen(Local_File_Name, "wb"); + else + pFile = fopen(Local_File_Name, "rb+"); + if (pFile) { + /* is there anything to do with this? data.stream.requestedOctetCount */ + (void) fseek(pFile, data.type.stream.fileStartPosition, + SEEK_SET); + octets_written = fwrite(octetstring_value(&data.fileData), 1, /* unit to write in bytes - in our case, an octet is one byte */ + octetstring_length(&data.fileData), pFile); + if (octets_written != octetstring_length(&data.fileData)) + fprintf(stderr, + "Unable to write data to file \"%s\".\n", + Local_File_Name); + else + printf("\r%u bytes", + (data.type.stream.fileStartPosition + + octets_written)); + fclose(pFile); + } + if (data.endOfFile) { + End_Of_File_Detected = true; + printf("\r\n"); + } + } + } +} + +static void LocalIAmHandler(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src) +{ + int len = 0; + uint32_t device_id = 0; + unsigned max_apdu = 0; + int segmentation = 0; + uint16_t vendor_id = 0; + + (void) src; + (void) service_len; + len = iam_decode_service_request(service_request, + &device_id, &max_apdu, &segmentation, &vendor_id); + if (len != -1) { + address_add(device_id, max_apdu, src); + } else + fprintf(stderr, "!\n"); + + return; +} + +static void Init_Service_Handlers(void) +{ + /* 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, + LocalIAmHandler); + /* 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); + /* handle the data coming back from confirmed requests */ + apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_ATOMIC_READ_FILE, + AtomicReadFileAckHandler); + /* handle any errors coming back */ + apdu_set_error_handler(SERVICE_CONFIRMED_ATOMIC_READ_FILE, + Atomic_Read_File_Error_Handler); + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + 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; + int fileStartPosition = 0; + unsigned requestedOctetCount = 0; + uint8_t invoke_id = 0; + bool found = false; + uint16_t my_max_apdu = 0; + + if (argc < 4) { + /* FIXME: what about access method - record or stream? */ + printf("%s device-instance file-instance local-name\r\n", + filename_remove_path(argv[0])); + return 0; + } + /* decode the command line parameters */ + Target_Device_Object_Instance = strtol(argv[1], NULL, 0); + Target_File_Object_Instance = strtol(argv[2], NULL, 0); + Local_File_Name = argv[3]; + if (Target_Device_Object_Instance >= BACNET_MAX_INSTANCE) { + fprintf(stderr, "device-instance=%u - it must be less than %u\r\n", + Target_Device_Object_Instance, BACNET_MAX_INSTANCE); + return 1; + } + if (Target_File_Object_Instance >= BACNET_MAX_INSTANCE) { + fprintf(stderr, "file-instance=%u - it must be less than %u\r\n", + Target_File_Object_Instance, BACNET_MAX_INSTANCE + 1); + return 1; + } + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + address_init(); + Init_Service_Handlers(); + if (!datalink_init(NULL)) + return 1; + /* configure the timeout values */ + last_seconds = time(NULL); + timeout_seconds = (Device_APDU_Timeout() / 1000) * + Device_Number_Of_APDU_Retries(); + /* try to bind with the device */ + Send_WhoIs(Target_Device_Object_Instance, + Target_Device_Object_Instance); + /* loop forever */ + for (;;) { + /* increment timer - exit if timed out */ + current_seconds = time(NULL); + + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + /* at least one second has passed */ + if (current_seconds != last_seconds) + tsm_timer_milliseconds(((current_seconds - + last_seconds) * 1000)); + if (End_Of_File_Detected || Error_Detected) + break; + /* wait until the device is bound, or timeout and quit */ + found = address_bind_request(Target_Device_Object_Instance, + &max_apdu, &Target_Address); + if (found) { + /* calculate the smaller of our APDU size or theirs + and remove the overhead of the APDU (about 16 octets max). + note: we could fail if there is a bottle neck (router) + and smaller MPDU in betweeen. */ + if (max_apdu < MAX_APDU) + my_max_apdu = max_apdu; + else + my_max_apdu = MAX_APDU; + /* Typical sizes are 50, 128, 206, 480, 1024, and 1476 octets */ + if (my_max_apdu <= 50) + requestedOctetCount = my_max_apdu - 20; + else if (my_max_apdu <= 480) + requestedOctetCount = my_max_apdu - 32; + else if (my_max_apdu <= 1476) + requestedOctetCount = my_max_apdu - 64; + else + requestedOctetCount = my_max_apdu / 2; + /* has the previous invoke id expired or returned? + note: invoke ID = 0 is invalid, so it will be idle */ + if ((invoke_id == 0) || tsm_invoke_id_free(invoke_id)) { + if (invoke_id != 0) + fileStartPosition += requestedOctetCount; + /* we'll read the file in chunks + less than max_apdu to keep unsegmented */ + invoke_id = + Send_Atomic_Read_File_Stream + (Target_Device_Object_Instance, + Target_File_Object_Instance, fileStartPosition, + requestedOctetCount); + Current_Invoke_ID = invoke_id; + } else if (tsm_invoke_id_failed(invoke_id)) { + fprintf(stderr, "\rError: TSM Timeout!\r\n"); + tsm_free_invoke_id(invoke_id); + /* try again or abort? */ + break; + } + } else { + /* increment timer - exit if timed out */ + elapsed_seconds += (current_seconds - last_seconds); + if (elapsed_seconds > timeout_seconds) { + fprintf(stderr, "\rError: APDU Timeout!\r\n"); + break; + } + } + /* keep track of time for next check */ + last_seconds = current_seconds; + } + + return 0; +} diff --git a/bacnet-stack/demo/readprop/main.c b/bacnet-stack/demo/readprop/main.c index e69de29b..cd71dfbe 100644 --- a/bacnet-stack/demo/readprop/main.c +++ b/bacnet-stack/demo/readprop/main.c @@ -0,0 +1,236 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* command line tool that sends a BACnet service, and displays the reply */ +#include +#include +#include +#include +#include /* for time */ +#include +#include "bactext.h" +#include "iam.h" +#include "arf.h" +#include "tsm.h" +#include "address.h" +#include "config.h" +#include "bacdef.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "net.h" +#include "datalink.h" +#include "whois.h" +/* some demo stuff needed */ +#include "filename.h" +#include "handlers.h" +#include "client.h" +#include "txbuf.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/* global variables used in this file */ +static uint32_t Target_Device_Object_Instance = BACNET_MAX_INSTANCE; +static uint32_t Target_Object_Instance = BACNET_MAX_INSTANCE; +static BACNET_OBJECT_TYPE Target_Object_Type = OBJECT_ANALOG_INPUT; +static BACNET_PROPERTY_ID Target_Object_Property = PROP_ACKED_TRANSITIONS; +static int32_t Target_Object_Index = BACNET_ARRAY_ALL; + +static BACNET_ADDRESS Target_Address; +static bool Error_Detected = false; + +static void MyErrorHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, + BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + printf("BACnet Error: %s: %s\r\n", + bactext_error_class_name(error_class), + bactext_error_code_name(error_code)); + Error_Detected = true; +} + +void MyAbortHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + (void) server; + printf("BACnet Abort: %s\r\n", + bactext_abort_reason_name(abort_reason)); + Error_Detected = true; +} + +void MyRejectHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t reject_reason) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + printf("BACnet Reject: %s\r\n", + bactext_reject_reason_name(reject_reason)); + Error_Detected = true; +} + +static void Init_Service_Handlers(void) +{ + /* 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); + /* handle the data coming back from confirmed requests */ + apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_READ_PROPERTY, + handler_read_property_ack); + /* handle any errors coming back */ + apdu_set_error_handler(SERVICE_CONFIRMED_READ_PROPERTY, + MyErrorHandler); + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + 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; + uint8_t invoke_id = 0; + bool found = false; + + if (argc < 5) { + printf + ("%s device-instance object-type object-instance property [index]\r\n", + filename_remove_path(argv[0])); + return 0; + } + /* decode the command line parameters */ + Target_Device_Object_Instance = strtol(argv[1], NULL, 0); + Target_Object_Type = strtol(argv[2], NULL, 0); + Target_Object_Instance = strtol(argv[3], NULL, 0); + Target_Object_Property = strtol(argv[4], NULL, 0); + if (argc > 5) + Target_Object_Index = strtol(argv[5], NULL, 0); + if (Target_Device_Object_Instance >= BACNET_MAX_INSTANCE) { + fprintf(stderr, "device-instance=%u - it must be less than %u\r\n", + Target_Device_Object_Instance, BACNET_MAX_INSTANCE); + return 1; + } + if (Target_Object_Type > MAX_BACNET_OBJECT_TYPE) { + fprintf(stderr, "object-type=%u - it must be less than %u\r\n", + Target_Object_Type, MAX_BACNET_OBJECT_TYPE + 1); + return 1; + } + if (Target_Object_Instance > BACNET_MAX_INSTANCE) { + fprintf(stderr, "object-instance=%u - it must be less than %u\r\n", + Target_Object_Instance, BACNET_MAX_INSTANCE + 1); + return 1; + } + if (Target_Object_Property > MAX_BACNET_PROPERTY_ID) { + fprintf(stderr, "object-type=%u - it must be less than %u\r\n", + Target_Object_Property, MAX_BACNET_PROPERTY_ID + 1); + return 1; + } + + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + address_init(); + Init_Service_Handlers(); + if (!datalink_init(NULL)) + return 1; + /* configure the timeout values */ + last_seconds = time(NULL); + timeout_seconds = (Device_APDU_Timeout() / 1000) * + Device_Number_Of_APDU_Retries(); + /* try to bind with the device */ + Send_WhoIs(Target_Device_Object_Instance, + Target_Device_Object_Instance); + /* loop forever */ + for (;;) { + /* increment timer - exit if timed out */ + current_seconds = time(NULL); + + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + /* at least one second has passed */ + if (current_seconds != last_seconds) + tsm_timer_milliseconds(((current_seconds - + last_seconds) * 1000)); + if (Error_Detected) + break; + /* wait until the device is bound, or timeout and quit */ + found = address_bind_request(Target_Device_Object_Instance, + &max_apdu, &Target_Address); + if (found) { + if (invoke_id == 0) { + invoke_id = + Send_Read_Property_Request + (Target_Device_Object_Instance, Target_Object_Type, + Target_Object_Instance, Target_Object_Property, + Target_Object_Index); + } else if (tsm_invoke_id_free(invoke_id)) + break; + else if (tsm_invoke_id_failed(invoke_id)) { + fprintf(stderr, "\rError: TSM Timeout!\r\n"); + tsm_free_invoke_id(invoke_id); + /* try again or abort? */ + break; + } + } else { + /* increment timer - exit if timed out */ + elapsed_seconds += (current_seconds - last_seconds); + if (elapsed_seconds > timeout_seconds) { + printf("\rError: APDU Timeout!\r\n"); + break; + } + } + /* keep track of time for next check */ + last_seconds = current_seconds; + } + + return 0; +} diff --git a/bacnet-stack/demo/reinit/main.c b/bacnet-stack/demo/reinit/main.c index e69de29b..b0661ecb 100644 --- a/bacnet-stack/demo/reinit/main.c +++ b/bacnet-stack/demo/reinit/main.c @@ -0,0 +1,245 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* command line tool that sends a BACnet service, and displays the reply */ +#include +#include +#include +#include +#include /* for time */ +#include +#include "bactext.h" +#include "iam.h" +#include "arf.h" +#include "tsm.h" +#include "address.h" +#include "config.h" +#include "bacdef.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "net.h" +#include "datalink.h" +#include "whois.h" +#include "rd.h" +/* some demo stuff needed */ +#include "filename.h" +#include "handlers.h" +#include "client.h" +#include "txbuf.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/* global variables used in this file */ +static uint32_t Target_Device_Object_Instance = BACNET_MAX_INSTANCE; +static BACNET_ADDRESS Target_Address; +static BACNET_REINITIALIZED_STATE Reinitialize_State = + BACNET_REINIT_COLDSTART; +static char *Reinitialize_Password = NULL; + +static bool Error_Detected = false; + +static void MyErrorHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, + BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + printf("BACnet Error: %s: %s\r\n", + bactext_error_class_name(error_class), + bactext_error_code_name(error_code)); + Error_Detected = true; +} + +void MyAbortHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + (void) server; + printf("BACnet Abort: %s\r\n", + bactext_abort_reason_name(abort_reason)); + Error_Detected = true; +} + +void MyRejectHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t reject_reason) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + printf("BACnet Reject: %s\r\n", + bactext_reject_reason_name(reject_reason)); + Error_Detected = true; +} + +void MyReinitializeDeviceSimpleAckHandler(BACNET_ADDRESS * src, + uint8_t invoke_id) +{ + (void) src; + (void) invoke_id; + printf("ReinitializeDevice Acknowledged!\r\n"); +} + +static void Init_Service_Handlers(void) +{ + /* 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); + /* handle the ack coming back */ + apdu_set_confirmed_simple_ack_handler + (SERVICE_CONFIRMED_REINITIALIZE_DEVICE, + MyReinitializeDeviceSimpleAckHandler); + /* handle any errors coming back */ + apdu_set_error_handler(SERVICE_CONFIRMED_REINITIALIZE_DEVICE, + MyErrorHandler); + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + 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; + uint8_t invoke_id = 0; + bool found = false; + + if (argc < 3) { + /* note: priority 16 and 0 should produce the same end results... */ + printf("Usage: %s device-instance state [password]\r\n" + "Send BACnet ReinitializeDevice service to device.\r\n" + "\r\n" + "The device-instance can be 0 to %d.\r\n" + "Possible state values:\r\n" + " 0=coldstart\r\n" + " 1=warmstart\r\n" + " 2=startbackup\r\n" + " 3=endbackup\r\n" + " 4=startrestore\r\n" + " 5=endrestore\r\n" + " 6=abortrestore\r\n" + "The optional password is a character string of 1 to 20 characters.\r\n", + filename_remove_path(argv[0]), BACNET_MAX_INSTANCE - 1); + return 0; + } + /* decode the command line parameters */ + Target_Device_Object_Instance = strtol(argv[1], NULL, 0); + Reinitialize_State = strtol(argv[2], NULL, 0); + /* optional password */ + if (argc > 3) + Reinitialize_Password = argv[3]; + + if (Target_Device_Object_Instance >= BACNET_MAX_INSTANCE) { + fprintf(stderr, "device-instance=%u - it must be less than %u\r\n", + Target_Device_Object_Instance, BACNET_MAX_INSTANCE); + return 1; + } + + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + address_init(); + Init_Service_Handlers(); + if (!datalink_init(NULL)) + return 1; + /* configure the timeout values */ + last_seconds = time(NULL); + timeout_seconds = (Device_APDU_Timeout() / 1000) * + Device_Number_Of_APDU_Retries(); + /* try to bind with the device */ + Send_WhoIs(Target_Device_Object_Instance, + Target_Device_Object_Instance); + /* loop forever */ + for (;;) { + /* increment timer - exit if timed out */ + current_seconds = time(NULL); + + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive( + &src, + &Rx_Buf[0], + MAX_MPDU, + timeout); + + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + /* at least one second has passed */ + if (current_seconds != last_seconds) + tsm_timer_milliseconds(((current_seconds - + last_seconds) * 1000)); + if (Error_Detected) + break; + /* wait until the device is bound, or timeout and quit */ + found = address_bind_request(Target_Device_Object_Instance, + &max_apdu, &Target_Address); + if (found) { + if (invoke_id == 0) { + invoke_id = + Send_Reinitialize_Device_Request + (Target_Device_Object_Instance, Reinitialize_State, + Reinitialize_Password); + } else if (tsm_invoke_id_free(invoke_id)) + break; + else if (tsm_invoke_id_failed(invoke_id)) { + fprintf(stderr, "\rError: TSM Timeout!\r\n"); + tsm_free_invoke_id(invoke_id); + /* try again or abort? */ + break; + } + } else { + /* increment timer - exit if timed out */ + elapsed_seconds += (current_seconds - last_seconds); + if (elapsed_seconds > timeout_seconds) { + fprintf(stderr, "\rError: APDU Timeout!\r\n"); + break; + } + } + /* keep track of time for next check */ + last_seconds = current_seconds; + } + + return 0; +} diff --git a/bacnet-stack/demo/server/main.c b/bacnet-stack/demo/server/main.c index e69de29b..a958b40e 100644 --- a/bacnet-stack/demo/server/main.c +++ b/bacnet-stack/demo/server/main.c @@ -0,0 +1,143 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "address.h" +#include "bacdef.h" +#include "handlers.h" +#include "client.h" +#include "bacdcode.h" +#include "npdu.h" +#include "apdu.h" +#include "iam.h" +#include "tsm.h" +#include "device.h" +#include "bacfile.h" +#include "datalink.h" +#include "dcc.h" +#include "net.h" +#include "txbuf.h" +#include "lc.h" + +/* This is an example server application using the BACnet Stack */ + +/* buffers used for receiving */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; +static char *Network_Interface = NULL; + +static void Init_Service_Handlers(void) +{ + /* we need to handle who-is to support dynamic device binding */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, + handler_who_is); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, + handler_who_has); + /* 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); + /* Set the handlers for any confirmed services that we support. */ + /* We must implement read property - it's required! */ + apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY, + handler_read_property); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, + handler_write_property); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_ATOMIC_READ_FILE, + handler_atomic_read_file); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, + handler_atomic_write_file); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_REINITIALIZE_DEVICE, + handler_reinitialize_device); + apdu_set_unconfirmed_handler + (SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION, + handler_timesync_utc); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION, + handler_timesync); + /* handle communication so we can shutup when asked */ + apdu_set_confirmed_handler + (SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + handler_device_communication_control); +} + +static void cleanup(void) +{ + datalink_cleanup(); +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + uint16_t pdu_len = 0; + unsigned timeout = 100; /* milliseconds */ + time_t last_seconds = 0; + time_t current_seconds = 0; + + /* allow the device ID to be set */ + if (argc > 1) + Device_Set_Object_Instance_Number(strtol(argv[1], NULL, 0)); +#if defined(BACDL_BIP) + if (argc > 2) + Network_Interface = argv[2]; + if (argc > 3) + bip_set_port(strtol(argv[3], NULL, 0)); +#endif + printf("BACnet Server Demo\n" + "BACnet Device ID: %u\r\n", + Device_Object_Instance_Number()); + Init_Service_Handlers(); + if (!datalink_init(Network_Interface)) + return 1; + atexit(cleanup); + /* configure the timeout values */ + last_seconds = time(NULL); + /* broadcast an I-Am on startup */ + iam_send(&Handler_Transmit_Buffer[0]); + /* loop forever */ + for (;;) { + /* input */ + current_seconds = time(NULL); + + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + /* at least one second has passed */ + if (current_seconds != last_seconds) { + dcc_timer_seconds(current_seconds - last_seconds); + Load_Control_State_Machine_Handler(); + } + /* output */ + + /* blink LEDs, Turn on or off outputs, etc */ + } +} diff --git a/bacnet-stack/demo/timesync/main.c b/bacnet-stack/demo/timesync/main.c index e69de29b..03548a1a 100644 --- a/bacnet-stack/demo/timesync/main.c +++ b/bacnet-stack/demo/timesync/main.c @@ -0,0 +1,186 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* command line tool that sends a BACnet service, and displays the reply */ +#include +#include +#include +#include +#include /* for time */ +#include +#include "bactext.h" +#include "config.h" +#include "bacdef.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "net.h" +#include "datalink.h" +#include "timesync.h" +/* some demo stuff needed */ +#include "filename.h" +#include "handlers.h" +#include "client.h" +#include "txbuf.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/* global variables used in this file */ +#if 0 +static int32_t Target_Object_Instance_Min = 0; +static int32_t Target_Object_Instance_Max = BACNET_MAX_INSTANCE; +#endif + +static bool Error_Detected = false; + +void MyAbortHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + (void) server; + printf("BACnet Abort: %s\r\n", + bactext_abort_reason_name(abort_reason)); + Error_Detected = true; +} + +void MyRejectHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t reject_reason) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + printf("BACnet Reject: %s\r\n", + bactext_reject_reason_name(reject_reason)); + Error_Detected = true; +} + +static void Init_Service_Handlers(void) +{ + /* we need to handle who-is + to support dynamic device binding to us */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, + handler_who_is); + /* 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); + /* handle the reply (request) coming in */ + apdu_set_unconfirmed_handler + (SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION, + handler_timesync_utc); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION, + handler_timesync); + /* handle any errors coming back */ + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + uint16_t pdu_len = 0; + unsigned timeout = 100; /* milliseconds */ + time_t elapsed_seconds = 0; + time_t last_seconds = 0; + time_t current_seconds = 0; + time_t timeout_seconds = 0; + time_t rawtime; + struct tm *my_time; + BACNET_DATE bdate; + BACNET_TIME btime; + + /* FIXME: we could send directed time sync, and how do we send UTC? */ +#if 0 + if (argc < 2) { + printf("Usage: %s date time [device-instance]\r\n" + "Send BACnet TimeSynchronization request to all devices.\r\n" + "date format: year/month/day:dayofweek (e.g. 2006/4/1:6)\r\n" + "year: AD, such as 2006\r\n" + "month: 1=January, 12=December\r\n" + "day: 1-31\r\n" + "dayofweek: 1=Monday, 7=Sunday\r\n" + "\r\n" + "time format: hour:minute:second.hundredths (e.g. 23:59:59.12)\r\n" + "hour: 0-23\r\n" + "minute: 0-59\r\n" + "second: 0-59\r\n" + "hundredths: 0-99\r\n" + "\r\n" + "Optional device-instance sends a unicast time sync.\r\n", + filename_remove_path(argv[0])); + return 0; + } +#else + (void) argc; + (void) argv; +#endif + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + Init_Service_Handlers(); + if (!datalink_init(NULL)) + return 1; + /* configure the timeout values */ + last_seconds = time(NULL); + timeout_seconds = Device_APDU_Timeout() / 1000; + /* send the request */ + time(&rawtime); + my_time = localtime(&rawtime); + bdate.year = my_time->tm_year + 1900; + bdate.month = my_time->tm_mon + 1; + bdate.day = my_time->tm_mday; + bdate.wday = my_time->tm_wday ? my_time->tm_wday : 7; + btime.hour = my_time->tm_hour; + btime.min = my_time->tm_min; + btime.sec = my_time->tm_sec; + btime.hundredths = 0; + Send_TimeSync(&bdate, &btime); + /* loop forever - not necessary for time sync, but we can watch */ + for (;;) { + /* increment timer - exit if timed out */ + current_seconds = time(NULL); + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + if (Error_Detected) + break; + /* increment timer - exit if timed out */ + elapsed_seconds += (current_seconds - last_seconds); + if (elapsed_seconds > timeout_seconds) + break; + /* keep track of time for next check */ + last_seconds = current_seconds; + } + + return 0; +} diff --git a/bacnet-stack/demo/ucov/main.c b/bacnet-stack/demo/ucov/main.c index e69de29b..fabd8d42 100644 --- a/bacnet-stack/demo/ucov/main.c +++ b/bacnet-stack/demo/ucov/main.c @@ -0,0 +1,215 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* command line tool that sends a BACnet service */ +#include +#include +#include +#include +#include /* for time */ +#include +#include "bactext.h" +#include "iam.h" +#include "cov.h" +#include "tsm.h" +#include "address.h" +#include "config.h" +#include "bacdef.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "net.h" +#include "datalink.h" +#include "whois.h" +/* some demo stuff needed */ +#include "filename.h" +#include "handlers.h" +#include "client.h" +#include "txbuf.h" + +static void Init_Service_Handlers(void) +{ + /* 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); +} + +int main(int argc, char *argv[]) +{ + char *value_string = NULL; + bool status = false; + BACNET_COV_DATA cov_data; + uint8_t tag; + + if (argc < 7) { + /* note: priority 16 and 0 should produce the same end results... */ + printf("Usage: %s pid device-id object-type object-instance " + "time property tag value [priority] [index]\r\n" + "\r\n" + "pid:\r\n" + "Process Identifier for this broadcast.\r\n" + "\r\n" + "device-id:\r\n" + "The Initiating BACnet Device Object Instance number.\r\n" + "\r\n" + "object-type:\r\n" + "The monitored object type is the integer value of the\r\n" + "enumeration BACNET_OBJECT_TYPE in bacenum.h. For example,\r\n" + "if you were monitoring Analog Output 2, the object-type\r\n" + "would be 1.\r\n" + "\r\n" + "object-instance:\r\n" + "The monitored object instance number.\r\n" + "\r\n" + "time:\r\n" + "The subscription time remaining is conveyed in seconds.\r\n" + "\r\n" + "property:\r\n" + "The property is an integer value of the enumeration \r\n" + "BACNET_PROPERTY_ID in bacenum.h. For example, if you were\r\n" + "monitoring the Present Value property, you would use 85\r\n" + "as the property.\r\n" + "\r\n" + "tag:\r\n" + "Tag is the integer value of the enumeration BACNET_APPLICATION_TAG \r\n" + "in bacenum.h. It is the data type of the value that you are\r\n" + "monitoring. For example, if you were monitoring a REAL value, you would \r\n" + "use a tag of 4." + "\r\n" + "value:\r\n" + "The value is an ASCII representation of some type of data that you\r\n" + "are monitoring. It is encoded using the tag information provided. For\r\n" + "example, if you were writing a REAL value of 100.0, you would use \r\n" + "100.0 as the value.\r\n" + "\r\n" + "[priority]:\r\n" + "This optional parameter is used for reporting the priority of the\r\n" + "value. If no priority is given, none is sent, and the BACnet \r\n" + "standard requires that the value is reported at the lowest \r\n" + "priority (16) if the object property supports priorities.\r\n" + "\r\n" + "[index]\r\n" + "This optional integer parameter is the index number of an array.\r\n" + "If the property is an array, individual elements can be reported.\r\n" + "\r\n" + "Here is a brief overview of BACnet property and tags:\r\n" + "Certain properties are expected to be written with certain \r\n" + "application tags, so you probably need to know which ones to use\r\n" + "with each property of each object. It is almost safe to say that\r\n" + "given a property and an object and a table, the tag could be looked\r\n" + "up automatically. There may be a few exceptions to this, such as\r\n" + "the Any property type in the schedule object and the Present Value\r\n" + "accepting REAL, BOOLEAN, NULL, etc. Perhaps it would be simpler for\r\n" + "the demo to use this kind of table - but I also wanted to be able\r\n" + "to do negative testing by passing the wrong tag and have the server\r\n" + "return a reject message.\r\n" + "\r\n" + "Example:\r\n" + "If you want generate an unconfirmed COV,\r\n" + "you could send the following command:\r\n" + "%s 1 2 3 4 5 85 4 100.0\r\n" + "where 1=pid, 2=device-id, 3=AV, 4=object-id, 5=time,\r\n" + "85=Present-Value, 4=REAL, 100.0=value\r\n", + filename_remove_path(argv[0]), filename_remove_path(argv[0])); + return 0; + } + /* decode the command line parameters */ + cov_data.subscriberProcessIdentifier = strtol(argv[1], NULL, 0); + cov_data.initiatingDeviceIdentifier = strtol(argv[2], NULL, 0); + cov_data.monitoredObjectIdentifier.type = strtol(argv[3], NULL, 0); + cov_data.monitoredObjectIdentifier.instance = strtol(argv[4], NULL, 0); + cov_data.timeRemaining = strtol(argv[5], NULL, 0); + cov_data.listOfValues.propertyIdentifier = strtol(argv[6], NULL, 0); + tag = strtol(argv[7], NULL, 0); + value_string = argv[8]; + /* optional priority */ + if (argc > 9) + cov_data.listOfValues.priority = strtol(argv[9], NULL, 0); + else + cov_data.listOfValues.priority = BACNET_NO_PRIORITY; + /* optional index */ + if (argc > 10) + cov_data.listOfValues.propertyArrayIndex = + strtol(argv[10], NULL, 0); + else + cov_data.listOfValues.propertyArrayIndex = BACNET_ARRAY_ALL; + + if (cov_data.initiatingDeviceIdentifier >= BACNET_MAX_INSTANCE) { + fprintf(stderr, "device-instance=%u - it must be less than %u\r\n", + cov_data.initiatingDeviceIdentifier, BACNET_MAX_INSTANCE); + return 1; + } + if (cov_data.monitoredObjectIdentifier.type > MAX_BACNET_OBJECT_TYPE) { + fprintf(stderr, "object-type=%u - it must be less than %u\r\n", + cov_data.monitoredObjectIdentifier.type, + MAX_BACNET_OBJECT_TYPE + 1); + return 1; + } + if (cov_data.monitoredObjectIdentifier.instance > BACNET_MAX_INSTANCE) { + fprintf(stderr, "object-instance=%u - it must be less than %u\r\n", + cov_data.monitoredObjectIdentifier.instance, + BACNET_MAX_INSTANCE + 1); + return 1; + } + if (cov_data.listOfValues.propertyIdentifier > MAX_BACNET_PROPERTY_ID) { + fprintf(stderr, "object-type=%u - it must be less than %u\r\n", + cov_data.listOfValues.propertyIdentifier, + MAX_BACNET_PROPERTY_ID + 1); + return 1; + } + if (tag >= MAX_BACNET_APPLICATION_TAG) { + fprintf(stderr, "tag=%u - it must be less than %u\r\n", + tag, MAX_BACNET_APPLICATION_TAG); + return 1; + } + status = bacapp_parse_application_data(tag, + value_string, &cov_data.listOfValues.value); + if (!status) { + /* FIXME: show the expected entry format for the tag */ + fprintf(stderr, "unable to parse the tag value\r\n"); + return 1; + } + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + Init_Service_Handlers(); + if (!datalink_init(NULL)) + return 1; + /* only one value in our value list */ + cov_data.listOfValues.next = NULL; + ucov_notify_send(&Handler_Transmit_Buffer[0], &cov_data); + + return 0; +} diff --git a/bacnet-stack/demo/whohas/main.c b/bacnet-stack/demo/whohas/main.c index e69de29b..c10e4ee8 100644 --- a/bacnet-stack/demo/whohas/main.c +++ b/bacnet-stack/demo/whohas/main.c @@ -0,0 +1,183 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* command line tool that sends a BACnet service, and displays the reply */ +#include +#include +#include +#include +#include /* for time */ +#include +#include "bactext.h" +#include "iam.h" +#include "arf.h" +#include "tsm.h" +#include "address.h" +#include "config.h" +#include "bacdef.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "net.h" +#include "datalink.h" +#include "whohas.h" +/* some demo stuff needed */ +#include "filename.h" +#include "handlers.h" +#include "client.h" +#include "txbuf.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/* global variables used in this file */ +static BACNET_OBJECT_TYPE Target_Object_Type = MAX_BACNET_OBJECT_TYPE; +static uint32_t Target_Object_Instance = BACNET_MAX_INSTANCE; +static char *Target_Object_Name = NULL; + +static bool Error_Detected = false; + +void MyAbortHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + (void) server; + printf("BACnet Abort: %s\r\n", + bactext_abort_reason_name(abort_reason)); + Error_Detected = true; +} + +void MyRejectHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t reject_reason) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + printf("BACnet Reject: %s\r\n", + bactext_reject_reason_name(reject_reason)); + Error_Detected = true; +} + +static void Init_Service_Handlers(void) +{ + /* we need to handle who-is + to support dynamic device binding to us */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, + handler_who_is); + /* 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); + /* handle the reply (request) coming back */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_HAVE, + handler_i_have); + /* handle any errors coming back */ + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + uint16_t pdu_len = 0; + unsigned timeout = 100; /* milliseconds */ + time_t elapsed_seconds = 0; + time_t last_seconds = 0; + time_t current_seconds = 0; + time_t timeout_seconds = 0; + + if (argc < 2) { + /* note: priority 16 and 0 should produce the same end results... */ + printf("Usage: %s \r\n" + "Send BACnet WhoHas request to devices, and wait for responses.\r\n" + "\r\n" + "Use either:\r\n" + "The object-type can be 0 to %d.\r\n" + "The object-instance can be 0 to %d.\r\n" + "or:\r\n" + "The object-name can be any string of characters.\r\n", + filename_remove_path(argv[0]), + MAX_BACNET_OBJECT_TYPE - 1, BACNET_MAX_INSTANCE); + return 0; + } + /* decode the command line parameters */ + if (argc < 3) { + Target_Object_Name = argv[1]; + } else { + Target_Object_Type = strtol(argv[1], NULL, 0); + Target_Object_Instance = strtol(argv[2], NULL, 0); + if (Target_Object_Instance > BACNET_MAX_INSTANCE) { + fprintf(stderr, + "object-instance=%u - it must be less than %u\r\n", + Target_Object_Instance, BACNET_MAX_INSTANCE + 1); + return 1; + } + if (Target_Object_Type > MAX_BACNET_OBJECT_TYPE) { + fprintf(stderr, "object-type=%u - it must be less than %u\r\n", + Target_Object_Type, MAX_BACNET_OBJECT_TYPE + 1); + return 1; + } + } + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + Init_Service_Handlers(); + if (!datalink_init(NULL)) + return 1; + /* configure the timeout values */ + last_seconds = time(NULL); + timeout_seconds = Device_APDU_Timeout() / 1000; + /* send the request */ + if (argc < 3) + Send_WhoHas_Name(-1, -1, Target_Object_Name); + else + Send_WhoHas_Object(-1, -1, + Target_Object_Type, Target_Object_Instance); + /* loop forever */ + for (;;) { + /* increment timer - exit if timed out */ + current_seconds = time(NULL); + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + if (Error_Detected) + break; + /* increment timer - exit if timed out */ + elapsed_seconds += (current_seconds - last_seconds); + if (elapsed_seconds > timeout_seconds) + break; + /* keep track of time for next check */ + last_seconds = current_seconds; + } + + return 0; +} diff --git a/bacnet-stack/demo/whois/main.c b/bacnet-stack/demo/whois/main.c index e69de29b..612a302e 100644 --- a/bacnet-stack/demo/whois/main.c +++ b/bacnet-stack/demo/whois/main.c @@ -0,0 +1,200 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* command line tool that sends a BACnet service, and displays the reply */ +#include +#include +#include +#include +#include /* for time */ +#include +#include "bactext.h" +#include "iam.h" +#include "address.h" +#include "config.h" +#include "bacdef.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "net.h" +#include "datalink.h" +/* some demo stuff needed */ +#include "filename.h" +#include "handlers.h" +#include "client.h" +#include "txbuf.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/* global variables used in this file */ +static int32_t Target_Object_Instance_Min = 0; +static int32_t Target_Object_Instance_Max = BACNET_MAX_INSTANCE; + +static bool Error_Detected = false; + +void MyAbortHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + (void) server; + printf("BACnet Abort: %s\r\n", + bactext_abort_reason_name(abort_reason)); + Error_Detected = true; +} + +void MyRejectHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t reject_reason) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + printf("BACnet Reject: %s\r\n", + bactext_reject_reason_name(reject_reason)); + Error_Detected = true; +} + +static void Init_Service_Handlers(void) +{ + /* we need to handle who-is + to support dynamic device binding to us */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, + handler_who_is); + /* 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); + /* handle the reply (request) coming back */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, + handler_i_am_add); + /* handle any errors coming back */ + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +static void print_address_cache(void) +{ + int i, j; + BACNET_ADDRESS address; + uint32_t device_id = 0; + unsigned max_apdu = 0; + + fprintf(stderr, "Device\tMAC\tMaxAPDU\tNet\n"); + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + if (address_get_by_index(i, &device_id, &max_apdu, &address)) { + fprintf(stderr, "%u\t", device_id); + for (j = 0; j < address.mac_len; j++) { + fprintf(stderr, "%02X", address.mac[j]); + } + fprintf(stderr, "\t"); + fprintf(stderr, "%hu\t", max_apdu); + fprintf(stderr, "%hu\n", address.net); + } + } +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + uint16_t pdu_len = 0; + unsigned timeout = 100; /* milliseconds */ + time_t elapsed_seconds = 0; + time_t last_seconds = 0; + time_t current_seconds = 0; + time_t timeout_seconds = 0; + + if (argc < 2) { + printf + ("Usage: %s device-instance | device-instance-min device-instance-max\r\n" + "Send BACnet WhoIs request to devices, and wait for responses.\r\n" + "\r\n" "The device-instance can be 0 to %d, or -1 for ALL.\r\n" + "The device-instance can also be specified as a range.\r\n", + filename_remove_path(argv[0]), BACNET_MAX_INSTANCE); + return 0; + } + /* decode the command line parameters */ + if (argc < 3) { + Target_Object_Instance_Min = strtol(argv[1], NULL, 0); + Target_Object_Instance_Max = Target_Object_Instance_Min; + if (Target_Object_Instance_Min > BACNET_MAX_INSTANCE) { + fprintf(stderr, + "object-instance-min=%u - it must be less than %u\r\n", + Target_Object_Instance_Min, BACNET_MAX_INSTANCE + 1); + return 1; + } + } else { + Target_Object_Instance_Min = strtol(argv[1], NULL, 0); + Target_Object_Instance_Max = strtol(argv[2], NULL, 0); + if (Target_Object_Instance_Min > BACNET_MAX_INSTANCE) { + fprintf(stderr, + "object-instance-min=%u - it must be less than %u\r\n", + Target_Object_Instance_Min, BACNET_MAX_INSTANCE + 1); + return 1; + } + if (Target_Object_Instance_Max > BACNET_MAX_INSTANCE) { + fprintf(stderr, + "object-instance-max=%u - it must be less than %u\r\n", + Target_Object_Instance_Max, BACNET_MAX_INSTANCE + 1); + return 1; + } + } + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + Init_Service_Handlers(); + if (!datalink_init(NULL)) + return 1; + /* configure the timeout values */ + last_seconds = time(NULL); + timeout_seconds = Device_APDU_Timeout() / 1000; + /* send the request */ + Send_WhoIs(Target_Object_Instance_Min, Target_Object_Instance_Max); + /* loop forever */ + for (;;) { + /* increment timer - exit if timed out */ + current_seconds = time(NULL); + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + if (Error_Detected) + break; + /* increment timer - exit if timed out */ + elapsed_seconds += (current_seconds - last_seconds); + if (elapsed_seconds > timeout_seconds) + break; + /* keep track of time for next check */ + last_seconds = current_seconds; + } + print_address_cache(); + + return 0; +} diff --git a/bacnet-stack/demo/writefile/main.c b/bacnet-stack/demo/writefile/main.c index e69de29b..8fa936a6 100644 --- a/bacnet-stack/demo/writefile/main.c +++ b/bacnet-stack/demo/writefile/main.c @@ -0,0 +1,283 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* command line tool that sends a BACnet service, and displays the reply */ +#include +#include +#include +#include +#include /* for time */ +#include +#include "bactext.h" +#include "iam.h" +#include "awf.h" +#include "tsm.h" +#include "address.h" +#include "config.h" +#include "bacdef.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "net.h" +#include "datalink.h" +#include "whois.h" +/* some demo stuff needed */ +#include "filename.h" +#include "handlers.h" +#include "client.h" +#include "txbuf.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/* global variables used in this file */ +static uint32_t Target_File_Object_Instance = 4194303; +static uint32_t Target_Device_Object_Instance = 4194303; +static BACNET_ADDRESS Target_Address; +static char *Local_File_Name = NULL; +static bool End_Of_File_Detected = false; +static bool Error_Detected = false; +static uint8_t Current_Invoke_ID = 0; + +static void Atomic_Read_File_Error_Handler(BACNET_ADDRESS * src, + uint8_t invoke_id, + BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + printf("\r\nBACnet Error!\r\n"); + printf("Error Class: %s\r\n", bactext_error_class_name(error_class)); + printf("Error Code: %s\r\n", bactext_error_code_name(error_code)); + Error_Detected = true; +} + +void MyAbortHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + (void) server; + printf("\r\nBACnet Abort!\r\n"); + printf("Abort Reason: %s\r\n", + bactext_abort_reason_name(abort_reason)); + Error_Detected = true; +} + +void MyRejectHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t reject_reason) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + printf("\r\nBACnet Reject!\r\n"); + printf("Reject Reason: %s\r\n", + bactext_reject_reason_name(reject_reason)); + Error_Detected = true; +} + +static void LocalIAmHandler(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src) +{ + int len = 0; + uint32_t device_id = 0; + unsigned max_apdu = 0; + int segmentation = 0; + uint16_t vendor_id = 0; + + (void) src; + (void) service_len; + len = iam_decode_service_request(service_request, + &device_id, &max_apdu, &segmentation, &vendor_id); + if (len != -1) { + address_add(device_id, max_apdu, src); + } else + fprintf(stderr, "!\n"); + + return; +} + +static void Init_Service_Handlers(void) +{ + /* 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, + LocalIAmHandler); + /* 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); + /* handle any errors coming back */ + apdu_set_error_handler(SERVICE_CONFIRMED_ATOMIC_READ_FILE, + Atomic_Read_File_Error_Handler); + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + 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; + int fileStartPosition = 0; + unsigned requestedOctetCount = 0; + uint8_t invoke_id = 0; + bool found = false; + uint16_t my_max_apdu = 0; + FILE *pFile = NULL; + static BACNET_OCTET_STRING fileData; + size_t len = 0; + + if (argc < 4) { + /* FIXME: what about access method - record or stream? */ + printf("%s device-instance file-instance local-name\r\n", + filename_remove_path(argv[0])); + return 0; + } + /* decode the command line parameters */ + Target_Device_Object_Instance = strtol(argv[1], NULL, 0); + Target_File_Object_Instance = strtol(argv[2], NULL, 0); + Local_File_Name = argv[3]; + if (Target_Device_Object_Instance >= BACNET_MAX_INSTANCE) { + fprintf(stderr, "device-instance=%u - it must be less than %u\r\n", + Target_Device_Object_Instance, BACNET_MAX_INSTANCE); + return 1; + } + if (Target_File_Object_Instance >= BACNET_MAX_INSTANCE) { + fprintf(stderr, "file-instance=%u - it must be less than %u\r\n", + Target_File_Object_Instance, BACNET_MAX_INSTANCE + 1); + return 1; + } + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + address_init(); + Init_Service_Handlers(); + if (!datalink_init(NULL)) + return 1; + /* configure the timeout values */ + last_seconds = time(NULL); + timeout_seconds = (Device_APDU_Timeout() / 1000) * + Device_Number_Of_APDU_Retries(); + /* try to bind with the device */ + Send_WhoIs(Target_Device_Object_Instance, + Target_Device_Object_Instance); + /* loop forever */ + for (;;) { + /* increment timer - exit if timed out */ + current_seconds = time(NULL); + + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + /* at least one second has passed */ + if (current_seconds != last_seconds) + tsm_timer_milliseconds(((current_seconds - + last_seconds) * 1000)); + if (End_Of_File_Detected || Error_Detected) { + printf("\r\n"); + break; + } + /* wait until the device is bound, or timeout and quit */ + found = address_bind_request(Target_Device_Object_Instance, + &max_apdu, &Target_Address); + if (found) { + /* calculate the smaller of our APDU size or theirs + and remove the overhead of the APDU (varies depending on size). + note: we could fail if there is a bottle neck (router) + and smaller MPDU in betweeen. */ + if (max_apdu < MAX_APDU) + my_max_apdu = max_apdu; + else + my_max_apdu = MAX_APDU; + /* Typical sizes are 50, 128, 206, 480, 1024, and 1476 octets */ + if (my_max_apdu <= 50) + requestedOctetCount = my_max_apdu - 19; + else if (my_max_apdu <= 480) + requestedOctetCount = my_max_apdu - 32; + else if (my_max_apdu <= 1476) + requestedOctetCount = my_max_apdu - 64; + else + requestedOctetCount = my_max_apdu / 2; + /* has the previous invoke id expired or returned? + note: invoke ID = 0 is invalid, so it will be idle */ + if ((invoke_id == 0) || tsm_invoke_id_free(invoke_id)) { + if (invoke_id != 0) + fileStartPosition += requestedOctetCount; + /* we'll read the file in chunks + less than max_apdu to keep unsegmented */ + pFile = fopen(Local_File_Name, "rb"); + if (pFile) { + (void) fseek(pFile, fileStartPosition, SEEK_SET); + len = fread(octetstring_value(&fileData), 1, + requestedOctetCount, pFile); + if (len < requestedOctetCount) + End_Of_File_Detected = true; + octetstring_truncate(&fileData, len); + fclose(pFile); + } else + End_Of_File_Detected = true; + printf("\rSending %d bytes", (fileStartPosition + len)); + invoke_id = + Send_Atomic_Write_File_Stream + (Target_Device_Object_Instance, + Target_File_Object_Instance, fileStartPosition, + &fileData); + Current_Invoke_ID = invoke_id; + } else if (tsm_invoke_id_failed(invoke_id)) { + fprintf(stderr, "\rError: TSM Timeout!\r\n"); + tsm_free_invoke_id(invoke_id); + /* try again or abort? */ + break; + } + } else { + /* increment timer - exit if timed out */ + elapsed_seconds += (current_seconds - last_seconds); + if (elapsed_seconds > timeout_seconds) { + fprintf(stderr, "\rError: APDU Timeout!\r\n"); + break; + } + } + /* keep track of time for next check */ + last_seconds = current_seconds; + } + + return 0; +} diff --git a/bacnet-stack/demo/writeprop/main.c b/bacnet-stack/demo/writeprop/main.c index e69de29b..9ad374d7 100644 --- a/bacnet-stack/demo/writeprop/main.c +++ b/bacnet-stack/demo/writeprop/main.c @@ -0,0 +1,373 @@ +/************************************************************************** +* +* Copyright (C) 2006-2007 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. +* +*********************************************************************/ + +/* command line tool that sends a BACnet service, and displays the response */ +#include +#include +#include +#include +#include /* for time */ +#include +#include +#include /* toupper */ +#include "bactext.h" +#include "iam.h" +#include "arf.h" +#include "tsm.h" +#include "address.h" +#include "config.h" +#include "bacdef.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "net.h" +#include "datalink.h" +#include "whois.h" +/* some demo stuff needed */ +#include "filename.h" +#include "handlers.h" +#include "client.h" +#include "txbuf.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/* global variables used in this file */ +static uint32_t Target_Device_Object_Instance = BACNET_MAX_INSTANCE; +static uint32_t Target_Object_Instance = BACNET_MAX_INSTANCE; +static BACNET_OBJECT_TYPE Target_Object_Type = OBJECT_ANALOG_INPUT; +static BACNET_PROPERTY_ID Target_Object_Property = PROP_ACKED_TRANSITIONS; +/* array index value or BACNET_ARRAY_ALL */ +static int32_t Target_Object_Property_Index = BACNET_ARRAY_ALL; +#define MAX_PROPERTY_VALUES 16 +static BACNET_APPLICATION_DATA_VALUE + Target_Object_Property_Value[MAX_PROPERTY_VALUES]; + +/* 0 if not set, 1..16 if set */ +static uint8_t Target_Object_Property_Priority = 0; + +static BACNET_ADDRESS Target_Address; +static bool Error_Detected = false; + +static void MyErrorHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, + BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + printf("\r\nBACnet Error!\r\n"); + printf("Error Class: %s\r\n", bactext_error_class_name(error_class)); + printf("Error Code: %s\r\n", bactext_error_code_name(error_code)); + Error_Detected = true; +} + +void MyAbortHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + (void) server; + printf("\r\nBACnet Abort!\r\n"); + printf("Abort Reason: %s\r\n", + bactext_abort_reason_name(abort_reason)); + Error_Detected = true; +} + +void MyRejectHandler(BACNET_ADDRESS * src, + uint8_t invoke_id, uint8_t reject_reason) +{ + /* FIXME: verify src and invoke id */ + (void) src; + (void) invoke_id; + printf("\r\nBACnet Reject!\r\n"); + printf("Reject Reason: %s\r\n", + bactext_reject_reason_name(reject_reason)); + Error_Detected = true; +} + +void MyWritePropertySimpleAckHandler(BACNET_ADDRESS * src, + uint8_t invoke_id) +{ + (void) src; + (void) invoke_id; + printf("\r\nWriteProperty Acknowledged!\r\n"); +} + +static void Init_Service_Handlers(void) +{ + /* 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); + /* handle the ack coming back */ + apdu_set_confirmed_simple_ack_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, + MyWritePropertySimpleAckHandler); + /* handle any errors coming back */ + apdu_set_error_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, + MyErrorHandler); + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + 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; + uint8_t invoke_id = 0; + bool found = false; + char *value_string = NULL; + bool status = false; + int args_remaining = 0, tag_value_arg = 0, i = 0; + BACNET_APPLICATION_TAG property_tag; + uint8_t context_tag = 0; + + if (argc < 9) { + /* note: priority 16 and 0 should produce the same end results... */ + printf("Usage: %s device-instance object-type object-instance " + "property priority index tag value [tag value...]\r\n", + filename_remove_path(argv[0])); + if ((argc > 1) && (strcmp(argv[1], "--help") == 0)) { + printf("device-instance:\r\n" + "BACnet Device Object Instance number that you are trying to\r\n" + "communicate to. This number will be used to try and bind with\r\n" + "the device using Who-Is and I-Am services. For example, if you were\r\n" + "writing to Device Object 123, the device-instance would be 123.\r\n" + "\r\n" + "object-type:\r\n" + "The object type is the integer value of the enumeration\r\n" + "BACNET_OBJECT_TYPE in bacenum.h. It is the object that you are\r\n" + "writing to. For example if you were writing to Analog Output 2, \r\n" + "the object-type would be 1.\r\n" + "\r\n" + "object-instance:\r\n" + "This is the object instance number of the object that you are \r\n" + "writing to. For example, if you were writing to Analog Output 2, \r\n" + "the object-instance would be 2.\r\n" + "\r\n" + "property:\r\n" + "The property is an integer value of the enumeration \r\n" + "BACNET_PROPERTY_ID in bacenum.h. It is the property you are \r\n" + "writing to. For example, if you were writing to the Present Value\r\n" + "property, you would use 85 as the property.\r\n" + "\r\n" + "priority:\r\n" + "This parameter is used for setting the priority of the\r\n" + "write. If Priority 0 is given, no priority is sent. The BACnet \r\n" + "standard states that the value is written at the lowest \r\n" + "priority (16) if the object property supports priorities\r\n" + "when no priority is sent.\r\n" + "\r\n" + "index\r\n" + "This integer parameter is the index number of an array.\r\n" + "If the property is an array, individual elements can be written\r\n" + "to if supported. If this parameter is -1, the index is ignored.\r\n" + "\r\n" + "tag:\r\n" + "Tag is the integer value of the enumeration BACNET_APPLICATION_TAG \r\n" + "in bacenum.h. It is the data type of the value that you are\r\n" + "writing. For example, if you were writing a REAL value, you would \r\n" + "use a tag of 4.\r\n" + "Context tags are created using two tags in a row. The context tag\r\n" + "is preceded by a C. Ctag tag. C2 4 creates a context 2 tagged REAL.\r\n" + "\r\n" + "value:\r\n" + "The value is an ASCII representation of some type of data that you\r\n" + "are writing. It is encoded using the tag information provided. For\r\n" + "example, if you were writing a REAL value of 100.0, you would use \r\n" + "100.0 as the value.\r\n" + "\r\n" + "Here is a brief overview of BACnet property and tags:\r\n" + "Certain properties are expected to be written with certain \r\n" + "application tags, so you probably need to know which ones to use\r\n" + "with each property of each object. It is almost safe to say that\r\n" + "given a property and an object and a table, the tag could be looked\r\n" + "up automatically. There may be a few exceptions to this, such as\r\n" + "the Any property type in the schedule object and the Present Value\r\n" + "accepting REAL, BOOLEAN, NULL, etc. Perhaps it would be simpler for\r\n" + "the demo to use this kind of table - but I also wanted to be able\r\n" + "to do negative testing by passing the wrong tag and have the server\r\n" + "return a reject message.\r\n" + "\r\n" + "Example:\r\n" + "If you want send a 100 to the Present-Value in the Analog Output\r\n" + "at priority 16, you could send the following command:\r\n" + "%s 123 1 0 85 4 100\r\n" + "You could also send a relinquish command:\r\n" + "%s 123 1 0 85 0 0\r\n", + filename_remove_path(argv[0]), + filename_remove_path(argv[0])); + } + return 0; + } + /* decode the command line parameters */ + Target_Device_Object_Instance = strtol(argv[1], NULL, 0); + Target_Object_Type = strtol(argv[2], NULL, 0); + Target_Object_Instance = strtol(argv[3], NULL, 0); + Target_Object_Property = strtol(argv[4], NULL, 0); + Target_Object_Property_Priority = strtol(argv[5], NULL, 0); + Target_Object_Property_Index = strtol(argv[6], NULL, 0); + if (Target_Object_Property_Index == -1) + Target_Object_Property_Index = BACNET_ARRAY_ALL; + if (Target_Device_Object_Instance > BACNET_MAX_INSTANCE) { + fprintf(stderr, "device-instance=%u - it must be less than %u\r\n", + Target_Device_Object_Instance, BACNET_MAX_INSTANCE + 1); + return 1; + } + if (Target_Object_Type > MAX_BACNET_OBJECT_TYPE) { + fprintf(stderr, "object-type=%u - it must be less than %u\r\n", + Target_Object_Type, MAX_BACNET_OBJECT_TYPE + 1); + return 1; + } + if (Target_Object_Instance > BACNET_MAX_INSTANCE) { + fprintf(stderr, "object-instance=%u - it must be less than %u\r\n", + Target_Object_Instance, BACNET_MAX_INSTANCE + 1); + return 1; + } + if (Target_Object_Property > MAX_BACNET_PROPERTY_ID) { + fprintf(stderr, "object-type=%u - it must be less than %u\r\n", + Target_Object_Property, MAX_BACNET_PROPERTY_ID + 1); + return 1; + } + args_remaining = (argc - 7); + for (i = 0; i < MAX_PROPERTY_VALUES; i++) { + tag_value_arg = 7 + (i * 2); + /* special case for context tagged values */ + if (toupper(argv[tag_value_arg][0]) == 'C') { + context_tag = strtol(&argv[tag_value_arg][1], NULL, 0); + tag_value_arg++; + args_remaining--; + Target_Object_Property_Value[i].context_tag = context_tag; + Target_Object_Property_Value[i].context_specific = true; + } else { + Target_Object_Property_Value[i].context_specific = false; + } + property_tag = strtol(argv[tag_value_arg], NULL, 0); + value_string = argv[tag_value_arg + 1]; + args_remaining -= 2; + /* printf("tag[%d]=%u value[%d]=%s\r\n", + i, property_tag, i, value_string); */ + if (property_tag >= MAX_BACNET_APPLICATION_TAG) { + fprintf(stderr, "tag=%u - it must be less than %u\r\n", + property_tag, MAX_BACNET_APPLICATION_TAG); + return 1; + } + status = bacapp_parse_application_data(property_tag, + value_string, &Target_Object_Property_Value[i]); + if (!status) { + /* FIXME: show the expected entry format for the tag */ + fprintf(stderr, "unable to parse the tag value\r\n"); + return 1; + } + Target_Object_Property_Value[i].next = NULL; + if (i > 0) { + Target_Object_Property_Value[i - 1].next = + &Target_Object_Property_Value[i]; + } + if (args_remaining <= 0) + break; + } + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + address_init(); + Init_Service_Handlers(); + if (!datalink_init(NULL)) + return 1; + /* configure the timeout values */ + last_seconds = time(NULL); + timeout_seconds = (Device_APDU_Timeout() / 1000) * + Device_Number_Of_APDU_Retries(); + /* try to bind with the device */ + Send_WhoIs(Target_Device_Object_Instance, + Target_Device_Object_Instance); + /* loop forever */ + for (;;) { + /* increment timer - exit if timed out */ + current_seconds = time(NULL); + + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + /* at least one second has passed */ + if (current_seconds != last_seconds) + tsm_timer_milliseconds(((current_seconds - + last_seconds) * 1000)); + if (Error_Detected) + break; + /* wait until the device is bound, or timeout and quit */ + found = address_bind_request(Target_Device_Object_Instance, + &max_apdu, &Target_Address); + if (found) { + if (invoke_id == 0) { + invoke_id = + Send_Write_Property_Request + (Target_Device_Object_Instance, Target_Object_Type, + Target_Object_Instance, Target_Object_Property, + &Target_Object_Property_Value[0], + Target_Object_Property_Priority, + Target_Object_Property_Index); + } else if (tsm_invoke_id_free(invoke_id)) + break; + else if (tsm_invoke_id_failed(invoke_id)) { + fprintf(stderr, "\rError: TSM Timeout!\r\n"); + tsm_free_invoke_id(invoke_id); + /* try again or abort? */ + break; + } + } else { + /* increment timer - exit if timed out */ + elapsed_seconds += (current_seconds - last_seconds); + if (elapsed_seconds > timeout_seconds) { + printf("\rError: APDU Timeout!\r\n"); + break; + } + } + /* keep track of time for next check */ + last_seconds = current_seconds; + } + + return 0; +} diff --git a/bacnet-stack/dlmstp.h b/bacnet-stack/dlmstp.h index e69de29b..c7465bdc 100644 --- a/bacnet-stack/dlmstp.h +++ b/bacnet-stack/dlmstp.h @@ -0,0 +1,107 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307 + USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef DLMSTP_H +#define DLMSTP_H + +#include +#include +#include +#include "bacdef.h" +#include "npdu.h" + +/* defines specific to MS/TP */ +#define MAX_HEADER (2+1+1+1+2+1+2+1) +#define MAX_MPDU (MAX_HEADER+MAX_PDU) + +typedef struct dlmstp_packet { + bool ready; /* true if ready to be sent or received */ + BACNET_ADDRESS address; /* source address */ + uint8_t frame_type; /* type of message */ + unsigned pdu_len; /* packet length */ + uint8_t pdu[MAX_MPDU]; /* packet */ +} DLMSTP_PACKET; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + void dlmstp_init(void); + void dlmstp_cleanup(void); + void dlmstp_millisecond_timer(void); + + /* returns number of bytes sent on success, negative on failure */ + int dlmstp_send_pdu(BACNET_ADDRESS * dest, /* destination address */ + BACNET_NPDU_DATA * npdu_data, /* network information */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len); /* number of bytes of data */ + + /* returns the number of octets in the PDU, or zero on failure */ + uint16_t dlmstp_receive(BACNET_ADDRESS * src, /* source address */ + uint8_t * pdu, /* PDU data */ + uint16_t max_pdu, /* amount of space available in the PDU */ + unsigned timeout); /* milliseconds to wait for a packet */ + + /* This parameter represents the value of the Max_Info_Frames property of */ + /* the node's Device object. The value of Max_Info_Frames specifies the */ + /* maximum number of information frames the node may send before it must */ + /* pass the token. Max_Info_Frames may have different values on different */ + /* nodes. This may be used to allocate more or less of the available link */ + /* bandwidth to particular nodes. If Max_Info_Frames is not writable in a */ + /* node, its value shall be 1. */ + void dlmstp_set_max_info_frames(unsigned max_info_frames); + unsigned dlmstp_max_info_frames(void); + + /* This parameter represents the value of the Max_Master property of the */ + /* node's Device object. The value of Max_Master specifies the highest */ + /* allowable address for master nodes. The value of Max_Master shall be */ + /* less than or equal to 127. If Max_Master is not writable in a node, */ + /* its value shall be 127. */ + void dlmstp_set_max_master(uint8_t max_master); + uint8_t dlmstp_max_master(void); + + void dlmstp_set_my_address(uint8_t my_address); + void dlmstp_get_my_address(BACNET_ADDRESS * my_address); + void dlmstp_get_broadcast_address(BACNET_ADDRESS * dest); /* destination address */ + + /* MS/TP state machine functions */ + uint16_t dlmstp_put_receive(uint8_t src, /* source MS/TP address */ + uint8_t * pdu, /* PDU data */ + uint16_t pdu_len); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/ethernet.h b/bacnet-stack/ethernet.h index e69de29b..12ed8632 100644 --- a/bacnet-stack/ethernet.h +++ b/bacnet-stack/ethernet.h @@ -0,0 +1,76 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef ETHERNET_H +#define ETHERNET_H + +#include +#include +#include +#include "bacdef.h" +#include "npdu.h" + +/* specific defines for Ethernet */ +#define MAX_HEADER (6+6+2+1+1+1) +#define MAX_MPDU (MAX_HEADER+MAX_PDU) + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + bool ethernet_valid(void); + void ethernet_cleanup(void); + bool ethernet_init(char *interface_name); + +/* function to send a packet out the 802.2 socket */ +/* returns number of bytes sent on success, negative on failure */ + int ethernet_send_pdu(BACNET_ADDRESS * dest, /* destination address */ + BACNET_NPDU_DATA * npdu_data, /* network information */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len); /* number of bytes of data */ + +/* receives an 802.2 framed packet */ +/* returns the number of octets in the PDU, or zero on failure */ + uint16_t ethernet_receive(BACNET_ADDRESS * src, /* source address */ + uint8_t * pdu, /* PDU data */ + uint16_t max_pdu, /* amount of space available in the PDU */ + unsigned timeout); /* milliseconds to wait for a packet */ + + void ethernet_set_my_address(BACNET_ADDRESS * my_address); + void ethernet_get_my_address(BACNET_ADDRESS * my_address); + void ethernet_get_broadcast_address(BACNET_ADDRESS * dest); /* destination address */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/filename.c b/bacnet-stack/filename.c index e69de29b..449ad3f8 100644 --- a/bacnet-stack/filename.c +++ b/bacnet-stack/filename.c @@ -0,0 +1,101 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include + +char *filename_remove_path(const char *filename_in) +{ + char *filename_out = NULL; + + /* allow the device ID to be set */ + if (filename_in) { + filename_out = strrchr(filename_in, '\\'); + if (!filename_out) + filename_out = strrchr(filename_in, '/'); + /* go beyond the slash */ + if (filename_out) + filename_out++; + } + + return filename_out; +} + +#ifdef TEST +#include +#include + +#include "ctest.h" + +void testFilename(Test * pTest) +{ + char *data1 = "c:\\Joshua\\run"; + char *data2 = "/home/Anna/run"; + char *data3 = "c:\\Program Files\\Christopher\\run.exe"; + char *data4 = "//Mary/data/run"; + char *filename = NULL; + + filename = filename_remove_path(data1); + ct_test(pTest, strcmp("run", filename) == 0); + filename = filename_remove_path(data2); + ct_test(pTest, strcmp("run", filename) == 0); + filename = filename_remove_path(data3); + ct_test(pTest, strcmp("run.exe", filename) == 0); + filename = filename_remove_path(data4); + ct_test(pTest, strcmp("run", filename) == 0); + + return; +} + +#ifdef TEST_FILENAME +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("filename remove path", NULL); + + /* individual tests */ + rc = ct_addTestFunction(pTest, testFilename); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_FILENAME */ +#endif /* TEST */ diff --git a/bacnet-stack/filename.h b/bacnet-stack/filename.h index e69de29b..38ad0cd6 100644 --- a/bacnet-stack/filename.h +++ b/bacnet-stack/filename.h @@ -0,0 +1,46 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef FILENAME_H +#define FILENAME_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + char *filename_remove_path(const char *filename_in); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/iam.c b/bacnet-stack/iam.c index e69de29b..5ae007fe 100755 --- a/bacnet-stack/iam.c +++ b/bacnet-stack/iam.c @@ -0,0 +1,281 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacdef.h" +#include "npdu.h" +#include "dcc.h" +#include "datalink.h" +#include "device.h" +#include "bacdcode.h" +#include "address.h" + +/* encode I-Am service */ +int iam_encode_apdu(uint8_t * apdu, + uint32_t device_id, + unsigned max_apdu, int segmentation, uint16_t vendor_id) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST; + apdu[1] = SERVICE_UNCONFIRMED_I_AM; /* service choice */ + apdu_len = 2; + len = encode_tagged_object_id(&apdu[apdu_len], + OBJECT_DEVICE, device_id); + apdu_len += len; + len = encode_tagged_unsigned(&apdu[apdu_len], max_apdu); + apdu_len += len; + len = encode_tagged_enumerated(&apdu[apdu_len], segmentation); + apdu_len += len; + len = encode_tagged_unsigned(&apdu[apdu_len], vendor_id); + apdu_len += len; + } + + return apdu_len; +} + +int iam_decode_service_request(uint8_t * apdu, + uint32_t * pDevice_id, + unsigned *pMax_apdu, int *pSegmentation, uint16_t * pVendor_id) +{ + int len = 0; + int apdu_len = 0; /* total length of the apdu, return value */ + int object_type = 0; /* should be a Device Object */ + uint32_t object_instance = 0; + uint8_t tag_number = 0; + uint32_t len_value = 0; + uint32_t decoded_value = 0; + int decoded_integer = 0; + + /* OBJECT ID - object id */ + len = + decode_tag_number_and_value(&apdu[apdu_len], &tag_number, + &len_value); + apdu_len += len; + if (tag_number != BACNET_APPLICATION_TAG_OBJECT_ID) + return -1; + len = + decode_object_id(&apdu[apdu_len], &object_type, &object_instance); + apdu_len += len; + if (object_type != OBJECT_DEVICE) + return -1; + if (pDevice_id) + *pDevice_id = object_instance; + /* MAX APDU - unsigned */ + len = + decode_tag_number_and_value(&apdu[apdu_len], &tag_number, + &len_value); + apdu_len += len; + if (tag_number != BACNET_APPLICATION_TAG_UNSIGNED_INT) + return -1; + len = decode_unsigned(&apdu[apdu_len], len_value, &decoded_value); + apdu_len += len; + if (pMax_apdu) + *pMax_apdu = decoded_value; + /* Segmentation - enumerated */ + len = + decode_tag_number_and_value(&apdu[apdu_len], &tag_number, + &len_value); + apdu_len += len; + if (tag_number != BACNET_APPLICATION_TAG_ENUMERATED) + return -1; + len = decode_enumerated(&apdu[apdu_len], len_value, &decoded_integer); + apdu_len += len; + if (decoded_integer >= MAX_BACNET_SEGMENTATION) + return -1; + if (pSegmentation) + *pSegmentation = decoded_integer; + /* Vendor ID - unsigned16 */ + len = + decode_tag_number_and_value(&apdu[apdu_len], &tag_number, + &len_value); + apdu_len += len; + if (tag_number != BACNET_APPLICATION_TAG_UNSIGNED_INT) + return -1; + len = decode_unsigned(&apdu[apdu_len], len_value, &decoded_value); + apdu_len += len; + if (decoded_value > 0xFFFF) + return -1; + if (pVendor_id) + *pVendor_id = (uint16_t) decoded_value; + + return apdu_len; +} + +int iam_send(uint8_t * buffer) +{ + int len = 0; + int pdu_len = 0; + BACNET_ADDRESS dest; + int bytes_sent = 0; + BACNET_NPDU_DATA npdu_data; + + /* if we are forbidden to send, don't send! */ + if (!dcc_communication_enabled()) + return 0; + + /* I-Am is a global broadcast */ + datalink_get_broadcast_address(&dest); + /* encode the NPDU portion of the packet */ + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&buffer[0], &dest, NULL, &npdu_data); + /* encode the APDU portion of the packet */ + len = iam_encode_apdu(&buffer[pdu_len], + Device_Object_Instance_Number(), + MAX_APDU, SEGMENTATION_NONE, Device_Vendor_Identifier()); + pdu_len += len; + /* send data */ + bytes_sent = datalink_send_pdu(&dest, &npdu_data, &buffer[0], pdu_len); + + return bytes_sent; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +int iam_decode_apdu(uint8_t * apdu, + uint32_t * pDevice_id, + unsigned *pMax_apdu, int *pSegmentation, uint16_t * pVendor_id) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + /* valid data? */ + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST) + return -1; + if (apdu[1] != SERVICE_UNCONFIRMED_I_AM) + return -1; + apdu_len = iam_decode_service_request(&apdu[2], + pDevice_id, pMax_apdu, pSegmentation, pVendor_id); + + return apdu_len; +} + +void testIAm(Test * pTest) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + uint32_t device_id = 42; + unsigned max_apdu = 480; + int segmentation = SEGMENTATION_NONE; + uint16_t vendor_id = 42; + uint32_t test_device_id = 0; + unsigned test_max_apdu = 0; + int test_segmentation = 0; + uint16_t test_vendor_id = 0; + + len = iam_encode_apdu(&apdu[0], + device_id, max_apdu, segmentation, vendor_id); + ct_test(pTest, len != 0); + + len = iam_decode_apdu(&apdu[0], + &test_device_id, + &test_max_apdu, &test_segmentation, &test_vendor_id); + + ct_test(pTest, len != -1); + ct_test(pTest, test_device_id == device_id); + ct_test(pTest, test_vendor_id == vendor_id); + ct_test(pTest, test_max_apdu == max_apdu); + ct_test(pTest, test_segmentation == segmentation); +} + +#ifdef TEST_IAM +/* dummy function stubs */ +int datalink_send_pdu(BACNET_ADDRESS * dest, + BACNET_NPDU_DATA * npdu_data, uint8_t * pdu, unsigned pdu_len) +{ + (void) dest; + (void) npdu_data; + (void) pdu; + (void) pdu_len; + + return 0; +} + +void datalink_get_broadcast_address(BACNET_ADDRESS * dest) +{ /* destination address */ + (void) dest; +} + +uint16_t Device_Vendor_Identifier(void) +{ + return 0; +} + +uint32_t Device_Object_Instance_Number(void) +{ + return 0; +} + +void address_add_binding(uint32_t device_id, + unsigned max_apdu, BACNET_ADDRESS * src) +{ + (void) device_id; + (void) max_apdu; + (void) src; +} + +/* dummy for apdu dependency */ +void tsm_free_invoke_id(uint8_t invokeID) +{ + /* dummy stub for testing */ + (void) invokeID; +} + + +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet I-Am", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testIAm); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_IAM */ +#endif /* TEST */ diff --git a/bacnet-stack/iam.h b/bacnet-stack/iam.h index e69de29b..3c5674da 100644 --- a/bacnet-stack/iam.h +++ b/bacnet-stack/iam.h @@ -0,0 +1,67 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef IAM_H +#define IAM_H + +#include +#include +#include "bacdef.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + int iam_encode_apdu(uint8_t * apdu, + uint32_t device_id, + unsigned max_apdu, int segmentation, uint16_t vendor_id); + + int iam_decode_service_request(uint8_t * apdu, + uint32_t * pDevice_id, + unsigned *pMax_apdu, int *pSegmentation, uint16_t * pVendor_id); + + int iam_send(uint8_t * buffer); + +#ifdef TEST +#include "ctest.h" + int iam_decode_apdu(uint8_t * apdu, + uint32_t * pDevice_id, + unsigned *pMax_apdu, int *pSegmentation, uint16_t * pVendor_id); + + void testIAm(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/ihave.c b/bacnet-stack/ihave.c index e69de29b..ed660ffa 100644 --- a/bacnet-stack/ihave.c +++ b/bacnet-stack/ihave.c @@ -0,0 +1,196 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacdcode.h" +#include "bacdef.h" +#include "ihave.h" + +int ihave_encode_apdu(uint8_t * apdu, BACNET_I_HAVE_DATA * data) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu && data) { + apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST; + apdu[1] = SERVICE_UNCONFIRMED_I_HAVE; + apdu_len = 2; + /* deviceIdentifier */ + len = encode_tagged_object_id(&apdu[apdu_len], + data->device_id.type, data->device_id.instance); + apdu_len += len; + /* objectIdentifier */ + len = encode_tagged_object_id(&apdu[apdu_len], + data->object_id.type, data->object_id.instance); + apdu_len += len; + /* objectName */ + len = encode_tagged_character_string(&apdu[apdu_len], + &data->object_name); + apdu_len += len; + } + + return apdu_len; +} + +/* decode the service request only */ +int ihave_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_I_HAVE_DATA * data) +{ + int len = 0; + uint8_t tag_number = 0; + uint32_t len_value = 0; + int decoded_type = 0; /* for decoding */ + + if (apdu_len && data) { + /* deviceIdentifier */ + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + if (tag_number == BACNET_APPLICATION_TAG_OBJECT_ID) { + len += decode_object_id(&apdu[len], &decoded_type, + &data->device_id.instance); + data->device_id.type = decoded_type; + } else + return -1; + /* objectIdentifier */ + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + if (tag_number == BACNET_APPLICATION_TAG_OBJECT_ID) { + len += decode_object_id(&apdu[len], &decoded_type, + &data->object_id.instance); + data->object_id.type = decoded_type; + } else + return -1; + /* objectName */ + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + if (tag_number == BACNET_APPLICATION_TAG_CHARACTER_STRING) { + len += decode_character_string(&apdu[len], len_value, + &data->object_name); + } else + return -1; + } else + return -1; + + return len; +} + +int ihave_decode_apdu(uint8_t * apdu, + unsigned apdu_len, BACNET_I_HAVE_DATA * data) +{ + int len = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST) + return -1; + if (apdu[1] != SERVICE_UNCONFIRMED_I_HAVE) + return -1; + len = ihave_decode_service_request(&apdu[2], apdu_len - 2, data); + + return len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testIHaveData(Test * pTest, BACNET_I_HAVE_DATA * data) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + BACNET_I_HAVE_DATA test_data; + + len = ihave_encode_apdu(&apdu[0], data); + ct_test(pTest, len != 0); + apdu_len = len; + + len = ihave_decode_apdu(&apdu[0], apdu_len, &test_data); + ct_test(pTest, len != -1); + ct_test(pTest, test_data.device_id.type == data->device_id.type); + ct_test(pTest, test_data.device_id.instance == + data->device_id.instance); + ct_test(pTest, test_data.object_id.type == data->object_id.type); + ct_test(pTest, test_data.object_id.instance == + data->object_id.instance); + ct_test(pTest, characterstring_same(&test_data.object_name, + &data->object_name)); +} + +void testIHave(Test * pTest) +{ + BACNET_I_HAVE_DATA data; + + characterstring_init_ansi(&data.object_name, "Patricia - my love!"); + data.device_id.type = OBJECT_DEVICE; + for (data.device_id.instance = 1; + data.device_id.instance <= BACNET_MAX_INSTANCE; + data.device_id.instance <<= 1) { + for (data.object_id.type = OBJECT_ANALOG_INPUT; + data.object_id.type <= MAX_BACNET_OBJECT_TYPE; + data.object_id.type++) { + for (data.object_id.instance = 1; + data.object_id.instance <= BACNET_MAX_INSTANCE; + data.object_id.instance <<= 1) { + testIHaveData(pTest, &data); + } + } + } +} + +#ifdef TEST_I_HAVE +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet I-Have", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testIHave); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_WHOIS */ +#endif /* TEST */ diff --git a/bacnet-stack/ihave.h b/bacnet-stack/ihave.h index e69de29b..827815d4 100644 --- a/bacnet-stack/ihave.h +++ b/bacnet-stack/ihave.h @@ -0,0 +1,67 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef IHAVE_H +#define IHAVE_H + +#include +#include +#include "bacstr.h" + +typedef struct BACnet_I_Have_Data { + BACNET_OBJECT_ID device_id; + BACNET_OBJECT_ID object_id; + BACNET_CHARACTER_STRING object_name; +} BACNET_I_HAVE_DATA; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + int ihave_encode_apdu(uint8_t * apdu, BACNET_I_HAVE_DATA * data); + + int ihave_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_I_HAVE_DATA * data); + + int ihave_decode_apdu(uint8_t * apdu, + unsigned apdu_len, BACNET_I_HAVE_DATA * data); + +#ifdef TEST +#include "ctest.h" + void testIHave(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/indtext.c b/bacnet-stack/indtext.c index e69de29b..02cd41c6 100644 --- a/bacnet-stack/indtext.c +++ b/bacnet-stack/indtext.c @@ -0,0 +1,228 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include +#include "indtext.h" + +#if defined(__BORLANDC__) || defined(_MSC_VER) +#define strcasecmp stricmp +#endif + +bool indtext_by_string(INDTEXT_DATA * data_list, + const char *search_name, unsigned *found_index) +{ + bool found = false; + unsigned index = 0; + + if (data_list && search_name) { + while (data_list->pString) { + if (strcmp(data_list->pString, search_name) == 0) { + index = data_list->index; + found = true; + break; + } + data_list++; + } + } + + if (found && found_index) + *found_index = index; + + return found; +} + +/* case insensitive version */ +bool indtext_by_istring(INDTEXT_DATA * data_list, + const char *search_name, unsigned *found_index) +{ + bool found = false; + unsigned index = 0; + + if (data_list && search_name) { + while (data_list->pString) { + if (strcasecmp(data_list->pString, search_name) == 0) { + index = data_list->index; + found = true; + break; + } + data_list++; + } + } + + if (found && found_index) + *found_index = index; + + return found; +} + +unsigned indtext_by_string_default(INDTEXT_DATA * data_list, + const char *search_name, unsigned default_index) +{ + unsigned index = 0; + + if (!indtext_by_string(data_list, search_name, &index)) + index = default_index; + + return index; +} + +unsigned indtext_by_istring_default(INDTEXT_DATA * data_list, + const char *search_name, unsigned default_index) +{ + unsigned index = 0; + + if (!indtext_by_istring(data_list, search_name, &index)) + index = default_index; + + return index; +} + +const char *indtext_by_index_default(INDTEXT_DATA * data_list, + unsigned index, const char *default_string) +{ + const char *pString = NULL; + + if (data_list) { + while (data_list->pString) { + if (data_list->index == index) { + pString = data_list->pString; + break; + } + data_list++; + } + } + + return pString ? pString : default_string; +} + +const char *indtext_by_index_split_default(INDTEXT_DATA * data_list, + int index, + int split_index, + const char *before_split_default_name, const char *default_name) +{ + if (index < split_index) + return indtext_by_index_default(data_list, index, + before_split_default_name); + else + return indtext_by_index_default(data_list, index, default_name); +}; + + +const char *indtext_by_index(INDTEXT_DATA * data_list, unsigned index) +{ + return indtext_by_index_default(data_list, index, NULL); +} + +unsigned indtext_count(INDTEXT_DATA * data_list) +{ + unsigned count = 0; /* return value */ + + if (data_list) { + while (data_list->pString) { + count++; + data_list++; + } + } + return count; +} + +#ifdef TEST +#include +#include "ctest.h" + +static INDTEXT_DATA data_list[] = { + {1, "Joshua"}, + {2, "Mary"}, + {3, "Anna"}, + {4, "Christopher"}, + {5, "Patricia"}, + {0, NULL} +}; + +void testIndexText(Test * pTest) +{ + unsigned i; /*counter */ + const char *pString; + unsigned index; + bool valid; + unsigned count = 0; + + for (i = 0; i < 10; i++) { + pString = indtext_by_index(data_list, i); + if (pString) { + count++; + valid = indtext_by_string(data_list, pString, &index); + ct_test(pTest, valid == true); + ct_test(pTest, index == i); + ct_test(pTest, index == indtext_by_string_default(data_list, + pString, index)); + } + } + ct_test(pTest, indtext_count(data_list) == count); + ct_test(pTest, indtext_by_string(data_list, "Harry", NULL) == false); + ct_test(pTest, indtext_by_string(data_list, NULL, NULL) == false); + ct_test(pTest, indtext_by_string(NULL, NULL, NULL) == false); + ct_test(pTest, indtext_by_index(data_list, 0) == NULL); + ct_test(pTest, indtext_by_index(data_list, 10) == NULL); + ct_test(pTest, indtext_by_index(NULL, 10) == NULL); + /* case insensitive versions */ + ct_test(pTest, indtext_by_istring(data_list, "JOSHUA", NULL) == true); + ct_test(pTest, indtext_by_istring(data_list, "joshua", NULL) == true); + valid = indtext_by_istring(data_list, "ANNA", &index); + ct_test(pTest, index == indtext_by_istring_default(data_list, "ANNA", + index)); +} +#endif + +#ifdef TEST_INDEX_TEXT +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("index text", NULL); + + /* individual tests */ + rc = ct_addTestFunction(pTest, testIndexText); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_INDEX_TEXT */ diff --git a/bacnet-stack/indtext.h b/bacnet-stack/indtext.h index e69de29b..01f61f70 100644 --- a/bacnet-stack/indtext.h +++ b/bacnet-stack/indtext.h @@ -0,0 +1,95 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef INDTEXT_H +#define INDTEXT_H + +#include +#include +#include + +/* index and text pairs */ +typedef struct { + unsigned index; /* index number that matches the text */ + const char *pString; /* text pair - use NULL to end the list */ +} INDTEXT_DATA; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Searches for a matching string and returns the index to the string + in the parameter found_index. + If the string is not found, false is returned + If the string is found, true is returned and the found_index contains + the first index where the string was found. */ + bool indtext_by_string(INDTEXT_DATA * data_list, + const char *search_name, unsigned *found_index); +/* case insensitive version */ + bool indtext_by_istring(INDTEXT_DATA * data_list, + const char *search_name, unsigned *found_index); +/* Searches for a matching string and returns the index to the string + or the default_index if the string is not found. */ + unsigned indtext_by_string_default(INDTEXT_DATA * data_list, + const char *search_name, unsigned default_index); +/* case insensitive version */ + unsigned indtext_by_istring_default(INDTEXT_DATA * data_list, + const char *search_name, unsigned default_index); +/* for a given index, return the matching string, + or NULL if not found */ + const char *indtext_by_index(INDTEXT_DATA * data_list, unsigned index); +/* for a given index, return the matching string, + or default_name if not found */ + const char *indtext_by_index_default(INDTEXT_DATA * data_list, + unsigned index, const char *default_name); +/* for a given index, return the matching string, + or default_name if not found. + if the index is before the split, + the before_split_default_name is used */ + const char *indtext_by_index_split_default(INDTEXT_DATA * data_list, + int index, + int split_index, + const char *before_split_default_name, const char *default_name); + +/* returns the number of elements in the list */ + unsigned indtext_count(INDTEXT_DATA * data_list); + +#ifdef TEST +#include "ctest.h" + void testIndexText(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/key.c b/bacnet-stack/key.c index e69de29b..306eff5a 100644 --- a/bacnet-stack/key.c +++ b/bacnet-stack/key.c @@ -0,0 +1,116 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2003 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +//#define TEST +//#define TEST_KEY +#include "key.h" + +#ifdef TEST +#include +#include + +#include "ctest.h" + +// test the encode and decode macros +void testKeys(Test * pTest) +{ + int type, id; + int decoded_type, decoded_id; + KEY key; + + for (type = 0; type < KEY_TYPE_MAX; type++) { + for (id = 0; id < KEY_ID_MAX; id++) { + key = KEY_ENCODE(type, id); + decoded_type = KEY_DECODE_TYPE(key); + decoded_id = KEY_DECODE_ID(key); + ct_test(pTest, decoded_type == type); + ct_test(pTest, decoded_id == id); + } + } + + return; +} + +// test the encode and decode macros +void testKeySample(Test * pTest) +{ + int type, id; + int type_list[] = { 0, 1, KEY_TYPE_MAX / 2, KEY_TYPE_MAX - 1, -1 }; + int id_list[] = { 0, 1, KEY_ID_MAX / 2, KEY_ID_MAX - 1, -1 }; + int type_index = 0; + int id_index = 0; + int decoded_type, decoded_id; + KEY key; + + while (type_list[type_index] != -1) { + while (id_list[id_index] != -1) { + type = type_list[type_index]; + id = id_list[id_index]; + key = KEY_ENCODE(type, id); + decoded_type = KEY_DECODE_TYPE(key); + decoded_id = KEY_DECODE_ID(key); + ct_test(pTest, decoded_type == type); + ct_test(pTest, decoded_id == id); + + id_index++; + } + id_index = 0; + type_index++; + } + + return; +} + +#ifdef TEST_KEY +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("key", NULL); + /* add the individual tests */ +// rc = ct_addTestFunction(pTest, testKeys); +// assert(rc); + rc = ct_addTestFunction(pTest, testKeySample); + assert(rc); + /* run all the tests */ + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + /* completed testing - cleanup */ + ct_destroy(pTest); + + return 0; +} +#endif /* LOCAL_TEST */ +#endif diff --git a/bacnet-stack/key.h b/bacnet-stack/key.h index e69de29b..1bb66000 100644 --- a/bacnet-stack/key.h +++ b/bacnet-stack/key.h @@ -0,0 +1,60 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2003 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef KEY_H +#define KEY_H + +#include + +// This file has the macros that encode and decode the +// keys for the keylist when used with BACnet Object Id's +typedef uint32_t KEY; + +// assuming a 32 bit KEY +#define KEY_TYPE_OFFSET 22 /* bits - for BACnet */ +#define KEY_TYPE_MASK 0x000003FFL +#define KEY_ID_MASK 0x003FFFFFL +#define KEY_ID_MAX (KEY_ID_MASK + 1L) +#define KEY_TYPE_MAX (KEY_TYPE_MASK + 1L) +#define KEY_LAST(key) ((key & KEY_ID_MASK) == KEY_ID_MAX) + +#define KEY_ENCODE(type,id) ( ((unsigned int)\ +((unsigned int)(type) & KEY_TYPE_MASK) << KEY_TYPE_OFFSET) |\ + ((unsigned int)(id) & KEY_ID_MASK) ) + +#define KEY_DECODE_TYPE(key) ((int)(((unsigned int)(key) >> KEY_TYPE_OFFSET)\ + & KEY_TYPE_MASK)) + +#define KEY_DECODE_ID(key) ((int)((unsigned int)(key) & KEY_ID_MASK)) + +#endif diff --git a/bacnet-stack/keylist.c b/bacnet-stack/keylist.c index e69de29b..c34293d3 100644 --- a/bacnet-stack/keylist.c +++ b/bacnet-stack/keylist.c @@ -0,0 +1,716 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2003 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +// Keyed Linked List Library +// +// This is an enhanced array of pointers to data. +// The list is sorted, indexed, and keyed. +// The array is much faster than a linked list. +// It stores a pointer to data, which you must +// malloc and free on your own, or just use +// static data + +#include + +#include "keylist.h" // check for valid prototypes + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +///////////////////////////////////////////////////////////////////// +// Generic node routines +///////////////////////////////////////////////////////////////////// + +// grab memory for a node +static struct Keylist_Node *NodeCreate(void) +{ + return calloc(1, sizeof(struct Keylist_Node)); +} + +// grab memory for a list +static struct Keylist *KeylistCreate(void) +{ + return calloc(1, sizeof(struct Keylist)); +} + +// check to see if the array is big enough for an addition +// or is too big when we are deleting and we can shrink +// returns TRUE if success, FALSE if failed +static int CheckArraySize(OS_Keylist list) +{ + int new_size = 0; // set it up so that no size change is the default + const int chunk = 8; // minimum number of nodes to allocate memory for + struct Keylist_Node **new_array; // new array of nodes, if needed + int i; // counter + if (!list) + return FALSE; + + // indicates the need for more memory allocation + if (list->count == list->size) + new_size = list->size + chunk; + + // allow for shrinking memory + else if ((list->size > chunk) && (list->count < (list->size - chunk))) + new_size = list->size - chunk; + if (new_size) { + + // Allocate more room for node pointer array + new_array = calloc((size_t) new_size, sizeof(new_array)); + + // See if we got the memory we wanted + if (!new_array) + return FALSE; + + // copy the nodes from the old array to the new array + if (list->array) { + for (i = 0; i < list->count; i++) { + new_array[i] = list->array[i]; + } + free(list->array); + } + list->array = new_array; + list->size = new_size; + } + return TRUE; +} + + +// find the index of the key that we are looking for +// since it is sorted, we can optimize the search +// returns TRUE if found, and FALSE not found +// returns the found key and the index where it was found in parameters +// If the key is not found, the nearest index from the bottom will be returned, +// allowing the ability to find where an key should go into the list. +static int FindIndex(OS_Keylist list, KEY key, int *pIndex) +{ + struct Keylist_Node *node; // holds the new node + int left = 0; // the left branch of tree, beginning of list + int right = 0; // the right branch on the tree, end of list + int index = 0; // our current search place in the array + KEY current_key = 0; // place holder for current node key + int status = FALSE; // return value + if (!list || !list->array || !list->count) { + *pIndex = 0; + return (FALSE); + } + right = list->count - 1; + // assume that the list is sorted + do { + + // A binary search + index = (left + right) / 2; + node = list->array[index]; + if (!node) + break; + current_key = node->key; + if (key < current_key) + right = index - 1; + + else + left = index + 1; + } + while ((key != current_key) && (left <= right)); + if (key == current_key) { + status = TRUE; + *pIndex = index; + } + + else { + + // where the index should be + if (key > current_key) + *pIndex = index + 1; + + else + *pIndex = index; + } + return (status); +} + + +///////////////////////////////////////////////////////////////////// +// list data functions +///////////////////////////////////////////////////////////////////// +// inserts a node into its sorted position +int Keylist_Data_Add(OS_Keylist list, KEY key, void *data) +{ + struct Keylist_Node *node; // holds the new node + int index = -1; // return value + int i; // counts through the array + + if (list && CheckArraySize(list)) { + // figure out where to put the new node + if (list->count) { + (void) FindIndex(list, key, &index); + // Add to the beginning of the list + if (index < 0) + index = 0; + + // Add to the end of the list + else if (index > list->count) + index = list->count; + + // Move all the items up to make room for the new one + for (i = list->count; i > index; i--) { + list->array[i] = list->array[i - 1]; + } + } + + else { + index = 0; + } + + // create and add the node + node = NodeCreate(); + if (node) { + list->count++; + node->key = key; + node->data = data; + list->array[index] = node; + } + } + return index; +} + +// deletes a node specified by its index +// returns the data from the node +void *Keylist_Data_Delete_By_Index(OS_Keylist list, int index) +{ + struct Keylist_Node *node; + void *data = NULL; + + if (list && list->array && list->count && + (index >= 0) && (index < list->count)) { + node = list->array[index]; + if (node) + data = node->data; + + // move the nodes to account for the deleted one + if (list->count == 1) { + + // There is no node shifting to do + } + // We are the last one + else if (index == (list->count - 1)) { + + // There is no node shifting to do + } + // Move all the nodes down one + else { + int i; // counter + int count = list->count - 1; + for (i = index; i < count; i++) { + list->array[i] = list->array[i + 1]; + } + } + list->count--; + if (node) + free(node); + + // potentially reduce the size of the array + (void) CheckArraySize(list); + } + return (data); +} + + +// deletes a node specified by its key +// returns the data from the node +void *Keylist_Data_Delete(OS_Keylist list, KEY key) +{ + void *data = NULL; // return value + int index; // where the node is in the array + + if (list) { + if (FindIndex(list, key, &index)) + data = Keylist_Data_Delete_By_Index(list, index); + } + + return data; +} + +// returns the data from last node, and removes it from the list +void *Keylist_Data_Pop(OS_Keylist list) +{ + void *data = NULL; // return value + int index; // position in the array + + if (list && list->count) { + index = list->count - 1; + data = Keylist_Data_Delete_By_Index(list, index); + } + + return data; +} + +// returns the data from the node specified by key +void *Keylist_Data(OS_Keylist list, KEY key) +{ + struct Keylist_Node *node = NULL; + int index = 0; // used to look up the index of node + + if (list && list->array && list->count) { + if (FindIndex(list, key, &index)) + node = list->array[index]; + } + + return node ? node->data : NULL; +} + +// returns the data specified by key +void *Keylist_Data_Index(OS_Keylist list, int index) +{ + struct Keylist_Node *node = NULL; + + if (list && list->array && list->count && + (index >= 0) && (index < list->count)) + node = list->array[index]; + + return node ? node->data : NULL; +} + +// return the key at the given index +KEY Keylist_Key(OS_Keylist list, int index) +{ + KEY key = 0; // return value + struct Keylist_Node *node; + + if (list && list->array && list->count && + (index >= 0) && (index < list->count)) { + node = list->array[index]; + if (node) + key = node->key; + } + + return key; +} + +// returns the next empty key from the list +KEY Keylist_Next_Empty_Key(OS_Keylist list, KEY key) +{ + int index; + + if (list) { + while (FindIndex(list, key, &index)) { + if (KEY_LAST(key)) + break; + key++; + } + } + + return key; +} + +// return the number of nodes in this list +int Keylist_Count(OS_Keylist list) +{ + return list->count; +} + +///////////////////////////////////////////////////////////////////// +// Public List functions +///////////////////////////////////////////////////////////////////// + +// returns head of the list or NULL on failure. +OS_Keylist Keylist_Create(void) +{ + struct Keylist *list; + + list = KeylistCreate(); + if (list) + CheckArraySize(list); + + return list; +} + +// delete specified list +void Keylist_Delete(OS_Keylist list) // list number to be deleted +{ + if (list) { + // clean out the list + while (list->count) { + (void) Keylist_Data_Delete_By_Index(list, 0); + } + if (list->array) + free(list->array); + free(list); + } + + return; +} + +#ifdef TEST +#include +#include + +#include "ctest.h" + +// test the encode and decode macros +void testKeySample(Test * pTest) +{ + int type, id; + int type_list[] = + { 0, 1, KEY_TYPE_MAX / 2, KEY_TYPE_MAX - 2, KEY_TYPE_MAX - 1, -1 }; + int id_list[] = + { 0, 1, KEY_ID_MAX / 2, KEY_ID_MAX - 2, KEY_ID_MAX - 1, -1 }; + int type_index = 0; + int id_index = 0; + int decoded_type, decoded_id; + KEY key; + + while (type_list[type_index] != -1) { + while (id_list[id_index] != -1) { + type = type_list[type_index]; + id = id_list[id_index]; + key = KEY_ENCODE(type, id); + decoded_type = KEY_DECODE_TYPE(key); + decoded_id = KEY_DECODE_ID(key); + ct_test(pTest, decoded_type == type); + ct_test(pTest, decoded_id == id); + + id_index++; + } + id_index = 0; + type_index++; + } + + return; +} + +// test the FIFO +void testKeyListFIFO(Test * pTest) +{ + OS_Keylist list; + KEY key; + int index; + char *data1 = "Joshua"; + char *data2 = "Anna"; + char *data3 = "Mary"; + char *data; + + list = Keylist_Create(); + ct_test(pTest, list != NULL); + + key = 0; + index = Keylist_Data_Add(list, key, data1); + ct_test(pTest, index == 0); + index = Keylist_Data_Add(list, key, data2); + ct_test(pTest, index == 0); + index = Keylist_Data_Add(list, key, data3); + ct_test(pTest, index == 0); + + ct_test(pTest, Keylist_Count(list) == 3); + + data = Keylist_Data_Pop(list); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data1) == 0); + data = Keylist_Data_Pop(list); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data2) == 0); + data = Keylist_Data_Pop(list); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data3) == 0); + data = Keylist_Data_Pop(list); + ct_test(pTest, data == NULL); + data = Keylist_Data_Pop(list); + ct_test(pTest, data == NULL); + + Keylist_Delete(list); + + return; +} + +// test the FILO +void testKeyListFILO(Test * pTest) +{ + OS_Keylist list; + KEY key; + int index; + char *data1 = "Joshua"; + char *data2 = "Anna"; + char *data3 = "Mary"; + char *data; + + list = Keylist_Create(); + ct_test(pTest, list != NULL); + + key = 0; + index = Keylist_Data_Add(list, key, data1); + ct_test(pTest, index == 0); + index = Keylist_Data_Add(list, key, data2); + ct_test(pTest, index == 0); + index = Keylist_Data_Add(list, key, data3); + ct_test(pTest, index == 0); + + ct_test(pTest, Keylist_Count(list) == 3); + + data = Keylist_Data_Delete_By_Index(list, 0); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data3) == 0); + + data = Keylist_Data_Delete_By_Index(list, 0); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data2) == 0); + + data = Keylist_Data_Delete_By_Index(list, 0); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data1) == 0); + + data = Keylist_Data_Delete_By_Index(list, 0); + ct_test(pTest, data == NULL); + + data = Keylist_Data_Delete_By_Index(list, 0); + ct_test(pTest, data == NULL); + + Keylist_Delete(list); + + return; +} + +void testKeyListDataKey(Test * pTest) +{ + OS_Keylist list; + KEY key; + KEY test_key; + int index; + char *data1 = "Joshua"; + char *data2 = "Anna"; + char *data3 = "Mary"; + char *data; + + list = Keylist_Create(); + ct_test(pTest, list != NULL); + + key = 1; + index = Keylist_Data_Add(list, key, data1); + ct_test(pTest, index == 0); + test_key = Keylist_Key(list, index); + ct_test(pTest, test_key == key); + + key = 2; + index = Keylist_Data_Add(list, key, data2); + ct_test(pTest, index == 1); + test_key = Keylist_Key(list, index); + ct_test(pTest, test_key == key); + + key = 3; + index = Keylist_Data_Add(list, key, data3); + ct_test(pTest, index == 2); + test_key = Keylist_Key(list, index); + ct_test(pTest, test_key == key); + + ct_test(pTest, Keylist_Count(list) == 3); + + // look at the data + key = 2; + data = Keylist_Data(list, key); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data2) == 0); + + key = 1; + data = Keylist_Data(list, key); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data1) == 0); + + key = 3; + data = Keylist_Data(list, key); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data3) == 0); + + // work the data + key = 2; + data = Keylist_Data_Delete(list, key); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data2) == 0); + data = Keylist_Data_Delete(list, key); + ct_test(pTest, data == NULL); + ct_test(pTest, Keylist_Count(list) == 2); + + key = 1; + data = Keylist_Data(list, key); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data1) == 0); + + key = 3; + data = Keylist_Data(list, key); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data3) == 0); + + // cleanup + do { + data = Keylist_Data_Pop(list); + } + while (data); + + Keylist_Delete(list); + + return; +} + +void testKeyListDataIndex(Test * pTest) +{ + OS_Keylist list; + KEY key; + int index; + char *data1 = "Joshua"; + char *data2 = "Anna"; + char *data3 = "Mary"; + char *data; + + list = Keylist_Create(); + ct_test(pTest, list != NULL); + + key = 0; + index = Keylist_Data_Add(list, key, data1); + ct_test(pTest, index == 0); + index = Keylist_Data_Add(list, key, data2); + ct_test(pTest, index == 0); + index = Keylist_Data_Add(list, key, data3); + ct_test(pTest, index == 0); + + + ct_test(pTest, Keylist_Count(list) == 3); + + // look at the data + data = Keylist_Data_Index(list, 0); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data3) == 0); + + data = Keylist_Data_Index(list, 1); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data2) == 0); + + data = Keylist_Data_Index(list, 2); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data1) == 0); + + // work the data + data = Keylist_Data_Delete_By_Index(list, 1); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data2) == 0); + + ct_test(pTest, Keylist_Count(list) == 2); + + data = Keylist_Data_Index(list, 0); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data3) == 0); + + data = Keylist_Data_Index(list, 1); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data1) == 0); + + data = Keylist_Data_Delete_By_Index(list, 1); + ct_test(pTest, data != NULL); + ct_test(pTest, strcmp(data, data1) == 0); + + data = Keylist_Data_Delete_By_Index(list, 1); + ct_test(pTest, data == NULL); + + // cleanup + do { + data = Keylist_Data_Pop(list); + } + while (data); + + Keylist_Delete(list); + + return; +} + +// test access of a lot of entries +void testKeyListLarge(Test * pTest) +{ + int data1 = 42; + int *data; + OS_Keylist list; + KEY key; + int index; + const unsigned num_keys = 1024 * 16; + + list = Keylist_Create(); + if (!list) + return; + + for (key = 0; key < num_keys; key++) { + index = Keylist_Data_Add(list, key, &data1); + + } + for (key = 0; key < num_keys; key++) { + data = Keylist_Data(list, key); + ct_test(pTest, *data == data1); + } + for (index = 0; index < num_keys; index++) { + data = Keylist_Data_Index(list, index); + ct_test(pTest, *data == data1); + } + Keylist_Delete(list); + + return; +} + +#ifdef TEST_KEYLIST +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("keylist", NULL); + + /* individual tests */ + rc = ct_addTestFunction(pTest, testKeyListFIFO); + assert(rc); + rc = ct_addTestFunction(pTest, testKeyListFILO); + assert(rc); + rc = ct_addTestFunction(pTest, testKeyListDataKey); + assert(rc); + rc = ct_addTestFunction(pTest, testKeySample); + assert(rc); + rc = ct_addTestFunction(pTest, testKeyListDataIndex); + assert(rc); + rc = ct_addTestFunction(pTest, testKeyListLarge); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_KEYLIST */ +#endif /* TEST */ diff --git a/bacnet-stack/keylist.h b/bacnet-stack/keylist.h index e69de29b..9faa99d1 100644 --- a/bacnet-stack/keylist.h +++ b/bacnet-stack/keylist.h @@ -0,0 +1,93 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2003 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef KEYLIST_H +#define KEYLIST_H + +#include "key.h" + +// This is a key sorted linked list data library that +// uses a key or index to access the data. +// If the keys are duplicated, they can be added into the list like FIFO + +// list data and datatype +struct Keylist_Node { + KEY key; // unique number that is sorted in the list + void *data; // pointer to some data that is stored +}; + +typedef struct Keylist { + struct Keylist_Node **array; // array of nodes + int count; // number of nodes in this list - more effecient than loop + int size; // number of available nodes on this list - can grow or shrink +} KEYLIST_TYPE; +typedef KEYLIST_TYPE *OS_Keylist; + +// returns head of the list or NULL on failure. +OS_Keylist Keylist_Create(void); + +// delete specified list +// note: you should pop all the nodes off the list first. +void Keylist_Delete(OS_Keylist list); + +// inserts a node into its sorted position +// returns the index where it was added +int Keylist_Data_Add(OS_Keylist list, KEY key, void *data); + +// deletes a node specified by its key +// returns the data from the node +void *Keylist_Data_Delete(OS_Keylist list, KEY key); + +// deletes a node specified by its index +// returns the data from the node +void *Keylist_Data_Delete_By_Index(OS_Keylist list, int index); + +// returns the data from last node, and removes it from the list +void *Keylist_Data_Pop(OS_Keylist list); + +// returns the data from the node specified by key +void *Keylist_Data(OS_Keylist list, KEY key); + +// returns the data specified by key +void *Keylist_Data_Index(OS_Keylist list, int index); + +// return the key at the given index +KEY Keylist_Key(OS_Keylist list, int index); + +// returns the next empty key from the list +KEY Keylist_Next_Empty_Key(OS_Keylist list, KEY key); + +// returns the number of items in the list +int Keylist_Count(OS_Keylist list); + +#endif diff --git a/bacnet-stack/npdu.c b/bacnet-stack/npdu.c index e69de29b..03a37f61 100644 --- a/bacnet-stack/npdu.c +++ b/bacnet-stack/npdu.c @@ -0,0 +1,506 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bits.h" +#include "npdu.h" +#include "apdu.h" + +void npdu_copy_data(BACNET_NPDU_DATA * dest, BACNET_NPDU_DATA * src) +{ + if (dest && src) { + dest->protocol_version = src->protocol_version; + dest->data_expecting_reply = src->data_expecting_reply; + dest->network_layer_message = src->network_layer_message; + dest->priority = src->priority; + dest->network_message_type = src->network_message_type; + dest->vendor_id = src->vendor_id; + dest->hop_count = src->hop_count; + } + + return; +} + +/* + +The following ICI parameters are exchanged with the +various service primitives across an API: + +'destination_address' (DA): the address of the device(s) +intended to receive the service primitive. Its format (device name, +network address, etc.) is a local matter. This address +may also be a multicast, local broadcast or global broadcast type. + +'source_address' (SA): the address of the device from which +the service primitive was received. Its format (device name, +network address, etc.) is a local matter. + +'network_priority' (NP): a four-level network priority parameter +described in 6.2.2. + +'data_expecting_reply' (DER): a Boolean parameter that indicates +whether (TRUE) or not (FALSE) a reply service primitive +is expected for the service being issued. + + +Table 5-1. Applicability of ICI parameters for abstract service primitives + Service Primitive DA SA NP DER +CONF_SERV.request Yes No Yes Yes +CONF_SERV.indication Yes Yes Yes Yes +CONF_SERV.response Yes No Yes Yes +CONF_SERV.confirm Yes Yes Yes No +UNCONF_SERV.request Yes No Yes No +UNCONF_SERV.indication Yes Yes Yes No +REJECT.request Yes No Yes No +REJECT.indication Yes Yes Yes No +SEGMENT_ACK.request Yes No Yes No +SEGMENT_ACK.indication Yes Yes Yes No +ABORT.request Yes No Yes No +ABORT.indication Yes Yes Yes No +*/ + +int npdu_encode_pdu(uint8_t * npdu, + BACNET_ADDRESS * dest, + BACNET_ADDRESS * src, BACNET_NPDU_DATA * npdu_data) +{ + int len = 0; /* return value - number of octets loaded in this function */ + int i = 0; /* counter */ + + + if (npdu && npdu_data) { + /* protocol version */ + npdu[0] = npdu_data->protocol_version; + /* initialize the control octet */ + npdu[1] = 0; + /* Bit 7: 1 indicates that the NSDU conveys a network layer message. */ + /* Message Type field is present. */ + /* 0 indicates that the NSDU contains a BACnet APDU. */ + /* Message Type field is absent. */ + if (npdu_data->network_layer_message) + npdu[1] |= BIT7; + /*Bit 6: Reserved. Shall be zero. */ + /*Bit 5: Destination specifier where: */ + /* 0 = DNET, DLEN, DADR, and Hop Count absent */ + /* 1 = DNET, DLEN, and Hop Count present */ + /* DLEN = 0 denotes broadcast MAC DADR and DADR field is absent */ + /* DLEN > 0 specifies length of DADR field */ + if (dest && dest->net) + npdu[1] |= BIT5; + /* Bit 4: Reserved. Shall be zero. */ + /* Bit 3: Source specifier where: */ + /* 0 = SNET, SLEN, and SADR absent */ + /* 1 = SNET, SLEN, and SADR present */ + /* SLEN = 0 Invalid */ + /* SLEN > 0 specifies length of SADR field */ + if (src && src->net) + npdu[1] |= BIT3; + /* Bit 2: The value of this bit corresponds to the */ + /* data_expecting_reply parameter in the N-UNITDATA primitives. */ + /* 1 indicates that a BACnet-Confirmed-Request-PDU, */ + /* a segment of a BACnet-ComplexACK-PDU, */ + /* or a network layer message expecting a reply is present. */ + /* 0 indicates that other than a BACnet-Confirmed-Request-PDU, */ + /* a segment of a BACnet-ComplexACK-PDU, */ + /* or a network layer message expecting a reply is present. */ + if (npdu_data->data_expecting_reply) + npdu[1] |= BIT2; + /* Bits 1,0: Network priority where: */ + /* B'11' = Life Safety message */ + /* B'10' = Critical Equipment message */ + /* B'01' = Urgent message */ + /* B'00' = Normal message */ + npdu[1] |= (npdu_data->priority & 0x03); + len = 2; + if (dest && dest->net) { + len += encode_unsigned16(&npdu[len], dest->net); + npdu[len++] = dest->len; + /* DLEN = 0 denotes broadcast MAC DADR and DADR field is absent */ + /* DLEN > 0 specifies length of DADR field */ + if (dest->len) { + for (i = 0; i < dest->len; i++) { + npdu[len++] = dest->adr[i]; + } + } + } + if (src && src->net) { + len += encode_unsigned16(&npdu[len], src->net); + npdu[len++] = src->len; + /* SLEN = 0 denotes broadcast MAC SADR and SADR field is absent */ + /* SLEN > 0 specifies length of SADR field */ + if (src->len) { + for (i = 0; i < src->len; i++) { + npdu[len++] = src->adr[i]; + } + } + } + /* The Hop Count field shall be present only if the message is */ + /* destined for a remote network, i.e., if DNET is present. */ + /* This is a one-octet field that is initialized to a value of 0xff. */ + if (dest && dest->net) { + npdu[len] = 0xFF; + len++; + } + if (npdu_data->network_layer_message) { + npdu[len] = npdu_data->network_message_type; + len++; + /* Message Type field contains a value in the range 0x80 - 0xFF, */ + /* then a Vendor ID field shall be present */ + if (npdu_data->network_message_type >= 0x80) + len += encode_unsigned16(&npdu[len], npdu_data->vendor_id); + } + } + + return len; +} + +/* Configure the NPDU portion of the packet for an APDU */ +/* This function does not handle the network messages, just APDUs. */ +void npdu_encode_npdu_data(BACNET_NPDU_DATA * npdu_data, + bool data_expecting_reply, BACNET_MESSAGE_PRIORITY priority) +{ + if (npdu_data) { + npdu_data->data_expecting_reply = data_expecting_reply; + npdu_data->protocol_version = BACNET_PROTOCOL_VERSION; + npdu_data->network_layer_message = false; /* false if APDU */ + npdu_data->network_message_type = 0; /* optional */ + npdu_data->vendor_id = 0; /* optional, if net message type is > 0x80 */ + npdu_data->priority = priority; + npdu_data->hop_count = 0; + } +} + +int npdu_decode(uint8_t * npdu, + BACNET_ADDRESS * dest, + BACNET_ADDRESS * src, BACNET_NPDU_DATA * npdu_data) +{ + int len = 0; /* return value - number of octets loaded in this function */ + int i = 0; /* counter */ + uint16_t src_net = 0; + uint16_t dest_net = 0; + uint8_t address_len = 0; + uint8_t mac_octet = 0; + + if (npdu && npdu_data) { + /* Protocol Version */ + npdu_data->protocol_version = npdu[0]; + /* control octet */ + /* Bit 7: 1 indicates that the NSDU conveys a network layer message. */ + /* Message Type field is present. */ + /* 0 indicates that the NSDU contains a BACnet APDU. */ + /* Message Type field is absent. */ + npdu_data->network_layer_message = (npdu[1] & BIT7) ? true : false; + /*Bit 6: Reserved. Shall be zero. */ + /* Bit 4: Reserved. Shall be zero. */ + /* Bit 2: The value of this bit corresponds to data expecting reply */ + /* parameter in the N-UNITDATA primitives. */ + /* 1 indicates that a BACnet-Confirmed-Request-PDU, */ + /* a segment of a BACnet-ComplexACK-PDU, */ + /* or a network layer message expecting a reply is present. */ + /* 0 indicates that other than a BACnet-Confirmed-Request-PDU, */ + /* a segment of a BACnet-ComplexACK-PDU, */ + /* or a network layer message expecting a reply is present. */ + npdu_data->data_expecting_reply = (npdu[1] & BIT2) ? true : false; + /* Bits 1,0: Network priority where: */ + /* B'11' = Life Safety message */ + /* B'10' = Critical Equipment message */ + /* B'01' = Urgent message */ + /* B'00' = Normal message */ + npdu_data->priority = npdu[1] & 0x03; + /* set the offset to where the optional stuff starts */ + len = 2; + /*Bit 5: Destination specifier where: */ + /* 0 = DNET, DLEN, DADR, and Hop Count absent */ + /* 1 = DNET, DLEN, and Hop Count present */ + /* DLEN = 0 denotes broadcast MAC DADR and DADR field is absent */ + /* DLEN > 0 specifies length of DADR field */ + if (npdu[1] & BIT5) { + len += decode_unsigned16(&npdu[len], &dest_net); + /* DLEN = 0 denotes broadcast MAC DADR and DADR field is absent */ + /* DLEN > 0 specifies length of DADR field */ + address_len = npdu[len++]; + if (dest) { + dest->net = dest_net; + dest->len = address_len; + } + if (address_len) { + for (i = 0; i < address_len; i++) { + mac_octet = npdu[len++]; + if (dest) + dest->adr[i] = mac_octet; + } + } + } + /* zero out the destination address */ + else if (dest) { + dest->net = 0; + dest->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = 0; + } + } + /* Bit 3: Source specifier where: */ + /* 0 = SNET, SLEN, and SADR absent */ + /* 1 = SNET, SLEN, and SADR present */ + /* SLEN = 0 Invalid */ + /* SLEN > 0 specifies length of SADR field */ + if (npdu[1] & BIT3) { + len += decode_unsigned16(&npdu[len], &src_net); + /* SLEN = 0 denotes broadcast MAC SADR and SADR field is absent */ + /* SLEN > 0 specifies length of SADR field */ + address_len = npdu[len++]; + if (src) { + src->net = src_net; + src->len = address_len; + } + if (address_len) { + for (i = 0; i < address_len; i++) { + mac_octet = npdu[len++]; + if (src) + src->adr[i] = mac_octet; + } + } + } else if (src) { + src->net = 0; + src->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + src->adr[i] = 0; + } + } + /* The Hop Count field shall be present only if the message is */ + /* destined for a remote network, i.e., if DNET is present. */ + /* This is a one-octet field that is initialized to a value of 0xff. */ + if (dest_net) + npdu_data->hop_count = npdu[len++]; + else + npdu_data->hop_count = 0; + /* Indicates that the NSDU conveys a network layer message. */ + /* Message Type field is present. */ + if (npdu_data->network_layer_message) { + npdu_data->network_message_type = npdu[len++]; + /* Message Type field contains a value in the range 0x80 - 0xFF, */ + /* then a Vendor ID field shall be present */ + if (npdu_data->network_message_type >= 0x80) + len += + decode_unsigned16(&npdu[len], &npdu_data->vendor_id); + } else + npdu_data->network_message_type = 0; + } + + return len; +} + +void npdu_handler(BACNET_ADDRESS * src, /* source address */ + uint8_t * pdu, /* PDU data */ + uint16_t pdu_len) +{ /* length PDU */ + int apdu_offset = 0; + BACNET_ADDRESS dest = { 0 }; + BACNET_NPDU_DATA npdu_data = { 0 }; + + apdu_offset = npdu_decode(&pdu[0], &dest, src, &npdu_data); + if (npdu_data.network_layer_message) { + /*FIXME: network layer message received! Handle it! */ + } else if ((apdu_offset > 0) && (apdu_offset <= pdu_len)) { + /* only handle the version that we know how to handle */ + if (npdu_data.protocol_version == BACNET_PROTOCOL_VERSION) + apdu_handler(src, &pdu[apdu_offset], + (uint16_t) (pdu_len - apdu_offset)); + } + + return; +} + + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testNPDU2(Test * pTest) +{ + uint8_t pdu[480] = { 0 }; + BACNET_ADDRESS dest = { 0 }; + BACNET_ADDRESS src = { 0 }; + BACNET_ADDRESS npdu_dest = { 0 }; + BACNET_ADDRESS npdu_src = { 0 }; + int len = 0; + bool data_expecting_reply = true; + BACNET_MESSAGE_PRIORITY priority = MESSAGE_PRIORITY_NORMAL; + BACNET_NPDU_DATA npdu_data = { 0 }; + int i = 0; /* counter */ + int npdu_len = 0; + bool network_layer_message = false; /* false if APDU */ + BACNET_NETWORK_MESSAGE_TYPE network_message_type = 0; /* optional */ + uint16_t vendor_id = 0; /* optional, if net message type is > 0x80 */ + + /* mac_len = 0 if global address */ + dest.mac_len = 6; + for (i = 0; i < dest.mac_len; i++) { + dest.mac[i] = i; + } + /* DNET,DLEN,DADR */ + dest.net = 1; + dest.len = 6; + for (i = 0; i < dest.len; i++) { + dest.adr[i] = i * 10; + } + src.mac_len = 1; + for (i = 0; i < src.mac_len; i++) { + src.mac[i] = 0x80; + } + /* SNET,SLEN,SADR */ + src.net = 2; + src.len = 1; + for (i = 0; i < src.len; i++) { + src.adr[i] = 0x40; + } + npdu_encode_npdu_data(&npdu_data, true, priority); + len = npdu_encode_pdu(&pdu[0], &dest, &src, &npdu_data); + ct_test(pTest, len != 0); + /* can we get the info back? */ + npdu_len = npdu_decode(&pdu[0], &npdu_dest, &npdu_src, &npdu_data); + ct_test(pTest, npdu_len != 0); + ct_test(pTest, npdu_data.data_expecting_reply == data_expecting_reply); + ct_test(pTest, + npdu_data.network_layer_message == network_layer_message); + ct_test(pTest, npdu_data.network_message_type == network_message_type); + ct_test(pTest, npdu_data.vendor_id == vendor_id); + ct_test(pTest, npdu_data.priority == priority); + /* DNET,DLEN,DADR */ + ct_test(pTest, npdu_dest.net == dest.net); + ct_test(pTest, npdu_dest.len == dest.len); + for (i = 0; i < dest.len; i++) { + ct_test(pTest, npdu_dest.adr[i] == dest.adr[i]); + } + /* SNET,SLEN,SADR */ + ct_test(pTest, npdu_src.net == src.net); + ct_test(pTest, npdu_src.len == src.len); + for (i = 0; i < src.len; i++) { + ct_test(pTest, npdu_src.adr[i] == src.adr[i]); + } +} + +void testNPDU1(Test * pTest) +{ + uint8_t pdu[480] = { 0 }; + BACNET_ADDRESS dest = { 0 }; + BACNET_ADDRESS src = { 0 }; + BACNET_ADDRESS npdu_dest = { 0 }; + BACNET_ADDRESS npdu_src = { 0 }; + int len = 0; + bool data_expecting_reply = false; + BACNET_MESSAGE_PRIORITY priority = MESSAGE_PRIORITY_NORMAL; + BACNET_NPDU_DATA npdu_data = { 0 }; + int i = 0; /* counter */ + int npdu_len = 0; + bool network_layer_message = false; /* false if APDU */ + BACNET_NETWORK_MESSAGE_TYPE network_message_type = 0; /* optional */ + uint16_t vendor_id = 0; /* optional, if net message type is > 0x80 */ + + /* mac_len = 0 if global address */ + dest.mac_len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + dest.mac[i] = 0; + } + /* DNET,DLEN,DADR */ + dest.net = 0; + dest.len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + dest.adr[i] = 0; + } + src.mac_len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + src.mac[i] = 0; + } + /* SNET,SLEN,SADR */ + src.net = 0; + src.len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + src.adr[i] = 0; + } + npdu_encode_npdu_data(&npdu_data, false, priority); + len = npdu_encode_pdu(&pdu[0], &dest, &src, &npdu_data); + ct_test(pTest, len != 0); + /* can we get the info back? */ + npdu_len = npdu_decode(&pdu[0], &npdu_dest, &npdu_src, &npdu_data); + ct_test(pTest, npdu_len != 0); + ct_test(pTest, npdu_data.data_expecting_reply == data_expecting_reply); + ct_test(pTest, + npdu_data.network_layer_message == network_layer_message); + ct_test(pTest, npdu_data.network_message_type == network_message_type); + ct_test(pTest, npdu_data.vendor_id == vendor_id); + ct_test(pTest, npdu_data.priority == priority); + ct_test(pTest, npdu_dest.mac_len == src.mac_len); + ct_test(pTest, npdu_src.mac_len == dest.mac_len); +} + +#ifdef TEST_NPDU +/* dummy stub for testing */ +void tsm_free_invoke_id(uint8_t invokeID) +{ + (void) invokeID; +} + +void iam_handler(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src) +{ + (void) service_request; + (void) service_len; + (void) src; +} + +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet NPDU", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testNPDU1); + assert(rc); + rc = ct_addTestFunction(pTest, testNPDU2); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_NPDU */ +#endif /* TEST */ diff --git a/bacnet-stack/npdu.h b/bacnet-stack/npdu.h index e69de29b..2ae54606 100644 --- a/bacnet-stack/npdu.h +++ b/bacnet-stack/npdu.h @@ -0,0 +1,81 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef NPDU_H +#define NPDU_H + +#include +#include +#include "bacdef.h" +#include "bacenum.h" + +/* an NPDU structure keeps the parameter stack to a minimum */ +typedef struct bacnet_npdu_data_t { + uint8_t protocol_version; + /* parts of the control octet: */ + bool data_expecting_reply; + bool network_layer_message; /* false if APDU */ + BACNET_MESSAGE_PRIORITY priority; + /* optional network message info */ + BACNET_NETWORK_MESSAGE_TYPE network_message_type; /* optional */ + uint16_t vendor_id; /* optional, if net message type is > 0x80 */ + uint8_t hop_count; +} BACNET_NPDU_DATA; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + uint8_t npdu_encode_max_seg_max_apdu(int max_segs, int max_apdu); + + int npdu_encode_pdu(uint8_t * npdu, + BACNET_ADDRESS * dest, + BACNET_ADDRESS * src, BACNET_NPDU_DATA * npdu_data); + + void npdu_encode_npdu_data(BACNET_NPDU_DATA * npdu, + bool data_expecting_reply, BACNET_MESSAGE_PRIORITY priority); + + void npdu_copy_data(BACNET_NPDU_DATA * dest, BACNET_NPDU_DATA * src); + + int npdu_decode(uint8_t * npdu, + BACNET_ADDRESS * dest, + BACNET_ADDRESS * src, BACNET_NPDU_DATA * npdu_data); + + void npdu_handler(BACNET_ADDRESS * src, /* source address */ + uint8_t * pdu, /* PDU data */ + uint16_t pdu_len); /* length PDU */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/ports/linux/arcnet.c b/bacnet-stack/ports/linux/arcnet.c index e69de29b..99691583 100644 --- a/bacnet-stack/ports/linux/arcnet.c +++ b/bacnet-stack/ports/linux/arcnet.c @@ -0,0 +1,380 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#include +#include "bacdef.h" +#include "npdu.h" +#include "arcnet.h" +#include "net.h" + +/* my local device data - MAC address */ +uint8_t ARCNET_MAC_Address = 0; +/* ARCNET file handle */ +static int ARCNET_Sock_FD = -1; +/* ARCNET socket address (has the interface name) */ +static struct sockaddr ARCNET_Socket_Address; +/* Broadcast address */ +#define ARCNET_BROADCAST 0 + +/* +Hints: + +When using a PCI20-485D ARCNET card from Contemporary Controls, +you might need to know about the following settings: + +Assuming a 20MHz clock on the COM20020 chip: + +clockp Clock Prescaler DataRate +------ --------------- -------- +0 8 2.5 Mbps +1 16 1.25 Mbps +2 32 625 Kbps +3 64 312.5 Kbps +4 128 156.25Kbps + +1. Install the arcnet driver and arcnet raw mode driver: +# modprobe com20020_pci clockp=4 +# modprobe arc_rawmode + +2. Use ifconfig to bring up the interface +# ifconfig arc0 up + +3. The hardware address (MAC address) is set using the dipswitch + on the back of the card. 0 is broadcast, so don't use 0. + +4. The backplane mode on the PCI20-485D card is done in hardware, + so the driver does not need to do backplane mode. If you + use another type of PCI20 card, you could pass in backplane=1 or + backplane=0 as an option to the modprobe of com20020_pci. + +*/ + +bool arcnet_valid(void) +{ + return (ARCNET_Sock_FD >= 0); +} + +void arcnet_cleanup(void) +{ + if (arcnet_valid()) + close(ARCNET_Sock_FD); + ARCNET_Sock_FD = -1; + + return; +} + +static int arcnet_bind(char *interface_name) +{ + int sock_fd = -1; /* return value */ + struct ifreq ifr; + int rv; /* return value - error value from df or ioctl call */ + int uid = 0; + + /* check to see if we are being run as root */ + uid = getuid(); + if (uid != 0) { + fprintf(stderr, + "arcnet: Unable to open an af_packet socket. " + "Try running with root priveleges.\n"); + return sock_fd; + } + fprintf(stderr, "arcnet: opening \"%s\"\n", interface_name); + /* note: on some systems you may have to add or enable in */ + /* modules.conf (or in modutils/alias on Debian with update-modules) */ + /* alias net-pf-17 af_packet */ + /* Then follow it by: # modprobe af_packet */ + if ((sock_fd = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ALL))) < 0) { + /* Error occured */ + fprintf(stderr, + "arcnet: Error opening socket: %s\n", strerror(errno)); + fprintf(stderr, + "You might need to add the following to modules.conf\n" + "(or in /etc/modutils/alias on Debian with update-modules):\n" + "alias net-pf-17 af_packet\n" + "Also, add af_packet to /etc/modules.\n" + "Then follow it by:\n" "# modprobe af_packet\n"); + exit(-1); + } + + if (ARCNET_Sock_FD >= 0) { + /* Bind the socket to an interface name so we only get packets from it */ + ARCNET_Socket_Address.sa_family = ARPHRD_ARCNET; + /*ARCNET_Socket_Address.sa_family = PF_INET; */ + /* Clear the memory before copying */ + memset(ARCNET_Socket_Address.sa_data, '\0', + sizeof(ARCNET_Socket_Address.sa_data)); + /* Strcpy the interface name into the address */ + strncpy(ARCNET_Socket_Address.sa_data, interface_name, + sizeof(ARCNET_Socket_Address.sa_data) - 1); + fprintf(stderr, "arcnet: binding \"%s\"\n", + ARCNET_Socket_Address.sa_data); + if (bind(sock_fd, &ARCNET_Socket_Address, + sizeof(ARCNET_Socket_Address)) != 0) { + /* Bind problem, close socket and return */ + fprintf(stderr, + "arcnet: Unable to bind socket : %s\n", strerror(errno)); + fprintf(stderr, + "You might need to add the following to modules.conf\n" + "(or in /etc/modutils/alias on Debian with update-modules):\n" + "alias net-pf-17 af_packet\n" + "Also, add af_packet to /etc/modules.\n" + "Then follow it by:\n" "# modprobe af_packet\n"); + /* Close the socket */ + close(sock_fd); + exit(-1); + } + } + strncpy(ifr.ifr_name, interface_name, sizeof(ifr.ifr_name)); + rv = ioctl(sock_fd, SIOCGIFHWADDR, &ifr); + if (rv != -1) /* worked okay */ + ARCNET_MAC_Address = ifr.ifr_hwaddr.sa_data[0]; + /* copy this info into the local copy since bind wiped it out */ + ARCNET_Socket_Address.sa_family = ARPHRD_ARCNET; + /*ARCNET_Socket_Address.sa_family = PF_INET; */ + /* Clear the memory before copying */ + memset(ARCNET_Socket_Address.sa_data, '\0', + sizeof(ARCNET_Socket_Address.sa_data)); + /* Strcpy the interface name into the address */ + strncpy(ARCNET_Socket_Address.sa_data, interface_name, + sizeof(ARCNET_Socket_Address.sa_data) - 1); + fprintf(stderr, "arcnet: MAC=%02Xh iface=\"%s\"\n", + ARCNET_MAC_Address, ARCNET_Socket_Address.sa_data); + + atexit(arcnet_cleanup); + + return sock_fd; +} + +bool arcnet_init(char *interface_name) +{ + if (interface_name) + ARCNET_Sock_FD = arcnet_bind(interface_name); + else + ARCNET_Sock_FD = arcnet_bind("arc0"); + + return arcnet_valid(); +} + +/* function to send a PDU out the socket */ +/* returns number of bytes sent on success, negative on failure */ +int arcnet_send_pdu(BACNET_ADDRESS * dest, /* destination address */ + BACNET_NPDU_DATA * npdu_data, /* network information */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len) +{ /* number of bytes of data */ + BACNET_ADDRESS src = { 0 }; /* source address */ + int bytes = 0; + uint8_t mtu[512] = { 0 }; + int mtu_len = 0; + struct archdr *pkt = (struct archdr *) mtu; + + (void) npdu_data; + src.mac[0] = ARCNET_MAC_Address; + src.mac_len = 1; + + /* don't waste time if the socket is not valid */ + if (ARCNET_Sock_FD < 0) { + fprintf(stderr, "arcnet: socket is invalid!\n"); + return -1; + } + /* load destination MAC address */ + if (dest->mac_len == 1) + pkt->hard.dest = dest->mac[0]; + else { + fprintf(stderr, "arcnet: invalid destination MAC address!\n"); + return -2; + } + if (src.mac_len == 1) + pkt->hard.source = src.mac[0]; + else { + fprintf(stderr, "arcnet: invalid source MAC address!\n"); + return -3; + } + /* Logical PDU portion */ + pkt->soft.raw[0] = 0xCD; /* SC for BACnet */ + pkt->soft.raw[1] = 0x82; /* DSAP for BACnet */ + pkt->soft.raw[2] = 0x82; /* SSAP for BACnet */ + pkt->soft.raw[3] = 0x03; /* LLC Control byte in header */ + /* packet length */ + mtu_len = ARC_HDR_SIZE + 4 /*SC,DSAP,SSAP,LLC */ + pdu_len; + if (mtu_len > 512) { + fprintf(stderr, "arcnet: PDU is too big to send!\n"); + return -4; + } + memcpy(&pkt->soft.raw[4], pdu, pdu_len); + /* Send the packet */ + bytes = + sendto(ARCNET_Sock_FD, &mtu, mtu_len, 0, + (struct sockaddr *) &ARCNET_Socket_Address, + sizeof(ARCNET_Socket_Address)); + /* did it get sent? */ + if (bytes < 0) + fprintf(stderr, "arcnet: Error sending packet: %s\n", + strerror(errno)); + + return bytes; +} + +/* receives an framed packet */ +/* returns the number of octets in the PDU, or zero on failure */ +uint16_t arcnet_receive(BACNET_ADDRESS * src, /* source address */ + uint8_t * pdu, /* PDU data */ + uint16_t max_pdu, /* amount of space available in the PDU */ + unsigned timeout) +{ /* milliseconds to wait for a packet */ + int received_bytes; + uint8_t buf[512] = { 0 }; /* data */ + uint16_t pdu_len = 0; /* return value */ + fd_set read_fds; + int max; + struct timeval select_timeout; + struct archdr *pkt = (struct archdr *) buf; + + /* Make sure the socket is open */ + if (ARCNET_Sock_FD <= 0) + return 0; + + /* we could just use a non-blocking socket, but that consumes all + the CPU time. We can use a timeout; it is only supported as + a select. */ + if (timeout >= 1000) { + select_timeout.tv_sec = timeout / 1000; + select_timeout.tv_usec = + 1000 * (timeout - select_timeout.tv_sec * 1000); + } else { + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 1000 * timeout; + } + FD_ZERO(&read_fds); + FD_SET(ARCNET_Sock_FD, &read_fds); + max = ARCNET_Sock_FD; + + if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) + received_bytes = read(ARCNET_Sock_FD, &buf[0], sizeof(buf)); + else + return 0; + + /* See if there is a problem */ + if (received_bytes < 0) { + /* EAGAIN Non-blocking I/O has been selected */ + /* using O_NONBLOCK and no data */ + /* was immediately available for reading. */ + if (errno != EAGAIN) + fprintf(stderr, + "ethernet: Read error in receiving packet: %s\n", + strerror(errno)); + return 0; + } + + if (received_bytes == 0) + return 0; + + /* printf("arcnet: received %u bytes (offset=%02Xh %02Xh) " + "from %02Xh (proto==%02Xh)\n", + received_bytes, pkt->offset[0], pkt->offset[1], + pkt->hard.source, pkt->soft.raw[0]); + */ + + if (pkt->hard.source == ARCNET_MAC_Address) { + fprintf(stderr, "arcnet: self sent packet?\n"); + return 0; + } + if (pkt->soft.raw[0] != 0xCD) { + /* fprintf(stderr,"arcnet: Non-BACnet packet.\n"); */ + return 0; + } + if ((pkt->hard.dest != ARCNET_MAC_Address) && + (pkt->hard.dest != ARCNET_BROADCAST)) { + fprintf(stderr, "arcnet: This packet is not for us.\n"); + return 0; + } + if ((pkt->soft.raw[1] != 0x82) || /* DSAP */ + (pkt->soft.raw[2] != 0x82) || /* LSAP */ + (pkt->soft.raw[3] != 0x03)) { /* LLC Control */ + fprintf(stderr, "arcnet: BACnet packet has invalid LLC.\n"); + return 0; + } + /* It must be addressed to us or be a Broadcast */ + if ((pkt->hard.dest != ARCNET_MAC_Address) && + (pkt->hard.dest != ARCNET_BROADCAST)) { + fprintf(stderr, "arcnet: This packet is not for us.\n"); + return 0; + } + /* copy the source address */ + src->mac_len = 1; + src->mac[0] = pkt->hard.source; + /* compute the PDU length */ + pdu_len = received_bytes - ARC_HDR_SIZE; + pdu_len -= 4 /* SC, DSAP, SSAP, LLC Control */ ; + /* copy the buffer into the PDU */ + if (pdu_len < max_pdu) + memmove(&pdu[0], &pkt->soft.raw[4], pdu_len); + /* silently ignore packets that are too large */ + else + pdu_len = 0; + + return pdu_len; +} + +void arcnet_get_my_address(BACNET_ADDRESS * my_address) +{ + int i = 0; + + my_address->mac_len = 1; + my_address->mac[0] = ARCNET_MAC_Address; + my_address->net = 0; /* DNET=0 is local only, no routing */ + my_address->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + my_address->adr[i] = 0; + } + + return; +} + +void arcnet_get_broadcast_address(BACNET_ADDRESS * dest) +{ /* destination address */ + int i = 0; /* counter */ + + if (dest) { + dest->mac[0] = ARCNET_BROADCAST; + dest->mac_len = 1; + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; /* always zero when DNET is broadcast */ + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = 0; + } + } + + return; +} diff --git a/bacnet-stack/ports/linux/bip-init.c b/bacnet-stack/ports/linux/bip-init.c index e69de29b..d672ca5a 100644 --- a/bacnet-stack/ports/linux/bip-init.c +++ b/bacnet-stack/ports/linux/bip-init.c @@ -0,0 +1,148 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#include /* for standard integer types uint8_t etc. */ +#include /* for the standard bool type. */ +#include "bacdcode.h" +#include "bip.h" +#include "net.h" + + +static int get_local_ifr_ioctl(char *ifname, struct ifreq *ifr, + int request) +{ + int fd; + int rv; /* return value */ + + strncpy(ifr->ifr_name, ifname, sizeof(ifr->ifr_name)); + fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (fd < 0) + rv = fd; + else + rv = ioctl(fd, request, ifr); + + return rv; +} + +static int get_local_address_ioctl(char *ifname, + struct in_addr *addr, int request) +{ + struct ifreq ifr = { {{0}} }; + struct sockaddr_in *tcpip_address; + int rv; /* return value */ + + rv = get_local_ifr_ioctl(ifname, &ifr, request); + if (rv >= 0) { + tcpip_address = (struct sockaddr_in *) &ifr.ifr_addr; + memcpy(addr, &tcpip_address->sin_addr, sizeof(struct in_addr)); + } + + return rv; +} + + +/* on Linux, ifname is eth0, ath0, arc0, and others. */ +static void bip_set_interface(char *ifname) +{ + struct in_addr local_address; + struct in_addr broadcast_address; + + /* setup local address */ + get_local_address_ioctl(ifname, &local_address, SIOCGIFADDR); + bip_set_addr(local_address.s_addr); +#ifdef BIP_DEBUG + fprintf(stderr, "Interface: %s\n", ifname); + fprintf(stderr, "IP Address: %s\n", inet_ntoa(local_address)); +#endif + /* setup local broadcast address */ + get_local_address_ioctl(ifname, &broadcast_address, SIOCGIFBRDADDR); + bip_set_broadcast_addr(broadcast_address.s_addr); +#ifdef BIP_DEBUG + fprintf(stderr, "IP Broadcast Address: %s\n", + inet_ntoa(broadcast_address)); + fprintf(stderr, "UDP Port: 0x%04X [%hu]\n", + bip_get_port(), + bip_get_port()); +#endif +} + +bool bip_init(char *ifname) +{ + int status = 0; /* return from socket lib calls */ + struct sockaddr_in sin; + int sockopt = 0; + int sock_fd = -1; + + if (ifname) + bip_set_interface(ifname); + else + bip_set_interface("eth0"); + /* assumes that the driver has already been initialized */ + sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + bip_set_socket(sock_fd); + if (sock_fd < 0) + return false; + /* Allow us to use the same socket for sending and receiving */ + /* This makes sure that the src port is correct when sending */ + sockopt = 1; + status = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, + &sockopt, sizeof(sockopt)); + if (status < 0) { + close(sock_fd); + bip_set_socket(-1); + return status; + } + /* allow us to send a broadcast */ + status = setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, + &sockopt, sizeof(sockopt)); + if (status < 0) { + close(sock_fd); + bip_set_socket(-1); + return status; + } + /* bind the socket to the local port number and IP address */ + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + sin.sin_port = htons(bip_get_port()); + memset(&(sin.sin_zero), '\0', sizeof(sin.sin_zero)); + status = bind(sock_fd, + (const struct sockaddr *) &sin, sizeof(struct sockaddr)); + if (status < 0) { + close(sock_fd); + bip_set_socket(-1); + return false; + } + + return true; +} diff --git a/bacnet-stack/ports/linux/ethernet.c b/bacnet-stack/ports/linux/ethernet.c index e69de29b..9838286f 100644 --- a/bacnet-stack/ports/linux/ethernet.c +++ b/bacnet-stack/ports/linux/ethernet.c @@ -0,0 +1,409 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#include /* for standard integer types uint8_t etc. */ +#include /* for the standard bool type. */ + +#include "net.h" +#include "bacdef.h" +#include "ethernet.h" +#include "bacdcode.h" + +/* commonly used comparison address for ethernet */ +uint8_t Ethernet_Broadcast[MAX_MAC_LEN] = + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; +/* commonly used empty address for ethernet quick compare */ +uint8_t Ethernet_Empty_MAC[MAX_MAC_LEN] = { 0, 0, 0, 0, 0, 0 }; + +/* my local device data - MAC address */ +uint8_t Ethernet_MAC_Address[MAX_MAC_LEN] = { 0 }; + +static int eth802_sockfd = -1; /* 802.2 file handle */ +static struct sockaddr eth_addr = { 0 }; /* used for binding 802.2 */ + +bool ethernet_valid(void) +{ + return (eth802_sockfd >= 0); +} + +void ethernet_cleanup(void) +{ + if (ethernet_valid()) + close(eth802_sockfd); + eth802_sockfd = -1; + + return; +} + +/*---------------------------------------------------------------------- + Portable function to set a socket into nonblocking mode. + Calling this on a socket causes all future read() and write() calls on + that socket to do only as much as they can immediately, and return + without waiting. + If no data can be read or written, they return -1 and set errno + to EAGAIN (or EWOULDBLOCK). + Thanks to Bjorn Reese for this code. +----------------------------------------------------------------------*/ +int setNonblocking(int fd) +{ + int flags; + + if (-1 == (flags = fcntl(fd, F_GETFL, 0))) + flags = 0; + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); +} + +/* opens an 802.2 socket to receive and send packets */ +static int ethernet_bind(struct sockaddr *eth_addr, char *interface_name) +{ + int sock_fd = -1; /* return value */ + int uid = 0; + + fprintf(stderr, "ethernet: opening \"%s\"\n", interface_name); + /* check to see if we are being run as root */ + uid = getuid(); + if (uid != 0) { + fprintf(stderr, + "ethernet: Unable to open an 802.2 socket. " + "Try running with root priveleges.\n"); + return sock_fd; + } + /* note: on some systems you may have to add or enable in */ + /* modules.conf (or in modutils/alias on Debian with update-modules) */ + /* alias net-pf-17 af_packet */ + /* Then follow it by: # modprobe af_packet */ + + /* Attempt to open the socket for 802.2 ethernet frames */ + if ((sock_fd = socket(PF_INET, SOCK_PACKET, htons(ETH_P_802_2))) < 0) { + /* Error occured */ + fprintf(stderr, + "ethernet: Error opening socket: %s\n", strerror(errno)); + fprintf(stderr, + "You might need to add the following to modules.conf\n" + "(or in /etc/modutils/alias on Debian with update-modules):\n" + "alias net-pf-17 af_packet\n" + "Also, add af_packet to /etc/modules.\n" + "Then follow it by:\n" "# modprobe af_packet\n"); + exit(-1); + } + /* Bind the socket to an address */ + eth_addr->sa_family = PF_INET; + /* Clear the memory before copying */ + memset(eth_addr->sa_data, '\0', sizeof(eth_addr->sa_data)); + /* Strcpy the interface name into the address */ + strncpy(eth_addr->sa_data, interface_name, + sizeof(eth_addr->sa_data) - 1); + fprintf(stderr, "ethernet: binding \"%s\"\n", eth_addr->sa_data); + /* Attempt to bind the socket to the interface */ + if (bind(sock_fd, eth_addr, sizeof(struct sockaddr)) != 0) { + /* Bind problem, close socket and return */ + fprintf(stderr, + "ethernet: Unable to bind 802.2 socket : %s\n", + strerror(errno)); + fprintf(stderr, + "You might need to add the following to modules.conf\n" + "(or in /etc/modutils/alias on Debian with update-modules):\n" + "alias net-pf-17 af_packet\n" + "Also, add af_packet to /etc/modules.\n" + "Then follow it by:\n" "# modprobe af_packet\n"); + /* Close the socket */ + close(sock_fd); + exit(-1); + } + + atexit(ethernet_cleanup); + + return sock_fd; +} + +/* function to find the local ethernet MAC address */ +static int get_local_hwaddr(const char *ifname, unsigned char *mac) +{ + struct ifreq ifr; + int fd; + int rv; /* return value - error value from df or ioctl call */ + + /* determine the local MAC address */ + strcpy(ifr.ifr_name, ifname); + fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (fd < 0) + rv = fd; + else { + rv = ioctl(fd, SIOCGIFHWADDR, &ifr); + if (rv >= 0) /* worked okay */ + memcpy(mac, ifr.ifr_hwaddr.sa_data, IFHWADDRLEN); + } + + return rv; +} + +bool ethernet_init(char *interface_name) +{ + if (interface_name) { + get_local_hwaddr(interface_name, Ethernet_MAC_Address); + eth802_sockfd = ethernet_bind(ð_addr, interface_name); + } else { + get_local_hwaddr("eth0", Ethernet_MAC_Address); + eth802_sockfd = ethernet_bind(ð_addr, "eth0"); + } + + return ethernet_valid(); +} + +/* function to send a packet out the 802.2 socket */ +/* returns number of bytes sent on success, negative on failure */ +int ethernet_send_pdu(BACNET_ADDRESS * dest, /* destination address */ + BACNET_NPDU_DATA * npdu_data, /* network information */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len) +{ /* number of bytes of data */ + int i = 0; /* counter */ + int bytes = 0; + BACNET_ADDRESS src = { 0 }; /* source address for npdu */ + uint8_t mtu[MAX_MPDU] = { 0 }; /* our buffer */ + int mtu_len = 0; + + (void) npdu_data; + /* load the BACnet address for NPDU data */ + for (i = 0; i < 6; i++) { + src.mac[i] = Ethernet_MAC_Address[i]; + src.mac_len++; + } + + /* don't waste time if the socket is not valid */ + if (eth802_sockfd < 0) { + fprintf(stderr, "ethernet: 802.2 socket is invalid!\n"); + return -1; + } + /* load destination ethernet MAC address */ + if (dest->mac_len == 6) { + for (i = 0; i < 6; i++) { + mtu[i] = dest->mac[i]; + } + } else { + fprintf(stderr, "ethernet: invalid destination MAC address!\n"); + return -2; + } + + /* load source ethernet MAC address */ + if (src.mac_len == 6) { + for (i = 0; i < 6; i++) { + mtu[6 + i] = src.mac[i]; + } + } else { + fprintf(stderr, "ethernet: invalid source MAC address!\n"); + return -3; + } + /* Logical PDU portion */ + mtu[14] = 0x82; /* DSAP for BACnet */ + mtu[15] = 0x82; /* SSAP for BACnet */ + mtu[16] = 0x03; /* Control byte in header */ + mtu_len = 17; + if ((mtu_len + pdu_len) > MAX_MPDU) { + fprintf(stderr, "ethernet: PDU is too big to send!\n"); + return -4; + } + memcpy(&mtu[mtu_len], pdu, pdu_len); + mtu_len += pdu_len; + /* packet length - only the logical portion, not the address */ + encode_unsigned16(&mtu[12], 3 + pdu_len); + + /* Send the packet */ + bytes = + sendto(eth802_sockfd, &mtu, mtu_len, 0, + (struct sockaddr *) ð_addr, sizeof(struct sockaddr)); + /* did it get sent? */ + if (bytes < 0) + fprintf(stderr, "ethernet: Error sending packet: %s\n", + strerror(errno)); + + return bytes; +} + +/* receives an 802.2 framed packet */ +/* returns the number of octets in the PDU, or zero on failure */ +uint16_t ethernet_receive(BACNET_ADDRESS * src, /* source address */ + uint8_t * pdu, /* PDU data */ + uint16_t max_pdu, /* amount of space available in the PDU */ + unsigned timeout) +{ /* number of milliseconds to wait for a packet */ + int received_bytes; + uint8_t buf[MAX_MPDU] = { 0 }; /* data */ + uint16_t pdu_len = 0; /* return value */ + fd_set read_fds; + int max; + struct timeval select_timeout; + + /* Make sure the socket is open */ + if (eth802_sockfd <= 0) + return 0; + + /* we could just use a non-blocking socket, but that consumes all + the CPU time. We can use a timeout; it is only supported as + a select. */ + if (timeout >= 1000) { + select_timeout.tv_sec = timeout / 1000; + select_timeout.tv_usec = + 1000 * (timeout - select_timeout.tv_sec * 1000); + } else { + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 1000 * timeout; + } + FD_ZERO(&read_fds); + FD_SET(eth802_sockfd, &read_fds); + max = eth802_sockfd; + + if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) + received_bytes = read(eth802_sockfd, &buf[0], sizeof(buf)); + else + return 0; + + /* See if there is a problem */ + if (received_bytes < 0) { + /* EAGAIN Non-blocking I/O has been selected */ + /* using O_NONBLOCK and no data */ + /* was immediately available for reading. */ + if (errno != EAGAIN) + fprintf(stderr, + "ethernet: Read error in receiving packet: %s\n", + strerror(errno)); + return 0; + } + + if (received_bytes == 0) + return 0; + + /* the signature of an 802.2 BACnet packet */ + if ((buf[14] != 0x82) && (buf[15] != 0x82)) { + /*fprintf(stderr,"ethernet: Non-BACnet packet\n"); */ + return 0; + } + /* copy the source address */ + src->mac_len = 6; + memmove(src->mac, &buf[6], 6); + + /* check destination address for when */ + /* the Ethernet card is in promiscious mode */ + if ((memcmp(&buf[0], Ethernet_MAC_Address, 6) != 0) + && (memcmp(&buf[0], Ethernet_Broadcast, 6) != 0)) { + /*fprintf(stderr, "ethernet: This packet isn't for us\n"); */ + return 0; + } + + (void) decode_unsigned16(&buf[12], &pdu_len); + pdu_len -= 3 /* DSAP, SSAP, LLC Control */ ; + /* copy the buffer into the PDU */ + if (pdu_len < max_pdu) + memmove(&pdu[0], &buf[17], pdu_len); + /* ignore packets that are too large */ + else + pdu_len = 0; + + + return pdu_len; +} + +void ethernet_set_my_address(BACNET_ADDRESS * my_address) +{ + int i = 0; + + for (i = 0; i < 6; i++) { + Ethernet_MAC_Address[i] = my_address->mac[i]; + } + + return; +} + +void ethernet_get_my_address(BACNET_ADDRESS * my_address) +{ + int i = 0; + + my_address->mac_len = 0; + for (i = 0; i < 6; i++) { + my_address->mac[i] = Ethernet_MAC_Address[i]; + my_address->mac_len++; + } + my_address->net = 0; /* DNET=0 is local only, no routing */ + my_address->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + my_address->adr[i] = 0; + } + + return; +} + +void ethernet_get_broadcast_address(BACNET_ADDRESS * dest) +{ /* destination address */ + int i = 0; /* counter */ + + if (dest) { + for (i = 0; i < 6; i++) { + dest->mac[i] = Ethernet_Broadcast[i]; + } + dest->mac_len = 6; + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; /* always zero when DNET is broadcast */ + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = 0; + } + } + + return; +} + +void ethernet_debug_address(const char *info, BACNET_ADDRESS * dest) +{ + int i = 0; /* counter */ + + if (info) + fprintf(stderr, "%s", info); + if (dest) { + fprintf(stderr, "Address:\n"); + fprintf(stderr, " MAC Length=%d\n", dest->mac_len); + fprintf(stderr, " MAC Address="); + for (i = 0; i < MAX_MAC_LEN; i++) { + fprintf(stderr, "%02X ", (unsigned) dest->mac[i]); + } + fprintf(stderr, "\n"); + fprintf(stderr, " Net=%hu\n", dest->net); + fprintf(stderr, " Len=%d\n", dest->len); + fprintf(stderr, " Adr="); + for (i = 0; i < MAX_MAC_LEN; i++) { + fprintf(stderr, "%02X ", (unsigned) dest->adr[i]); + } + fprintf(stderr, "\n"); + } + + return; +} diff --git a/bacnet-stack/ports/linux/main.c b/bacnet-stack/ports/linux/main.c index e69de29b..7075ea49 100644 --- a/bacnet-stack/ports/linux/main.c +++ b/bacnet-stack/ports/linux/main.c @@ -0,0 +1,298 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "address.h" +#include "bacdef.h" +#include "handlers.h" +#include "client.h" +#include "bacdcode.h" +#include "npdu.h" +#include "apdu.h" +#include "iam.h" +#include "tsm.h" +#include "device.h" +#include "bacfile.h" +#include "datalink.h" +#include "net.h" +#include "txbuf.h" + +/* This is an example application using the BACnet Stack on Linux */ +bool Who_Is_Request = true; + +/* buffers used for receiving */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +static void LocalIAmHandler(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src) +{ + int len = 0; + uint32_t device_id = 0; + unsigned max_apdu = 0; + int segmentation = 0; + uint16_t vendor_id = 0; + + (void) src; + (void) service_len; + len = iam_decode_service_request(service_request, + &device_id, &max_apdu, &segmentation, &vendor_id); + fprintf(stderr, "Received I-Am Request"); + if (len != -1) { + fprintf(stderr, " from %u!\n", device_id); + address_add(device_id, max_apdu, src); + } else + fprintf(stderr, "!\n"); + + return; +} + +static void Read_Properties(void) +{ + uint32_t device_id = 0; + bool status = false; + unsigned max_apdu = 0; + BACNET_ADDRESS src; + bool next_device = false; + static unsigned index = 0; + static unsigned property = 0; + /* list of required (and some optional and proprietary) + properties in the Device Object. Note that this demo + tests for error messages so that the device doesn't have + to have all the properties listed here. */ + const int object_props[] = { + PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, + PROP_OBJECT_TYPE, + PROP_SYSTEM_STATUS, + PROP_VENDOR_NAME, + PROP_VENDOR_IDENTIFIER, + PROP_MODEL_NAME, + PROP_FIRMWARE_REVISION, + PROP_APPLICATION_SOFTWARE_VERSION, + PROP_PROTOCOL_VERSION, + PROP_PROTOCOL_CONFORMANCE_CLASS, + PROP_PROTOCOL_SERVICES_SUPPORTED, + PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, + PROP_MAX_APDU_LENGTH_ACCEPTED, + PROP_SEGMENTATION_SUPPORTED, + PROP_LOCAL_TIME, + PROP_LOCAL_DATE, + PROP_UTC_OFFSET, + PROP_DAYLIGHT_SAVINGS_STATUS, + PROP_APDU_SEGMENT_TIMEOUT, + PROP_APDU_TIMEOUT, + PROP_NUMBER_OF_APDU_RETRIES, + PROP_TIME_SYNCHRONIZATION_RECIPIENTS, + PROP_MAX_MASTER, + PROP_MAX_INFO_FRAMES, + PROP_DEVICE_ADDRESS_BINDING, + /* note: PROP_OBJECT_LIST is missing because + the result can be very large. Read index 0 + which gives us the number of objects in the list, + and then we can read index 1, 2.. n one by one, + rather than trying to read the entire object + list in one message. */ + /* some proprietary properties */ + 514, 515, + /* end of list */ + -1 + }; + + if (address_count()) { + if (address_get_by_index(index, &device_id, &max_apdu, &src)) { + if (object_props[property] < 0) + next_device = true; + else { + /* note: if we wanted to do this synchronously, we would get the + invoke ID from the sending of the request, and wait until we + got the reply with matching invoke ID or the TSM of the + invoke ID expired. This demo is doing things asynchronously. */ + status = Send_Read_Property_Request(device_id, /* destination device */ + OBJECT_DEVICE, + device_id, object_props[property], BACNET_ARRAY_ALL); + if (status) + property++; + } + } else + next_device = true; + if (next_device) { + next_device = false; + index++; + if (index >= MAX_ADDRESS_CACHE) + index = 0; + property = 0; + } + } + + return; +} + +static void Init_Service_Handlers(void) +{ + /* we need to handle who-is to support dynamic device binding */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, + handler_who_is); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, + LocalIAmHandler); + + /* 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); + /* Set the handlers for any confirmed services that we support. */ + /* We must implement read property - it's required! */ + apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY, + handler_read_property); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, + handler_write_property); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_ATOMIC_READ_FILE, + handler_atomic_read_file); + /* handle the data coming back from confirmed requests */ + apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_READ_PROPERTY, + handler_read_property_ack); + apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_ATOMIC_READ_FILE, + handler_atomic_read_file_ack); +} + +static void print_address_cache(void) +{ + unsigned i, j; + BACNET_ADDRESS address; + uint32_t device_id = 0; + unsigned max_apdu = 0; + + fprintf(stderr, "Device\tMAC\tMaxAPDU\tNet\n"); + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + if (address_get_by_index(i, &device_id, &max_apdu, &address)) { + fprintf(stderr, "%u\t", device_id); + for (j = 0; j < address.mac_len; j++) { + fprintf(stderr, "%02X", address.mac[j]); + } + fprintf(stderr, "\t"); + fprintf(stderr, "%hu\t", max_apdu); + fprintf(stderr, "%hu\n", address.net); + } + } +} + +static void print_tsm_stats(void) +{ + int idle = 0; + int total = 0; + + idle = tsm_transaction_idle_count(); + total = MAX_TSM_TRANSACTIONS; + fprintf(stderr, "TSM: %d idle of %d transactions\n", idle, total); +} + +static void sig_handler(int signo) +{ + datalink_cleanup(); + print_address_cache(); + print_tsm_stats(); + + exit(0); +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + uint16_t pdu_len = 0; + unsigned timeout = 100; /* milliseconds */ + unsigned count = 0; /* milliseconds */ + time_t start_time; + time_t new_time = 0; + + start_time = time(NULL); /* get current time */ + /* Linux specials */ + signal(SIGINT, sig_handler); + signal(SIGHUP, sig_handler); + signal(SIGTERM, sig_handler); + /* setup this BACnet Server device */ + Device_Set_Object_Instance_Number(111); + Init_Service_Handlers(); +#ifdef BACDL_ETHERNET + /* init the physical layer */ + if (!ethernet_init("eth0")) + return 1; +#endif +#ifdef BACDL_BIP + bip_set_interface("eth0"); + bip_set_port(0xBAC0); + if (!bip_init()) + return 1; +#endif +#ifdef BACDL_ARCNET + if (!arcnet_init("arc0")) + return 1; +#endif + + /* loop forever */ + for (;;) { + /* input */ + new_time = time(NULL); + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + if (new_time > start_time) { + tsm_timer_milliseconds(new_time - start_time * 1000); + start_time = new_time; + } + if (I_Am_Request) { + I_Am_Request = false; + iam_send(&Handler_Transmit_Buffer[0]); + } else if (Who_Is_Request) { + Who_Is_Request = false; + Send_WhoIs(-1, -1); + } + /* output */ + /* some round robin task switching */ + count++; + switch (count) { + case 1: + /* used for testing, but kind of noisy on the network */ + /*Read_Properties(); */ + break; + case 2: + break; + default: + count = 0; + break; + } + + /* blink LEDs, Turn on or off outputs, etc */ + } + + return 0; +} diff --git a/bacnet-stack/ports/linux/net.h b/bacnet-stack/ports/linux/net.h index e69de29b..1afeabce 100644 --- a/bacnet-stack/ports/linux/net.h +++ b/bacnet-stack/ports/linux/net.h @@ -0,0 +1,99 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ + +#ifndef NET_H +#define NET_H + +/* common unix sockets headers needed */ +#include /* basic system data types */ +#include /* timeval{} for select() */ +#include /* timespec{} for pselect() */ +#include /* sockaddr_in{} and other Internet defns */ +#include /* inet(3) functions */ +#include /* for nonblocking */ +#include +#include +#include +#include +#include +#include +#include /* for S_xxx file mode constants */ +#include /* for iovec{} and readv/writev */ +#include +#include +#include /* for Unix domain sockets */ + +#ifdef HAVE_SYS_SELECT_H +# include /* for convenience */ +#endif + +#ifdef HAVE_POLL_H +# include /* for convenience */ +#endif + +#ifdef HAVE_STRINGS_H +# include /* for convenience */ +#endif + +/* Three headers are normally needed for socket/file ioctl's: + * , , and . + */ +#ifdef HAVE_SYS_IOCTL_H +# include +#endif +#ifdef HAVE_SYS_FILIO_H +# include +#endif +#ifdef HAVE_SYS_SOCKIO_H +# include +#endif + +#ifdef HAVE_PTHREAD_H +# include +#endif + + +#define ENUMS +#include +#include +#include +#include +#include /* for the glibc version number */ +#if __GLIBC__ >= 2 && __GLIBC_MINOR >= 1 +#include +#include /* the L2 protocols */ +#else +#include +#include +#include +#include +#endif +#include +#include +#include +#include +#include + +#endif diff --git a/bacnet-stack/ports/linux/rs485.c b/bacnet-stack/ports/linux/rs485.c index e69de29b..c534e81b 100644 --- a/bacnet-stack/ports/linux/rs485.c +++ b/bacnet-stack/ports/linux/rs485.c @@ -0,0 +1,93 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307 + USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +/* The module handles sending data out the RS-485 port */ +/* and handles receiving data from the RS-485 port. */ +/* Customize this file for your specific hardware */ +#include +#include +#include +#include + +#include "mstp.h" + +/* Transmits a Frame on the wire */ +void RS485_Send_Frame(struct mstp_port_struct_t *mstp_port, /* port specific data */ + uint8_t * buffer, /* frame to send (up to 501 bytes of data) */ + uint16_t nbytes) +{ /* number of bytes of data (up to 501) */ + + /* in order to avoid line contention */ + while (mstp_port->Turn_Around_Waiting) { + /* wait, yield, or whatever */ + } + + /* Disable the receiver, and enable the transmit line driver. */ + + while (nbytes) { + putc(*buffer, stderr); + buffer++; + nbytes--; + } + + /* Wait until the final stop bit of the most significant CRC octet */ + /* has been transmitted but not more than Tpostdrive. */ + + /* Disable the transmit line driver. */ + + return; +} + +/* called by timer, interrupt(?) or other thread */ +void RS485_Check_UART_Data(struct mstp_port_struct_t *mstp_port) +{ + if (mstp_port->ReceiveError == true) { + /* wait for state machine to clear this */ + } + /* wait for state machine to read from the DataRegister */ + else if (mstp_port->DataAvailable == false) { + /* check for data */ + + /* if error, */ + /* ReceiveError = TRUE; */ + /* return; */ + + mstp_port->DataRegister = 0; /* FIXME: Get this data from UART or buffer */ + + /* if data is ready, */ + /* DataAvailable = TRUE; */ + /* return; */ + } +} diff --git a/bacnet-stack/ports/pic18f6720/ai.c b/bacnet-stack/ports/pic18f6720/ai.c index e69de29b..31adc66b 100644 --- a/bacnet-stack/ports/pic18f6720/ai.c +++ b/bacnet-stack/ports/pic18f6720/ai.c @@ -0,0 +1,200 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ + +/* Analog Input Objects customize for your use */ + +#include +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "config.h" + +/* Analog Input = Photocell */ +#define MAX_ANALOG_INPUTS 2 + +static uint8_t Present_Value[MAX_ANALOG_INPUTS]; + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need validate that the */ +/* given instance exists */ +bool Analog_Input_Valid_Instance(uint32_t object_instance) +{ + if (object_instance < MAX_ANALOG_INPUTS) + return true; + + return false; +} + +/* we simply have 0-n object instances. */ +unsigned Analog_Input_Count(void) +{ + return MAX_ANALOG_INPUTS; +} + +/* we simply have 0-n object instances. */ +uint32_t Analog_Input_Index_To_Instance(unsigned index) +{ + return index; +} + +char *Analog_Input_Name(uint32_t object_instance) +{ + static char text_string[16] = ""; /* okay for single thread */ + + if (object_instance < MAX_ANALOG_INPUTS) { + sprintf(text_string, "AI-%lu", object_instance); + return text_string; + } + + return NULL; +} + +static float Analog_Input_Present_Value(uint32_t object_instance) +{ + float value = 0.0; + + if (object_instance < MAX_ANALOG_INPUTS) + value = Present_Value[object_instance]; + + return value; +} + +/* return apdu length, or -1 on error */ +/* assumption - object has already exists */ +int Analog_Input_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + + (void) array_index; + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_ANALOG_INPUT, + object_instance); + break; + /* note: Name and Description don't have to be the same. + You could make Description writable and different */ + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, + Analog_Input_Name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_tagged_enumerated(&apdu[0], + OBJECT_ANALOG_INPUT); + break; + case PROP_PRESENT_VALUE: + apdu_len = encode_tagged_real(&apdu[0], + Analog_Input_Present_Value(object_instance)); + break; + case PROP_STATUS_FLAGS: + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: + apdu_len = encode_tagged_boolean(&apdu[0], false); + break; + case PROP_UNITS: + apdu_len = encode_tagged_enumerated(&apdu[0], UNITS_PERCENT); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testAnalogInput(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_ANALOG_OUTPUT; + uint32_t decoded_instance = 0; + uint32_t instance = 123; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + + /* FIXME: we should do a lot more testing here... */ + len = Analog_Input_Encode_Property_APDU(&apdu[0], + instance, + PROP_OBJECT_IDENTIFIER, + BACNET_ARRAY_ALL, &error_class, &error_code); + ct_test(pTest, len >= 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == OBJECT_ANALOG_INPUT); + ct_test(pTest, decoded_instance == instance); + + return; +} + +#ifdef TEST_ANALOG_INPUT +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Analog Input", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testAnalogInput); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_ANALOG_INPUT */ +#endif /* TEST */ diff --git a/bacnet-stack/ports/pic18f6720/av.c b/bacnet-stack/ports/pic18f6720/av.c index e69de29b..36ff384f 100644 --- a/bacnet-stack/ports/pic18f6720/av.c +++ b/bacnet-stack/ports/pic18f6720/av.c @@ -0,0 +1,416 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* Analog Value Objects - customize for your use */ + +#include +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bacapp.h" +#include "config.h" /* the custom stuff */ +#include "wp.h" + +#define MAX_ANALOG_VALUES 4 + +/* we choose to have a NULL level in our system represented by */ +/* a particular value. When the priorities are not in use, they */ +/* will be relinquished (i.e. set to the NULL level). */ +#define ANALOG_LEVEL_NULL 255 +/* When all the priorities are level null, the present value returns */ +/* the Relinquish Default value */ +#define ANALOG_RELINQUISH_DEFAULT 0 +/* Here is our Present_Value. They are supposed to be Real, but */ +/* we don't have that kind of memory, so we will use a single byte */ +/* and load a Real for returning the value when asked. */ +static uint8_t Present_Value[MAX_ANALOG_VALUES]; + +/* we need to have our arrays initialized before answering any calls */ +static bool Analog_Value_Initialized = false; + +void Analog_Value_Init(void) +{ + unsigned i; + + if (!Analog_Value_Initialized) { + Analog_Value_Initialized = true; + + /* initialize all the analog output priority arrays to NULL */ + for (i = 0; i < MAX_ANALOG_VALUES; i++) { + Present_Value[i] = ANALOG_LEVEL_NULL; + } + } + + return; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need validate that the */ +/* given instance exists */ +bool Analog_Value_Valid_Instance(uint32_t object_instance) +{ + Analog_Value_Init(); + if (object_instance < MAX_ANALOG_VALUES) + return true; + + return false; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then count how many you have */ +unsigned Analog_Value_Count(void) +{ + Analog_Value_Init(); + return MAX_ANALOG_VALUES; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the instance */ +/* that correlates to the correct index */ +uint32_t Analog_Value_Index_To_Instance(unsigned index) +{ + Analog_Value_Init(); + return index; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the index */ +/* that correlates to the correct instance number */ +unsigned Analog_Value_Instance_To_Index(uint32_t object_instance) +{ + unsigned index = MAX_ANALOG_VALUES; + + Analog_Value_Init(); + if (object_instance < MAX_ANALOG_VALUES) + index = object_instance; + + return index; +} + +static float Analog_Value_Present_Value(uint32_t object_instance) +{ + float value = ANALOG_RELINQUISH_DEFAULT; + unsigned index = 0; + unsigned i = 0; + + Analog_Value_Init(); + index = Analog_Value_Instance_To_Index(object_instance); + if (index < MAX_ANALOG_VALUES) { + value = Present_Value[index]; + } + + return value; +} + +/* note: the object name must be unique within this device */ +char *Analog_Value_Name(uint32_t object_instance) +{ + static char text_string[32] = ""; /* okay for single thread */ + + if (object_instance < MAX_ANALOG_VALUES) { + sprintf(text_string, "AV-%lu", object_instance); + return text_string; + } + + return NULL; +} + +/* return apdu len, or -1 on error */ +int Analog_Value_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int len = 0; + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + float real_value = (float) 1.414; + unsigned object_index = 0; + unsigned i = 0; + bool state = false; + + Analog_Value_Init(); + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_ANALOG_VALUE, + object_instance); + break; + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, + Analog_Value_Name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_tagged_enumerated(&apdu[0], OBJECT_ANALOG_VALUE); + break; + case PROP_PRESENT_VALUE: + real_value = Analog_Value_Present_Value(object_instance); + apdu_len = encode_tagged_real(&apdu[0], real_value); + break; + case PROP_STATUS_FLAGS: + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: +#if 0 + object_index = Analog_Value_Instance_To_Index(object_instance); + state = Analog_Value_Out_Of_Service[object_index]; +#endif + apdu_len = encode_tagged_boolean(&apdu[0], false); + break; + case PROP_UNITS: + apdu_len = encode_tagged_enumerated(&apdu[0], UNITS_PERCENT); + break; +#if 0 + case PROP_PRIORITY_ARRAY: + /* Array element zero is the number of elements in the array */ + if (array_index == 0) + apdu_len = + encode_tagged_unsigned(&apdu[0], BACNET_MAX_PRIORITY); + /* if no index was specified, then try to encode the entire list */ + /* into one packet. */ + else if (array_index == BACNET_ARRAY_ALL) { + object_index = Analog_Value_Instance_To_Index(object_instance); + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + /* FIXME: check if we have room before adding it to APDU */ + if (Present_Value[object_index][i] == + ANALOG_LEVEL_NULL) + len = encode_tagged_null(&apdu[apdu_len]); + else { + real_value = Present_Value[object_index][i]; + len = encode_tagged_real(&apdu[apdu_len], real_value); + } + /* add it if we have room */ + if ((apdu_len + len) < MAX_APDU) + apdu_len += len; + else { + *error_class = ERROR_CLASS_SERVICES; + *error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; + apdu_len = -1; + break; + } + } + } else { + object_index = Analog_Value_Instance_To_Index(object_instance); + if (array_index <= BACNET_MAX_PRIORITY) { + if (Present_Value[object_index][array_index - 1] == + ANALOG_LEVEL_NULL) + apdu_len = encode_tagged_null(&apdu[0]); + else { + real_value = + Present_Value[object_index][array_index - 1]; + apdu_len = encode_tagged_real(&apdu[0], real_value); + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = -1; + } + } + + break; + case PROP_RELINQUISH_DEFAULT: + real_value = ANALOG_RELINQUISH_DEFAULT; + apdu_len = encode_tagged_real(&apdu[0], real_value); + break; +#endif + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +/* returns true if successful */ +bool Analog_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + bool status = false; /* return value */ + unsigned int object_index = 0; + unsigned int priority = 0; + uint8_t level = ANALOG_LEVEL_NULL; + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + Analog_Value_Init(); + if (!Analog_Value_Valid_Instance(wp_data->object_instance)) { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + /* decode the some of the request */ + len = bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + switch (wp_data->object_property) { + case PROP_PRESENT_VALUE: + if (value.tag == BACNET_APPLICATION_TAG_REAL) { + priority = wp_data->priority; + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */ ) && + (value.type.Real >= 0.0) && (value.type.Real <= 100.0)) { + level = (uint8_t) value.type.Real; + object_index = + Analog_Value_Instance_To_Index(wp_data-> + object_instance); + priority--; + Present_Value[object_index] = level; + /* Note: you could set the physical output here if we + are the highest priority. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } else if (priority == 6) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } +#if 0 + } else if (value.tag == BACNET_APPLICATION_TAG_NULL) { + level = ANALOG_LEVEL_NULL; + object_index = + Analog_Value_Instance_To_Index(wp_data->object_instance); + priority = wp_data->priority; + if (priority && (priority <= BACNET_MAX_PRIORITY)) { + priority--; + Present_Value[object_index][priority] = level; + /* Note: you could set the physical output here to the next + highest priority, or to the relinquish default if no + priorities are set. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } +#endif + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; +#if 0 + case PROP_OUT_OF_SERVICE: + if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { + object_index = + Analog_Value_Instance_To_Index(wp_data->object_instance); + Analog_Value_Out_Of_Service[object_index] = value.type.Boolean; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; +#endif + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} + + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testAnalog_Value(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_ANALOG_VALUE; + uint32_t decoded_instance = 0; + uint32_t instance = 123; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + + len = Analog_Value_Encode_Property_APDU(&apdu[0], + instance, + PROP_OBJECT_IDENTIFIER, + BACNET_ARRAY_ALL, &error_class, &error_code); + ct_test(pTest, len != 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == OBJECT_ANALOG_VALUE); + ct_test(pTest, decoded_instance == instance); + + return; +} + +#ifdef TEST_ANALOG_VALUE +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Analog Value", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testAnalog_Value); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_ANALOG_VALUE */ +#endif /* TEST */ diff --git a/bacnet-stack/ports/pic18f6720/bi.c b/bacnet-stack/ports/pic18f6720/bi.c index e69de29b..e3f5fe33 100644 --- a/bacnet-stack/ports/pic18f6720/bi.c +++ b/bacnet-stack/ports/pic18f6720/bi.c @@ -0,0 +1,232 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* Binary Input Objects customize for your use */ + +#include +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "config.h" + +#define MAX_BINARY_INPUTS 8 + +static BACNET_BINARY_PV Present_Value[MAX_BINARY_INPUTS]; + +static void Binary_Input_Initialize(void) +{ + static bool initialized = false; + unsigned i; + + if (!initialized) { + initialized = true; + for (i = 0; i < MAX_BINARY_INPUTS; i++) { + Present_Value[i] = BINARY_INACTIVE; + } + } +} + +/* we simply have 0-n object instances. */ +bool Binary_Input_Valid_Instance(uint32_t object_instance) +{ + if (object_instance < MAX_BINARY_INPUTS) + return true; + + return false; +} + +/* we simply have 0-n object instances. */ +unsigned Binary_Input_Count(void) +{ + return MAX_BINARY_INPUTS; +} + +/* we simply have 0-n object instances.*/ +uint32_t Binary_Input_Index_To_Instance(unsigned index) +{ + return index; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the index */ +/* that correlates to the correct instance number */ +unsigned Binary_Input_Instance_To_Index(uint32_t object_instance) +{ + unsigned index = MAX_BINARY_INPUTS; + + if (object_instance < MAX_BINARY_INPUTS) + index = object_instance; + + return index; +} + +static BACNET_BINARY_PV Binary_Input_Present_Value(uint32_t + object_instance) +{ + BACNET_BINARY_PV value = BINARY_INACTIVE; + unsigned index = 0; + + Binary_Input_Initialize(); + index = Binary_Input_Instance_To_Index(object_instance); + if (index < MAX_BINARY_INPUTS) { + value = Present_Value[index]; + } + + return value; +} + +char *Binary_Input_Name(uint32_t object_instance) +{ + static char text_string[16] = ""; /* okay for single thread */ + + if (object_instance < MAX_BINARY_INPUTS) { + sprintf(text_string, "BI-%lu", object_instance); + return text_string; + } + + return NULL; +} + +/* return apdu length, or -1 on error */ +/* assumption - object already exists, and has been bounds checked */ +int Binary_Input_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + BACNET_POLARITY polarity = POLARITY_NORMAL; + BACNET_BINARY_PV value = BINARY_INACTIVE; + + + (void) array_index; + Binary_Input_Initialize(); + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_BINARY_INPUT, + object_instance); + break; + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + /* note: object name must be unique in our device */ + characterstring_init_ansi(&char_string, + Binary_Input_Name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_tagged_enumerated(&apdu[0], OBJECT_BINARY_INPUT); + break; + case PROP_PRESENT_VALUE: + value = Binary_Input_Present_Value(object_instance); + apdu_len = encode_tagged_enumerated(&apdu[0],value); + break; + case PROP_STATUS_FLAGS: + /* note: see the details in the standard on how to use these */ + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + /* note: see the details in the standard on how to use this */ + apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: + apdu_len = encode_tagged_boolean(&apdu[0], false); + break; + case PROP_POLARITY: + apdu_len = encode_tagged_enumerated(&apdu[0], polarity); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testBinaryInput(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_BINARY_OUTPUT; + uint32_t decoded_instance = 0; + uint32_t instance = 123; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + + /* FIXME: we should do a lot more testing here... */ + len = Binary_Input_Encode_Property_APDU(&apdu[0], + instance, + PROP_OBJECT_IDENTIFIER, + BACNET_ARRAY_ALL, &error_class, &error_code); + ct_test(pTest, len >= 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == OBJECT_BINARY_INPUT); + ct_test(pTest, decoded_instance == instance); + + return; +} + +#ifdef TEST_BINARY_INPUT +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Binary Input", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testBinaryInput); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_BINARY_INPUT */ +#endif /* TEST */ diff --git a/bacnet-stack/ports/pic18f6720/bv.c b/bacnet-stack/ports/pic18f6720/bv.c index e69de29b..c56158ab 100644 --- a/bacnet-stack/ports/pic18f6720/bv.c +++ b/bacnet-stack/ports/pic18f6720/bv.c @@ -0,0 +1,338 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ + +/* Binary Value Objects - customize for your use */ + +#include +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "config.h" /* the custom stuff */ +#include "wp.h" + +#define MAX_BINARY_VALUES 8 + +static BACNET_BINARY_PV Present_Value[MAX_BINARY_VALUES]; + +static void Binary_Value_Initialize(void) +{ + static bool initialized = false; + unsigned i; + + if (!initialized) { + initialized = true; + for (i = 0; i < MAX_BINARY_VALUES; i++) { + Present_Value[i] = BINARY_INACTIVE; + } + } +} + +/* we simply have 0-n object instances. */ +bool Binary_Value_Valid_Instance(uint32_t object_instance) +{ + if (object_instance < MAX_BINARY_VALUES) + return true; + + return false; +} + +/* we simply have 0-n object instances. */ +unsigned Binary_Value_Count(void) +{ + return MAX_BINARY_VALUES; +} + +/* we simply have 0-n object instances. */ +uint32_t Binary_Value_Index_To_Instance(unsigned index) +{ + return index; +} + +/* we simply have 0-n object instances. */ +unsigned Binary_Value_Instance_To_Index(uint32_t object_instance) +{ + unsigned index = MAX_BINARY_VALUES; + + if (object_instance < MAX_BINARY_VALUES) + index = object_instance; + + return index; +} + +static BACNET_BINARY_PV Binary_Value_Present_Value(uint32_t + object_instance) +{ + BACNET_BINARY_PV value = BINARY_INACTIVE; + + Binary_Value_Initialize(); + if (object_instance < MAX_BINARY_VALUES) { + value = Present_Value[object_instance]; + } + + return value; +} + +/* note: the object name must be unique within this device */ +char *Binary_Value_Name(uint32_t object_instance) +{ + static char text_string[16] = ""; /* okay for single thread */ + + if (object_instance < MAX_BINARY_VALUES) { + sprintf(text_string, "BV-%lu", object_instance); + return text_string; + } + + return NULL; +} + +/* return apdu len, or -1 on error */ +int Binary_Value_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int len = 0; + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + BACNET_BINARY_PV present_value = BINARY_INACTIVE; + BACNET_POLARITY polarity = POLARITY_NORMAL; + unsigned object_index = 0; + unsigned i = 0; + bool state = false; + + Binary_Value_Initialize(); + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_BINARY_VALUE, + object_instance); + break; + /* note: Name and Description don't have to be the same. + You could make Description writable and different */ + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, + Binary_Value_Name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_tagged_enumerated(&apdu[0], OBJECT_BINARY_VALUE); + break; + case PROP_PRESENT_VALUE: + present_value = Binary_Value_Present_Value(object_instance); + apdu_len = encode_tagged_enumerated(&apdu[0], present_value); + break; + case PROP_STATUS_FLAGS: + /* note: see the details in the standard on how to use these */ + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + /* note: see the details in the standard on how to use this */ + apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: + apdu_len = encode_tagged_boolean(&apdu[0], false); + break; + case PROP_POLARITY: + /* FIXME: figure out the polarity */ + apdu_len = encode_tagged_enumerated(&apdu[0], polarity); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +/* returns true if successful */ +bool Binary_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + bool status = false; /* return value */ + unsigned int object_index = 0; + unsigned int priority = 0; + BACNET_BINARY_PV level = BINARY_NULL; + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + if (!Binary_Value_Valid_Instance(wp_data->object_instance)) { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + /* decode the some of the request */ + len = bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + switch (wp_data->object_property) { + case PROP_PRESENT_VALUE: + if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) { + priority = wp_data->priority; + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */ ) && + (value.type.Enumerated >= MIN_BINARY_PV) && + (value.type.Enumerated <= MAX_BINARY_PV)) { + level = value.type.Enumerated; + object_index = + Binary_Value_Instance_To_Index(wp_data-> + object_instance); + priority--; + /* NOTE: this Binary value has no priority array */ + Present_Value[object_index] = level; + /* Note: you could set the physical output here if we + are the highest priority. + However, if Out of Service is TRUE, then don't set the + physical output. */ + status = true; + } else if (priority == 6) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else if (value.tag == BACNET_APPLICATION_TAG_NULL) { +#if 0 + /* NOTE: this Binary Value has no priority array */ + level = BINARY_NULL; + object_index = + Binary_Value_Instance_To_Index(wp_data->object_instance); + priority = wp_data->priority; + if (priority && (priority <= BACNET_MAX_PRIORITY)) { + priority--; + Binary_Value_Level[object_index][priority] = level; + /* Note: you could set the physical output here to the next + highest priority, or to the relinquish default if no + priorities are set. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } +#else + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; +#endif + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; +#if 0 + case PROP_OUT_OF_SERVICE: + if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { + object_index = + Binary_Value_Instance_To_Index(wp_data->object_instance); + Binary_Value_Out_Of_Service[object_index] = value.type.Boolean; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; +#endif + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testBinary_Value(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_BINARY_VALUE; + uint32_t decoded_instance = 0; + uint32_t instance = 123; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + + len = Binary_Value_Encode_Property_APDU(&apdu[0], + instance, + PROP_OBJECT_IDENTIFIER, + BACNET_ARRAY_ALL, &error_class, &error_code); + ct_test(pTest, len != 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == OBJECT_BINARY_VALUE); + ct_test(pTest, decoded_instance == instance); + + return; +} + +#ifdef TEST_BINARY_VALUE +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Binary_Value", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testBinary_Value); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_BINARY_VALUE */ +#endif /* TEST */ diff --git a/bacnet-stack/ports/pic18f6720/device.c b/bacnet-stack/ports/pic18f6720/device.c index e69de29b..3780805a 100644 --- a/bacnet-stack/ports/pic18f6720/device.c +++ b/bacnet-stack/ports/pic18f6720/device.c @@ -0,0 +1,579 @@ +/************************************************************************** +* +* Copyright (C) 2007 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. +* +*********************************************************************/ + +#include +#include +#include /* for memmove */ +#include "bacdef.h" +#include "bacdcode.h" +#include "bacstr.h" +#include "bacenum.h" +#include "config.h" /* the custom stuff */ +#include "apdu.h" +#include "device.h" /* me */ +#include "dlmstp.h" +#include "rs485.h" +#include "ai.h" +#include "av.h" +#include "bi.h" +#include "bv.h" +#include "wp.h" +#include "dcc.h" + +/* 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 = 12345; +static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL; + +BACNET_REINITIALIZED_STATE_OF_DEVICE Reinitialize_State = + REINITIALIZED_STATE_IDLE; + +void Device_Reinit(void) +{ + dcc_set_status_duration(COMMUNICATION_ENABLE, 0); + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); +} + +void Device_Init(void) +{ + Reinitialize_State = REINITIALIZED_STATE_IDLE; + dcc_set_status_duration(COMMUNICATION_ENABLE, 0); + /* FIXME: Get the data from the eeprom */ + /* I2C_Read_Block(EEPROM_DEVICE_ADDRESS, + (char *)&Object_Instance_Number, + sizeof(Object_Instance_Number), + EEPROM_BACNET_ID_ADDR); */ +} + +/* methods to manipulate the data */ +uint32_t Device_Object_Instance_Number(void) +{ + return Object_Instance_Number; +} + +bool Device_Set_Object_Instance_Number(uint32_t object_id) +{ + bool status = true; /* return value */ + + if (object_id <= BACNET_MAX_INSTANCE) { + Object_Instance_Number = object_id; + /* FIXME: Write the data to the eeprom */ + /* I2C_Write_Block( + EEPROM_DEVICE_ADDRESS, + (char *)&Object_Instance_Number, + sizeof(Object_Instance_Number), + EEPROM_BACNET_ID_ADDR); */ + } else + status = false; + + return status; +} + +bool Device_Valid_Object_Instance_Number(uint32_t object_id) +{ + /* BACnet allows for a wildcard instance number */ + return ((Object_Instance_Number == object_id) || + (object_id == BACNET_MAX_INSTANCE)); +} + +BACNET_DEVICE_STATUS Device_System_Status(void) +{ + return System_Status; +} + +void Device_Set_System_Status(BACNET_DEVICE_STATUS status) +{ + if (status < MAX_DEVICE_STATUS) + System_Status = status; +} + +/* FIXME: put your vendor ID here! */ +uint16_t Device_Vendor_Identifier(void) +{ + return 0; +} + +uint8_t Device_Protocol_Version(void) +{ + return 1; +} + +uint8_t Device_Protocol_Revision(void) +{ + return 5; +} + +/* FIXME: MAX_APDU is defined in config.ini - set it! */ +uint16_t Device_Max_APDU_Length_Accepted(void) +{ + return MAX_APDU; +} + +BACNET_SEGMENTATION Device_Segmentation_Supported(void) +{ + return SEGMENTATION_NONE; +} + +uint16_t Device_APDU_Timeout(void) +{ + return 60000; +} + + +uint8_t Device_Number_Of_APDU_Retries(void) +{ + return 0; +} + +uint8_t Device_Database_Revision(void) +{ + return 0; +} + +/* Since many network clients depend on the object list */ +/* for discovery, it must be consistent! */ +unsigned Device_Object_List_Count(void) +{ + unsigned count = 1; /* at least 1 for device object */ + +/* FIXME: add objects as needed */ + count += Binary_Value_Count(); + count += Analog_Input_Count(); + count += Binary_Input_Count(); + + return count; +} + +/* Since many network clients depend on the object list */ +/* for discovery, it must be consistent! */ +bool Device_Object_List_Identifier(unsigned array_index, + int *object_type, uint32_t * instance) +{ + bool status = false; + unsigned object_index = 0; + unsigned object_count = 0; + + /* device object */ + if (array_index == 1) { + *object_type = OBJECT_DEVICE; + *instance = Object_Instance_Number; + status = true; + } + /* normalize the index since + we know it is not the previous objects */ + /* array index starts at 1 */ + object_index = array_index - 1; + /* 1 for the device object */ + object_count = 1; + /* FIXME: add objects as needed */ + /* binary value objects */ + if (!status) { + object_index -= object_count; + object_count = Binary_Value_Count(); + /* is it a valid index for this object? */ + if (object_index < object_count) { + *object_type = OBJECT_BINARY_VALUE; + *instance = Binary_Value_Index_To_Instance(object_index); + status = true; + } + } + /* analog input objects */ + if (!status) { + /* array index starts at 1, and 1 for the device object */ + object_index -= object_count; + object_count = Analog_Value_Count(); + if (object_index < object_count) { + *object_type = OBJECT_ANALOG_VALUE; + *instance = Analog_Value_Index_To_Instance(object_index); + status = true; + } + } + /* analog input objects */ + if (!status) { + /* array index starts at 1, and 1 for the device object */ + object_index -= object_count; + object_count = Analog_Input_Count(); + if (object_index < object_count) { + *object_type = OBJECT_ANALOG_INPUT; + *instance = Analog_Input_Index_To_Instance(object_index); + status = true; + } + } + /* binary input objects */ + if (!status) { + /* normalize the index since + we know it is not the previous objects */ + object_index -= object_count; + object_count = Binary_Input_Count(); + /* is it a valid index for this object? */ + if (object_index < object_count) { + *object_type = OBJECT_BINARY_INPUT; + *instance = Binary_Input_Index_To_Instance(object_index); + status = true; + } + } + + return status; +} + +/* return the length of the apdu encoded or -1 for error */ +int Device_Encode_Property_APDU(uint8_t * apdu, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + 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; + BACNET_TIME local_time; + BACNET_DATE local_date; + uint8_t year = 0; + char string_buffer[24]; + int16_t TimeZone = 0; + + /* FIXME: change the hardcoded names to suit your application */ + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_DEVICE, + Object_Instance_Number); + break; + case PROP_OBJECT_NAME: + (void) strcpypgm2ram(&string_buffer[0], "PIC18F6720 Device"); + characterstring_init_ansi(&char_string, string_buffer); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_tagged_enumerated(&apdu[0], OBJECT_DEVICE); + break; + case PROP_DESCRIPTION: + (void) strcpypgm2ram(&string_buffer[0], "BACnet Demo"); + characterstring_init_ansi(&char_string, string_buffer); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_SYSTEM_STATUS: + apdu_len = + encode_tagged_enumerated(&apdu[0], Device_System_Status()); + break; + case PROP_VENDOR_NAME: + (void) strcpypgm2ram(&string_buffer[0], "ASHRAE"); + characterstring_init_ansi(&char_string, string_buffer); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_VENDOR_IDENTIFIER: + apdu_len = + encode_tagged_unsigned(&apdu[0], Device_Vendor_Identifier()); + break; + case PROP_MODEL_NAME: + (void) strcpypgm2ram(&string_buffer[0], "GNU Demo"); + characterstring_init_ansi(&char_string, string_buffer); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_FIRMWARE_REVISION: + (void) strcpypgm2ram(&string_buffer[0], "1.00"); + characterstring_init_ansi(&char_string, string_buffer); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_APPLICATION_SOFTWARE_VERSION: + (void) strcpypgm2ram(&string_buffer[0], "1.00"); + characterstring_init_ansi(&char_string, string_buffer); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_LOCATION: + (void) strcpypgm2ram(&string_buffer[0], "USA"); + characterstring_init_ansi(&char_string, string_buffer); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_PROTOCOL_VERSION: + apdu_len = + encode_tagged_unsigned(&apdu[0], Device_Protocol_Version()); + break; + case PROP_PROTOCOL_REVISION: + apdu_len = + encode_tagged_unsigned(&apdu[0], Device_Protocol_Revision()); + break; + /* BACnet Legacy Support */ + case PROP_PROTOCOL_CONFORMANCE_CLASS: + apdu_len = encode_tagged_unsigned(&apdu[0], 1); + 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(i)); + } + apdu_len = encode_tagged_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); + } + /* FIXME: indicate the objects that YOU support */ + bitstring_set_bit(&bit_string, OBJECT_DEVICE, true); + bitstring_set_bit(&bit_string, OBJECT_ANALOG_VALUE, true); + bitstring_set_bit(&bit_string, OBJECT_BINARY_VALUE, true); + bitstring_set_bit(&bit_string, OBJECT_ANALOG_INPUT, true); + bitstring_set_bit(&bit_string, OBJECT_BINARY_INPUT, true); + apdu_len = encode_tagged_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 (array_index == 0) + apdu_len = encode_tagged_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 (array_index == BACNET_ARRAY_ALL) { + for (i = 1; i <= count; i++) { + if (Device_Object_List_Identifier(i, &object_type, + &instance)) { + len = + encode_tagged_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? */ + if ((apdu_len + len) >= MAX_APDU) { + *error_class = ERROR_CLASS_SERVICES; + *error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; + apdu_len = -1; + break; + } + } else { + /* error: internal error? */ + *error_class = ERROR_CLASS_SERVICES; + *error_code = ERROR_CODE_OTHER; + apdu_len = -1; + break; + } + } + } else { + if (Device_Object_List_Identifier(array_index, &object_type, + &instance)) + apdu_len = + encode_tagged_object_id(&apdu[0], object_type, + instance); + else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = -1; + } + } + break; + case PROP_MAX_APDU_LENGTH_ACCEPTED: + apdu_len = encode_tagged_unsigned(&apdu[0], + Device_Max_APDU_Length_Accepted()); + break; + case PROP_SEGMENTATION_SUPPORTED: + apdu_len = encode_tagged_enumerated(&apdu[0], + Device_Segmentation_Supported()); + break; + case PROP_APDU_TIMEOUT: + apdu_len = encode_tagged_unsigned(&apdu[0], Device_APDU_Timeout()); + break; + case PROP_NUMBER_OF_APDU_RETRIES: + apdu_len = + encode_tagged_unsigned(&apdu[0], + Device_Number_Of_APDU_Retries()); + break; + case PROP_DEVICE_ADDRESS_BINDING: + /* FIXME: encode the list here, if it exists */ + break; + case PROP_DATABASE_REVISION: + apdu_len = + encode_tagged_unsigned(&apdu[0], Device_Database_Revision()); + break; + case PROP_MAX_INFO_FRAMES: + apdu_len = + encode_tagged_unsigned(&apdu[0], dlmstp_max_info_frames()); + break; + case PROP_MAX_MASTER: + apdu_len = encode_tagged_unsigned(&apdu[0], dlmstp_max_master()); + break; + case PROP_LOCAL_TIME: + /* FIXME: if you support time */ + local_time.hour = 0; + local_time.min = 0; + local_time.sec = 0; + local_time.hundredths = 0; + apdu_len = encode_tagged_time(&apdu[0], &local_time); + break; + case PROP_UTC_OFFSET: + /* Note: BACnet Time Zone is inverse of everybody else */ + apdu_len = encode_tagged_signed(&apdu[0], 5 /* EST */ ); + break; + case PROP_LOCAL_DATE: + /* FIXME: if you support date */ + local_date.year = 2006; /* AD */ + local_date.month = 4; /* Jan=1..Dec=12 */ + local_date.day = 11; /* 1..31 */ + local_date.wday = 0; /* 1=Mon..7=Sun */ + apdu_len = encode_tagged_date(&apdu[0], &local_date); + break; + case PROP_DAYLIGHT_SAVINGS_STATUS: + /* FIXME: if you support time/date */ + apdu_len = encode_tagged_boolean(&apdu[0], false); + break; + case 9600: + apdu_len = encode_tagged_unsigned(&apdu[0], RS485_Get_Baud_Rate()); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + if (!Device_Valid_Object_Instance_Number(wp_data->object_instance)) { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + /* decode the some of the request */ + len = bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + switch (wp_data->object_property) { + case PROP_OBJECT_IDENTIFIER: + if (value.tag == BACNET_APPLICATION_TAG_OBJECT_ID) { + if ((value.type.Object_Id.type == OBJECT_DEVICE) && + (Device_Set_Object_Instance_Number(value.type. + Object_Id.instance))) { + /* we could send an I-Am broadcast to let the world know */ + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_MAX_INFO_FRAMES: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if (value.type.Unsigned_Int <= 255) { + dlmstp_set_max_info_frames(value.type.Unsigned_Int); + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_MAX_MASTER: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if ((value.type.Unsigned_Int > 0) && + (value.type.Unsigned_Int <= 127)) { + dlmstp_set_max_master(value.type.Unsigned_Int); + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_OBJECT_NAME: + if (value.tag == BACNET_APPLICATION_TAG_CHARACTER_STRING) { + uint8_t encoding; + size_t len; + + encoding = + characterstring_encoding(&value.type.Character_String); + len = characterstring_length(&value.type.Character_String); + if (encoding == CHARACTER_ANSI_X34) { + if (len <= 20) { + /* FIXME: set the name */ + /* Display_Set_Name( + characterstring_value(&value.type.Character_String)); */ + /* FIXME: All the object names in a device must be unique. + Disallow setting the Device Object Name to any objects in + the device. */ + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_CHARACTER_SET_NOT_SUPPORTED; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case 9600: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if (value.type.Unsigned_Int > 115200) { + RS485_Set_Baud_Rate(value.type.Unsigned_Int); + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} diff --git a/bacnet-stack/ports/pic18f6720/dlmstp.c b/bacnet-stack/ports/pic18f6720/dlmstp.c index e69de29b..0c045024 100644 --- a/bacnet-stack/ports/pic18f6720/dlmstp.c +++ b/bacnet-stack/ports/pic18f6720/dlmstp.c @@ -0,0 +1,325 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ +#include +#include +#include +#include +#if PRINT_ENABLED +#include +#endif +#include "bacdef.h" +#include "mstp.h" +#include "dlmstp.h" +#include "rs485.h" +#include "npdu.h" + +/* Number of MS/TP Packets Rx/Tx */ +uint16_t MSTP_Packets = 0; + +/* receive buffer */ +#pragma udata MSTP_RxData +static DLMSTP_PACKET Receive_Buffer; +/* temp buffer for NPDU insertion */ +/* local MS/TP port data - shared with RS-485 */ +#pragma udata MSTP_PortData +volatile struct mstp_port_struct_t MSTP_Port; +#pragma udata + +#define INCREMENT_AND_LIMIT_UINT16(x) {if (x < 0xFFFF) x++;} + +void dlmstp_millisecond_timer(void) +{ + INCREMENT_AND_LIMIT_UINT16(MSTP_Port.SilenceTimer); +} + +void dlmstp_reinit(void) +{ + RS485_Reinit(); + dlmstp_set_my_address(DEFAULT_MAC_ADDRESS); + dlmstp_set_max_info_frames(DEFAULT_MAX_INFO_FRAMES); + dlmstp_set_max_master(DEFAULT_MAX_MASTER); +} + +void dlmstp_init(void) +{ + uint8_t data; + + /* initialize buffer */ + Receive_Buffer.ready = false; + Receive_Buffer.pdu_len = 0; + /* initialize hardware */ + RS485_Initialize(); + MSTP_Port.InputBuffer = &Receive_Buffer.pdu[0]; + MSTP_Init(&MSTP_Port); + /* FIXME: implement your data storage */ + data = 64; /* I2C_Read_Byte( + EEPROM_DEVICE_ADDRESS, + EEPROM_MSTP_MAC_ADDR); */ + if (data <= 127) + MSTP_Port.This_Station = data; + else + dlmstp_set_my_address(DEFAULT_MAC_ADDRESS); + /* FIXME: implement your data storage */ + data = 127; /* I2C_Read_Byte( + EEPROM_DEVICE_ADDRESS, + EEPROM_MSTP_MAX_MASTER_ADDR); */ + if ((data <= 127) && (data >= MSTP_Port.This_Station)) + MSTP_Port.Nmax_master = data; + else + dlmstp_set_max_master(DEFAULT_MAX_MASTER); + /* FIXME: implement your data storage */ + data = 1; + /* I2C_Read_Byte( + EEPROM_DEVICE_ADDRESS, + EEPROM_MSTP_MAX_INFO_FRAMES_ADDR); */ + if (data >= 1) + MSTP_Port.Nmax_info_frames = data; + else + dlmstp_set_max_info_frames(DEFAULT_MAX_INFO_FRAMES); +} + +void dlmstp_cleanup(void) +{ + /* nothing to do for static buffers */ +} + +/* returns number of bytes sent on success, zero on failure */ +int dlmstp_send_pdu(BACNET_ADDRESS * dest, /* destination address */ + BACNET_NPDU_DATA * npdu_data, /* network information */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len) +{ /* number of bytes of data */ + int bytes_sent = 0; + unsigned npdu_len = 0; + uint8_t frame_type = 0; + uint8_t destination = 0; /* destination address */ + BACNET_ADDRESS src; + unsigned i = 0; /* loop counter */ + + if (MSTP_Port.TxReady == false) { + if (npdu_data->data_expecting_reply) + MSTP_Port.TxFrameType = FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY; + else + MSTP_Port.TxFrameType = + FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY; + + /* load destination MAC address */ + if (dest && dest->mac_len == 1) { + destination = dest->mac[0]; + } else { + return -2; + } + dlmstp_get_my_address(&src); + if ((8 /* header len */ + pdu_len) > MAX_MPDU) { + return -4; + } + bytes_sent = MSTP_Create_Frame( + (uint8_t *) & MSTP_Port.TxBuffer[0], + sizeof(MSTP_Port.TxBuffer), + MSTP_Port.TxFrameType, + destination, MSTP_Port.This_Station, pdu, pdu_len); + MSTP_Port.TxLength = bytes_sent; + MSTP_Port.TxReady = true; + MSTP_Packets++; + } + + return bytes_sent; +} + +void dlmstp_task(void) +{ + uint8_t bytes_remaining; + bool received_frame; + + /* only do receive state machine while we don't have a frame */ + if ((MSTP_Port.ReceivedValidFrame == false) && + (MSTP_Port.ReceivedInvalidFrame == false)) { + do { + bytes_remaining = RS485_Check_UART_Data(&MSTP_Port); + MSTP_Receive_Frame_FSM(&MSTP_Port); + received_frame = MSTP_Port.ReceivedValidFrame || + MSTP_Port.ReceivedInvalidFrame; + if (received_frame) + break; + } while (bytes_remaining); + } + /* only do master state machine while rx is idle */ + if (MSTP_Port.receive_state == MSTP_RECEIVE_STATE_IDLE) { + while (MSTP_Master_Node_FSM(&MSTP_Port)) { + }; + } + /* see if there is a packet available, and a place + to put the reply (if necessary) and process it */ + if (Receive_Buffer.ready && !MSTP_Port.TxReady) { + if (Receive_Buffer.pdu_len) { + MSTP_Packets++; + npdu_handler(&Receive_Buffer.address, + &Receive_Buffer.pdu[0], Receive_Buffer.pdu_len); + } + Receive_Buffer.ready = false; + } + + return; +} + +void dlmstp_fill_bacnet_address(BACNET_ADDRESS * src, uint8_t mstp_address) +{ + int i = 0; + + if (mstp_address == MSTP_BROADCAST_ADDRESS) { + /* mac_len = 0 if broadcast address */ + src->mac_len = 0; + src->mac[0] = 0; + } else { + src->mac_len = 1; + src->mac[0] = mstp_address; + } + /* fill with 0's starting with index 1; index 0 filled above */ + for (i = 1; i < MAX_MAC_LEN; i++) { + src->mac[i] = 0; + } + src->net = 0; + src->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + src->adr[i] = 0; + } +} + +/* for the MS/TP state machine to use for putting received data */ +uint16_t dlmstp_put_receive(uint8_t src, /* source MS/TP address */ + uint8_t * pdu, /* PDU data */ + uint16_t pdu_len) +{ /* amount of PDU data */ + /* PDU is already in the Receive_Buffer */ + dlmstp_fill_bacnet_address(&Receive_Buffer.address, src); + Receive_Buffer.pdu_len = pdu_len; + Receive_Buffer.ready = true; +} + +void dlmstp_set_my_address(uint8_t mac_address) +{ + /* Master Nodes can only have address 0-127 */ + if (mac_address <= 127) { + MSTP_Port.This_Station = mac_address; + /* FIXME: implement your data storage */ + /* I2C_Write_Byte( + EEPROM_DEVICE_ADDRESS, + mac_address, + EEPROM_MSTP_MAC_ADDR); */ + if (mac_address > MSTP_Port.Nmax_master) + dlmstp_set_max_master(mac_address); + } + + return; +} + +uint8_t dlmstp_my_address(void) +{ + return MSTP_Port.This_Station; +} + +/* This parameter represents the value of the Max_Info_Frames property of */ +/* the node's Device object. The value of Max_Info_Frames specifies the */ +/* maximum number of information frames the node may send before it must */ +/* pass the token. Max_Info_Frames may have different values on different */ +/* nodes. This may be used to allocate more or less of the available link */ +/* bandwidth to particular nodes. If Max_Info_Frames is not writable in a */ +/* node, its value shall be 1. */ +void dlmstp_set_max_info_frames(uint8_t max_info_frames) +{ + if (max_info_frames >= 1) { + MSTP_Port.Nmax_info_frames = max_info_frames; + /* FIXME: implement your data storage */ + /* I2C_Write_Byte( + EEPROM_DEVICE_ADDRESS, + (uint8_t)max_info_frames, + EEPROM_MSTP_MAX_INFO_FRAMES_ADDR); */ + } + + return; +} + +unsigned dlmstp_max_info_frames(void) +{ + return MSTP_Port.Nmax_info_frames; +} + +/* This parameter represents the value of the Max_Master property of the */ +/* node's Device object. The value of Max_Master specifies the highest */ +/* allowable address for master nodes. The value of Max_Master shall be */ +/* less than or equal to 127. If Max_Master is not writable in a node, */ +/* its value shall be 127. */ +void dlmstp_set_max_master(uint8_t max_master) +{ + if (max_master <= 127) { + if (MSTP_Port.This_Station <= max_master) { + MSTP_Port.Nmax_master = max_master; + /* FIXME: implement your data storage */ + /* I2C_Write_Byte( + EEPROM_DEVICE_ADDRESS, + max_master, + EEPROM_MSTP_MAX_MASTER_ADDR); */ + } + } + + return; +} + +uint8_t dlmstp_max_master(void) +{ + return MSTP_Port.Nmax_master; +} + +void dlmstp_get_my_address(BACNET_ADDRESS * my_address) +{ + int i = 0; /* counter */ + + my_address->mac_len = 1; + my_address->mac[0] = MSTP_Port.This_Station; + my_address->net = 0; /* local only, no routing */ + my_address->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + my_address->adr[i] = 0; + } + + return; +} + +void dlmstp_get_broadcast_address(BACNET_ADDRESS * dest) +{ /* destination address */ + int i = 0; /* counter */ + + if (dest) { + dest->mac_len = 1; + dest->mac[0] = MSTP_BROADCAST_ADDRESS; + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; /* always zero when DNET is broadcast */ + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = 0; + } + } + + return; +} diff --git a/bacnet-stack/ports/pic18f6720/dlmstp.h b/bacnet-stack/ports/pic18f6720/dlmstp.h index e69de29b..1343d7a8 100644 --- a/bacnet-stack/ports/pic18f6720/dlmstp.h +++ b/bacnet-stack/ports/pic18f6720/dlmstp.h @@ -0,0 +1,110 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307 + USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef DLMSTP_H +#define DLMSTP_H + +#include +#include +#include +#include "bacdef.h" +#include "npdu.h" + +/* defines specific to MS/TP */ +#define MAX_HEADER (2+1+1+1+2+1+2+1) +#define MAX_MPDU (MAX_HEADER+MAX_PDU) + +typedef struct dlmstp_packet { + bool ready; /* true if ready to be sent or received */ + BACNET_ADDRESS address; /* source address */ + uint8_t frame_type; /* type of message */ + unsigned pdu_len; /* packet length */ + uint8_t pdu[MAX_MPDU]; /* packet */ +} DLMSTP_PACKET; + +/* number of MS/TP tx/rx packets */ +extern uint16_t MSTP_Packets; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + void dlmstp_reinit(void); + void dlmstp_init(void); + void dlmstp_cleanup(void); + void dlmstp_millisecond_timer(void); + void dlmstp_task(void); + + /* returns number of bytes sent on success, negative on failure */ + int dlmstp_send_pdu(BACNET_ADDRESS * dest, /* destination address */ + BACNET_NPDU_DATA * npdu_data, /* network information */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len); /* number of bytes of data */ + + /* This parameter represents the value of the Max_Info_Frames property of */ + /* the node's Device object. The value of Max_Info_Frames specifies the */ + /* maximum number of information frames the node may send before it must */ + /* pass the token. Max_Info_Frames may have different values on different */ + /* nodes. This may be used to allocate more or less of the available link */ + /* bandwidth to particular nodes. If Max_Info_Frames is not writable in a */ + /* node, its value shall be 1. */ + void dlmstp_set_max_info_frames(uint8_t max_info_frames); + unsigned dlmstp_max_info_frames(void); + + /* This parameter represents the value of the Max_Master property of the */ + /* node's Device object. The value of Max_Master specifies the highest */ + /* allowable address for master nodes. The value of Max_Master shall be */ + /* less than or equal to 127. If Max_Master is not writable in a node, */ + /* its value shall be 127. */ + void dlmstp_set_max_master(uint8_t max_master); + uint8_t dlmstp_max_master(void); + + /* MAC address for MS/TP */ + void dlmstp_set_my_address(uint8_t my_address); + uint8_t dlmstp_my_address(void); + + /* BACnet address used in datalink */ + void dlmstp_get_my_address(BACNET_ADDRESS * my_address); + void dlmstp_get_broadcast_address(BACNET_ADDRESS * dest); /* destination address */ + + /* MS/TP state machine functions */ + uint16_t dlmstp_put_receive(uint8_t src, /* source MS/TP address */ + uint8_t * pdu, /* PDU data */ + uint16_t pdu_len); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/ports/pic18f6720/h_rp.c b/bacnet-stack/ports/pic18f6720/h_rp.c index e69de29b..01df2e86 100644 --- a/bacnet-stack/ports/pic18f6720/h_rp.c +++ b/bacnet-stack/ports/pic18f6720/h_rp.c @@ -0,0 +1,198 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "bacerror.h" +#include "apdu.h" +#include "npdu.h" +#include "abort.h" +#include "rp.h" +/* demo objects */ +#include "device.h" +#include "ai.h" +#include "av.h" +#include "bi.h" +#include "bv.h" + +/* note: this is a minimal handler. See h_rp.c for another */ + +static uint8_t Temp_Buf[MAX_APDU] = { 0 }; + +void handler_read_property(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data) +{ + BACNET_READ_PROPERTY_DATA data; + int len = 0; + int pdu_len = 0; + BACNET_NPDU_DATA npdu_data; + bool send = false; + bool error = false; + int bytes_sent = 0; + BACNET_ERROR_CLASS error_class = ERROR_CLASS_OBJECT; + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + BACNET_ADDRESS my_address; + + len = rp_decode_service_request(service_request, service_len, &data); + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], src, + &my_address, &npdu_data); + if (len < 0) { + /* bad decoding - send an abort */ + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, ABORT_REASON_OTHER, true); + } else if (service_data->segmented_message) { + /* we don't support segmentation - send an abort */ + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true); + } else { + /* most cases will be error */ + error = true; + switch (data.object_type) { + case OBJECT_DEVICE: + /* FIXME: probably need a length limitation sent with encode */ + if (data.object_instance == Device_Object_Instance_Number()) { + len = Device_Encode_Property_APDU(&Temp_Buf[0], + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); + error = false; + } + } + break; + case OBJECT_ANALOG_INPUT: + if (Analog_Input_Valid_Instance(data.object_instance)) { + len = Analog_Input_Encode_Property_APDU(&Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); + error = false; + } + } + break; + case OBJECT_BINARY_INPUT: + if (Binary_Input_Valid_Instance(data.object_instance)) { + len = Binary_Input_Encode_Property_APDU(&Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); + error = false; + } + } + break; + case OBJECT_BINARY_VALUE: + if (Binary_Value_Valid_Instance(data.object_instance)) { + len = Binary_Value_Encode_Property_APDU(&Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); + error = false; + } + } + break; + case OBJECT_ANALOG_VALUE: + if (Analog_Value_Valid_Instance(data.object_instance)) { + len = Analog_Value_Encode_Property_APDU(&Temp_Buf[0], + data.object_instance, + data.object_property, + data.array_index, &error_class, &error_code); + if (len >= 0) { + /* encode the APDU portion of the packet */ + data.application_data = &Temp_Buf[0]; + data.application_data_len = len; + /* FIXME: probably need a length limitation sent with encode */ + len = + rp_ack_encode_apdu(&Handler_Transmit_Buffer + [pdu_len], service_data->invoke_id, &data); + error = false; + } + } + break; + default: + break; + } + } + if (error) { + switch (len) { + /* BACnet APDU too small to fit data, so proper response is Abort */ + case -2: + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true); + break; + case -1: + default: + len = bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_READ_PROPERTY, error_class, error_code); + break; + } + } + pdu_len += len; + bytes_sent = datalink_send_pdu(src, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); + + return; +} diff --git a/bacnet-stack/ports/pic18f6720/h_wp.c b/bacnet-stack/ports/pic18f6720/h_wp.c index e69de29b..4ecc82b5 100644 --- a/bacnet-stack/ports/pic18f6720/h_wp.c +++ b/bacnet-stack/ports/pic18f6720/h_wp.c @@ -0,0 +1,192 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#include +#include +#include +#include +#include +#include "config.h" +#include "txbuf.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "apdu.h" +#include "npdu.h" +#include "abort.h" +#include "wp.h" +/* demo objects */ +#include "device.h" +#include "ai.h" +#include "av.h" +#include "bi.h" +#include "bv.h" + +/* too big to reside on stack frame for PIC */ +static BACNET_WRITE_PROPERTY_DATA wp_data; + +void handler_write_property(uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, BACNET_CONFIRMED_SERVICE_DATA * service_data) +{ + int len = 0; + int pdu_len = 0; + BACNET_NPDU_DATA npdu_data; + BACNET_ERROR_CLASS error_class = ERROR_CLASS_OBJECT; + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + int bytes_sent = 0; + BACNET_ADDRESS my_address; + + /* decode the service request only */ + len = wp_decode_service_request(service_request, + service_len, &wp_data); + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], src, + &my_address, &npdu_data); +#if PRINT_ENABLED + fprintf(stderr, "Received Write-Property Request!\n"); + if (len > 0) + fprintf(stderr, "type=%u instance=%u property=%u index=%d\n", + wp_data.object_type, + wp_data.object_instance, + wp_data.object_property, wp_data.array_index); + else + fprintf(stderr, "Unable to decode Write-Property Request!\n"); +#endif + /* bad decoding or something we didn't understand - send an abort */ + if (len <= 0) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, ABORT_REASON_OTHER, true); +#if PRINT_ENABLED + fprintf(stderr, "Sending Abort!\n"); +#endif + } else if (service_data->segmented_message) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true); +#if PRINT_ENABLED + fprintf(stderr, "Sending Abort!\n"); +#endif + } else { + switch (wp_data.object_type) { + case OBJECT_DEVICE: + if (Device_Write_Property(&wp_data, &error_class, &error_code)) { + len = + encode_simple_ack(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Write Property Simple Ack for Device!\n"); +#endif + } else { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY, error_class, + error_code); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Write Property Error for Device!\n"); +#endif + } + break; + case OBJECT_ANALOG_INPUT: + case OBJECT_BINARY_INPUT: + error_class = ERROR_CLASS_PROPERTY; + error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, SERVICE_CONFIRMED_WRITE_PROPERTY, + error_class, error_code); +#if PRINT_ENABLED + fprintf(stderr, "Sending Write Access Error!\n"); +#endif + break; + case OBJECT_BINARY_VALUE: + if (Binary_Value_Write_Property(&wp_data, &error_class, + &error_code)) { + len = + encode_simple_ack(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Write Property Simple Ack for BV!\n"); +#endif + } else { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY, error_class, + error_code); +#if PRINT_ENABLED + fprintf(stderr, "Sending Write Access Error for BV!\n"); +#endif + } + break; + case OBJECT_ANALOG_VALUE: + if (Analog_Value_Write_Property(&wp_data, &error_class, + &error_code)) { + len = + encode_simple_ack(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY); +#if PRINT_ENABLED + fprintf(stderr, + "Sending Write Property Simple Ack for AV!\n"); +#endif + } else { + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_WRITE_PROPERTY, error_class, + error_code); +#if PRINT_ENABLED + fprintf(stderr, "Sending Write Access Error for AV!\n"); +#endif + } + break; + default: + len = + bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, SERVICE_CONFIRMED_WRITE_PROPERTY, + error_class, error_code); +#if PRINT_ENABLED + fprintf(stderr, "Sending Unknown Object Error!\n"); +#endif + break; + } + } + pdu_len += len; + bytes_sent = datalink_send_pdu(src, &npdu_data, + &Handler_Transmit_Buffer[0], pdu_len); +#if PRINT_ENABLED + if (bytes_sent <= 0) + fprintf(stderr, "Failed to send PDU (%s)!\n", strerror(errno)); +#endif + + return; +} diff --git a/bacnet-stack/ports/pic18f6720/hardware.h b/bacnet-stack/ports/pic18f6720/hardware.h index e69de29b..a60744d5 100644 --- a/bacnet-stack/ports/pic18f6720/hardware.h +++ b/bacnet-stack/ports/pic18f6720/hardware.h @@ -0,0 +1,271 @@ +/************************************************************************** +* +* Copyright (C) 2007 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 HARDWARE_H +#define HARDWARE_H + +#include +#include +#include + +/* PORTA.0 Photocell Input PORTA.1 LED Row6 PORTA.2 LED Row5 PORTA.3 LED + * Row4 PORTA.4 Square Wave input from RTC PORTA.5 LCD RW PORTB.0 Zero + * Cross PORTB.1 USB RXF# PORTB.2 USB TXE# PORTB.3 Keypad Row Enable + * (74HC373 Output Control) PORTB.4 Keypad Row Gate (74HC373 Gate) + * PORTB.5 Switch Input Latch & Keypad Column Gate (74HC373 Gate) PORTB.6 + * ICD connection PORTB.7 ICD connection PORTC.0 Pilot Latch PORTC.1 + * Pilot Output Enable (low) PORTC.2 Piezo PORTC.3 I2C clock PORTC.4 I2C + * data PORTC.5 RS232 enable (low) PORTC.6 RS232 Tx PORTC.7 RS232 Rx + * PORTD.0 Data bus PORTD.1 Data bus PORTD.2 Data bus PORTD.3 Data bus + * PORTD.4 Data bus PORTD.5 Data bus PORTD.6 Data bus PORTD.7 Data bus + * PORTE.0 USB RD PORTE.1 USB WR PORTE.2 LCD RS PORTE.3 485 transmit + * enable PORTE.4 Relay data latch PORTE.5 Switch Input Clock PORTE.6 + * Switch Input High/Low PORTE.7 Switch Input Data PORTF.0 LED Row2 + * PORTF.1 LED Row1 PORTF.2 LED Col5 PORTF.3 LED Col4 PORTF.4 LED Col3 + * PORTF.5 LED Col2 PORTF.6 LED Col1 PORTF.7 LED Col0 PORTG.0 485 receive + * enable PORTG.1 485 Tx PORTG.2 485 Rx PORTG.3 LCD E PORTG.4 LED Row0 */ +#define LCD_BUSY PORTDbits.RD7 +#define LCD_E PORTGbits.RG3 +#define LCD_RW PORTAbits.RA5 +#define LCD_RS PORTEbits.RE2 +#define LCD_DATA PORTD +#define LCD_TRIS TRISD + +#define PILOT_LATCH PORTCbits.RC0 +#define PILOT_ENABLE PORTCbits.RC1 +#define PILOT_PORT PORTD +#define PILOT_PORT_TRIS TRISD + +#define PIEZO PORTCbits.RC2 +#define PIEZO_ON() TRISCbits.TRISC2 = 0 +#define PIEZO_OFF() TRISCbits.TRISC2 = 1 + +#define RS232_ENABLE PORTCbits.RC5 + +#define RS485_TX_ENABLE PORTEbits.RE3 +#define RS485_RX_DISABLE PORTGbits.RG0 + +#define SWITCH_LOAD PORTBbits.RB5 +#define SWITCH_CLK PORTEbits.RE5 +#define SWITCH_COM PORTEbits.RE6 +#define SWITCH_DATA PORTEbits.RE7 + +#define LEDPORT PORTF +#define LEDTRIS TRISF +#define LED_ROW1 PORTGbits.RG4 +#define LED_ROW2 PORTFbits.RF1 +#define LED_ROW3 PORTFbits.RF0 +#define LED_ROW4 PORTAbits.RA3 +#define LED_ROW5 PORTAbits.RA2 +#define LED_ROW6 PORTAbits.RA1 + +#define RELAY_PORT PORTD +#define RELAY_PORT_TRIS TRISD +#define RELAY_LATCH PORTEbits.RE4 + +#define KEYPAD_DATA PORTD +#define KEYPAD_TRIS TRISD +#define KEYPAD_COL_LATCH PORTBbits.RB5 +#define KEYPAD_ROW_ENABLE PORTBbits.RB3 +#define KEYPAD_ROW_LATCH PORTBbits.RB4 +#define KEYPAD_ROW1 0 b00000001 +#define KEYPAD_ROW2 0 b00000010 +#define KEYPAD_ROW3 0 b00000100 +#define KEYPAD_ROW4 0 b00001000 +#define KEYPAD_ROW5 0 b00010000 +#define KEYPAD_ROW6 0 b00100000 + +#define USB_RD_EMPTY PORTBbits.RB1 +#define USB_WR_FULL PORTBbits.RB2 +#define USB_RD PORTEbits.RE0 +#define USB_WR PORTEbits.RE1 +#define USB_PORT PORTD +#define USB_PORT_TRIS TRISD + +#define ZERO_CROSS PORTBbits.RB0 + +#define PORT_A_TRIS_MASK 0x11 /* 0b00010001 */ +#define PORT_B_TRIS_MASK 0xC7 /* 0b11000111 */ +#define PORT_C_TRIS_MASK 0x9C /* 0b10011100 */ +#define PORT_D_TRIS_MASK 0xFF /* 0b11111111 */ +#define PORT_E_TRIS_MASK 0x88 /* 0b10001000 */ +#define PORT_F_TRIS_MASK 0x00 /* 0b00000000 */ +#define PORT_G_TRIS_MASK 0x04 /* 0b00000100 */ + +#define TURN_OFF_COMPARATORS() CMCON = 0x07 + +#define SHORT_BEEP 50 +#define LONG_BEEP 250 + +#define CLICK() Hardware_Sound_Piezo(SHORT_BEEP); +#define BEEP() Hardware_Sound_Piezo(LONG_BEEP); + +typedef union { + struct { + uint8_t:1; + uint8_t:1; + uint8_t Thursday:1; + uint8_t Wednesday:1; + uint8_t Tuesday:1; + uint8_t Monday:1; + uint8_t Program:1; + uint8_t Run:1; + + uint8_t:1; + uint8_t:1; + uint8_t Input1:1; + uint8_t Input2:1; + uint8_t Input3:1; + uint8_t Input4:1; + uint8_t Input5:1; + uint8_t Input6:1; + + uint8_t:1; + uint8_t:1; + uint8_t:1; + uint8_t Input7:1; + uint8_t:1; + uint8_t:1; + uint8_t Input8:1; + uint8_t Photocell:1; + + uint8_t:1; + uint8_t:1; + uint8_t:1; + uint8_t:1; + uint8_t:1; + uint8_t:1; + uint8_t Remote:1; + uint8_t Relay8:1; + + uint8_t:1; + uint8_t:1; + uint8_t:1; + uint8_t Relay7:1; + uint8_t Relay6:1; + uint8_t Relay5:1; + uint8_t Relay4:1; + uint8_t Relay3:1; + + uint8_t:1; + uint8_t:1; + uint8_t Relay2:1; + uint8_t Relay1:1; + uint8_t Holiday:1; + uint8_t Sunday:1; + uint8_t Saturday:1; + uint8_t Friday:1; + }; + struct { + uint8_t row1; + uint8_t row2; + uint8_t row3; + uint8_t row4; + uint8_t row5; + uint8_t row6; + }; +} LED_REGS; + +union SWITCH_REGS { + struct { + uint8_t All_On:1; + uint8_t All_Off:1; + uint8_t Addr:4; + uint8_t Pilot_Fault:1; + uint8_t Master:1; + }; + uint8_t Sw_Byte; +}; + +enum INT_STATE { INT_DISABLED, INT_ENABLED, INT_RESTORE }; + +#define RESTART_WDT() { _asm CLRWDT _endasm } + +/* ************************************************************************* + define ENABLE_GLOBAL_INT() INTCONbits.GIE = 1 £ + #define DISABLE_GLOBAL_INT() INTCONbits.GIE = 0 £ + #define ENABLE_PERIPHERAL_INT() INTCONbits.PEIE = 1 £ + #define DISABLE_PERIPHERAL_INT() INTCONbits.PEIE = 0 + *************************************************************************** */ +#define ENABLE_HIGH_INT() INTCONbits.GIE = 1 +#define DISABLE_HIGH_INT() INTCONbits.GIE = 0 + +#define ENABLE_LOW_INT() INTCONbits.PEIE = 1 +#define DISABLE_LOW_INT() INTCONbits.PEIE = 0 + +#define ENABLE_TIMER0_INT() INTCONbits.TMR0IE = 1 +#define DISABLE_TIMER0_INT() INTCONbits.TMR0IE = 0 + +#define ENABLE_TIMER2_INT() PIE1bits.TMR2IE = 1 +#define DISABLE_TIMER2_INT() PIE1bits.TMR2IE = 0 + +#define ENABLE_TIMER4_INT() PIE3bits.TMR4IE = 1 +#define DISABLE_TIMER4_INT() PIE3bits.TMR4IE = 0 + +#define ENABLE_CCP2_INT() PIE2bits.CCP2IE = 1 +#define DISABLE_CCP2_INT() PIE2bits.CCP2IE = 0 + +#define ENABLE_CCP1_INT() PIE1bits.CCP1IE = 1 +#define DISABLE_CCP1_INT() PIE1bits.CCP1IE = 0 + +#define ENABLE_ABUS_INT() PIE1bits.SSPIE = 1 +#define DISABLE_ABUS_INT() PIE1bits.SSPIE = 0 +#define CLEAR_ABUS_FLAG() PIR1bits.SSPIF = 0 + +#define SETUP_CCP1(x) CCP1CON = x +#define SETUP_CCP2(x) CCP2CON = x + +#define DISABLE_RX_INT() PIE1bits.RCIE = 0 +#define ENABLE_RX_INT() PIE1bits.RCIE = 1 + +#define DISABLE_TX_INT() PIE1bits.TXIE = 0 +#define ENABLE_TX_INT() PIE1bits.TXIE = 1 + +#if CLOCKSPEED == 20 +#define DELAY_US(x) { _asm \ + MOVLW x \ + LOOP: \ + NOP \ + NOP \ + DECFSZ WREG, 1, 0 \ + BRA LOOP \ + _endasm } +#endif + +#define setup_timer4(mode, period, postscale) \ + T4CON = (mode | (postscale - 1) << 3); \ + PR4 = period + +#define setup_timer2(mode, period, postscale) \ + T2CON = (mode | (postscale - 1) << 3); \ + PR2 = period + + +/* Global Vars */ +extern volatile LED_REGS LEDS; +extern volatile LED_REGS Blink; +extern uint8_t Piezo_Timer; +extern volatile bool DataPortLocked; + +#endif /* HARDWARE_H */ diff --git a/bacnet-stack/ports/pic18f6720/isr.c b/bacnet-stack/ports/pic18f6720/isr.c index e69de29b..5a84fc46 100644 --- a/bacnet-stack/ports/pic18f6720/isr.c +++ b/bacnet-stack/ports/pic18f6720/isr.c @@ -0,0 +1,190 @@ +/************************************************************************** +* +* Copyright (C) 2007 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. +* +*********************************************************************/ +#include +#include "stdint.h" +#include "hardware.h" +#include "rs485.h" +#include "dlmstp.h" + +/* from main.c */ +extern volatile uint8_t Milliseconds; + +void InterruptHandlerHigh(void); +void InterruptHandlerLow(void); +void Interrupt_Timer2(void); +void Interrupt_Timer3(void); +void Interrupt_Timer4(void); +void Interrupt_USART_Rx(void); +void Interrupt_USART_Tx(void); +void Interrupt_CCP2(void); +void INT0_Interrupt(void); + +#pragma code InterruptVectorHigh = 0x08 +void InterruptVectorHigh(void) +{ + /* jump to interrupt routine */ +_asm goto InterruptHandlerHigh _endasm} +#pragma code +#pragma code InterruptVectorLow = 0x18 +void InterruptVectorLow(void) +{ + /* jump to interrupt routine */ +_asm goto InterruptHandlerLow _endasm} +#pragma code +#pragma interrupt InterruptHandlerHigh +void InterruptHandlerHigh(void) +{ +#if 0 + /* check for USART Rx int */ + if ((PIR1bits.RCIF) && (PIE1bits.RCIE)) { + if ((RCSTA1bits.FERR) || (RCSTA1bits.OERR)) { + Comstat.Rx_Bufferoverrun = TRUE; + PIE1bits.RC1IE = 0; /* Disable Interrupt on receipt */ + } else if (Comstat.Rx_Bytes++ < RX_BUFFER_SIZE - 1) { + Rx_Buffer[Comstat.RxHead++] = RCREG1; + + /* Stick a Null on the end to let us use str functions on our + * buffer */ + Rx_Buffer[Comstat.RxHead] = 0; + } else { + Comstat.Rx_Bufferoverrun = TRUE; + PIE1bits.RC1IE = 0; /* Disable Interrupt on receipt */ + } + } +#endif + + /* check for timer0 int */ + if ((INTCONbits.TMR0IF) && (INTCONbits.TMR0IE)) { + INTCONbits.TMR0IF = 0; + } +} + +#pragma interruptlow InterruptHandlerLow save = PROD, section(".tmpdata"), TABLAT, TBLPTR, section \ + ("MATH_DATA") + +void InterruptHandlerLow(void) +{ + /* check for timer2 int */ + if ((PIR1bits.TMR2IF) && (PIE1bits.TMR2IE)) { + PIR1bits.TMR2IF = 0; + Interrupt_Timer2(); + } + + /* check for timer3 int */ + if ((PIR2bits.TMR3IF) && (PIE2bits.TMR3IE)) { + PIR2bits.TMR3IF = 0; + Interrupt_Timer3(); + } + + /* check for timer4 int */ + if ((PIR3bits.TMR4IF) && (PIE3bits.TMR4IE)) { + PIR3bits.TMR4IF = 0; + dlmstp_millisecond_timer(); + Interrupt_Timer4(); + } + + /* check for compare int */ + if ((PIR2bits.CCP2IF) && (PIE2bits.CCP2IE)) { + PIR2bits.CCP2IF = 0; + Interrupt_CCP2(); + } + + /* check for USART Tx int */ + if ((PIR3bits.TX2IF) && (PIE3bits.TX2IE)) { + RS485_Interrupt_Tx(); + } + + /* check for USART Rx int */ + if ((PIR3bits.RC2IF) && (PIE3bits.RC2IE)) { + RS485_Interrupt_Rx(); + } + +/* Unused Interrupts + //check for timer1 int + if ((PIR1bits.TMR1IF) && (PIE1bits.TMR1IE)) + { + PIR1bits.TMR1IF = 0; + Interrupt_Timer1(); + } + + //check for compare int + if ((PIR1bits.CCP1IF) && (PIE1bits.CCP1IE)) + { + PIR1bits.CCP1IF = 0; + Interrupt_CCP1(); + } + + //check for compare int + if ((PIR3bits.CCP3IF) && (PIE3bits.CCP3IE)) + { + PIR3bits.CCP3IF = 0; + Interrupt_CCP3(); + } + + //check for compare int + if ((PIR3bits.CCP4IF) && (PIE3bits.CCP4IE)) + { + PIR3bits.CCP4IF = 0; + + Interrupt_CCP4(); + } + + //check for AD int + if ((PIR1bits.ADIF) && (PIE1bits.ADIE)) + { + PIR1bits.ADIF = 0; + Interrupt_ADC(); + } + + //check for MSSP int + if ((PIR1bits.SSPIF) && (PIE1bits.SSPIE)) + { + PIR1bits.SSPIF = 0; + Interrupt_SSP(); + } + +*/ +} + +void Interrupt_Timer2(void) +{ +} + +void Interrupt_Timer3(void) +{ +} + +/* Timer4 is set to go off every 1ms. This is our system tick */ +void Interrupt_Timer4(void) +{ + /* Milisecond is our system tick */ + if (Milliseconds < 0xFF) + ++Milliseconds; +} + +void Interrupt_CCP2(void) +{ + +} diff --git a/bacnet-stack/ports/pic18f6720/main.c b/bacnet-stack/ports/pic18f6720/main.c index e69de29b..abaeb5be 100644 --- a/bacnet-stack/ports/pic18f6720/main.c +++ b/bacnet-stack/ports/pic18f6720/main.c @@ -0,0 +1,294 @@ +/************************************************************************** +* +* Copyright (C) 2007 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. +* +*********************************************************************/ + +#include +#include +#include /* for memmove */ +#include +#include +#include +#include "stdint.h" +#include "hardware.h" +/* BACnet */ +#include "apdu.h" +#include "datalink.h" +#include "dcc.h" +#include "handlers.h" +#include "iam.h" +#include "txbuf.h" + +/* chip configuration data */ +/* define this to enable ICD */ +/* #define USE_ICD */ + +/* Configuration Bits */ +#pragma config OSC = HS, OSCS = OFF +#pragma config PWRT = ON +#pragma config BOR = ON, BORV = 27 +#pragma config CCP2MUX = ON +#pragma config STVR = ON +#pragma config LVP = OFF +#pragma config CP0 = OFF +#pragma config CP1 = OFF +#pragma config CP2 = OFF +#pragma config CP3 = OFF +#pragma config CP4 = OFF +#pragma config CP5 = OFF +#pragma config CP6 = OFF +#pragma config CP7 = OFF +#pragma config CPB = OFF +#pragma config CPD = OFF +#pragma config WRT0 = OFF +#pragma config WRT1 = OFF +#pragma config WRT2 = OFF +#pragma config WRT3 = OFF +#pragma config WRT4 = OFF +#pragma config WRT5 = OFF +#pragma config WRT6 = OFF +#pragma config WRT7 = OFF +#pragma config WRTB = OFF +#pragma config WRTC = OFF +#pragma config WRTD = OFF +#pragma config EBTR0 = OFF +#pragma config EBTR1 = OFF +#pragma config EBTR2 = OFF +#pragma config EBTR3 = OFF +#pragma config EBTR4 = OFF +#pragma config EBTR5 = OFF +#pragma config EBTR6 = OFF +#pragma config EBTR7 = OFF +#pragma config EBTRB = OFF + +#ifdef USE_ICD +#pragma config WDT = OFF, WDTPS = 128 +#pragma config DEBUG = ON +#else +#pragma config WDT = ON, WDTPS = 128 +#pragma config DEBUG = OFF +#endif /* USE_ICD */ + +volatile uint8_t Milliseconds = 0; +volatile uint8_t Zero_Cross_Timeout = 0; + +static void BACnet_Service_Handlers_Init(void) +{ + /* we need to handle who-is to support dynamic device binding */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, + handler_who_is); + /* Set the handlers for any confirmed services that we support. */ + /* We must implement read property - it's required! */ + apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY, + handler_read_property); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_REINITIALIZE_DEVICE, + handler_reinitialize_device); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, + handler_write_property); +#if 0 + apdu_set_unconfirmed_handler + (SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION, + handler_timesync_utc); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION, + handler_timesync); +#endif + /* handle communication so we can shutup when asked */ + apdu_set_confirmed_handler + (SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + handler_device_communication_control); +} + +void Reinitialize(void) +{ + uint8_t i; + char name = 0; + + _asm reset _endasm return; +} + +void Global_Int(enum INT_STATE state) +{ + static uint8_t intstate = 0; + + switch (state) { + case INT_DISABLED: + intstate >>= 2; + intstate |= (INTCON & 0xC0); + break; + case INT_ENABLED: + INTCONbits.GIE = 1; + INTCONbits.PEIE = 1; + intstate <<= 2; + break; + case INT_RESTORE: + INTCON |= (intstate & 0xC0); + intstate <<= 2; + break; + default: + break; + } +} + +void Hardware_Initialize(void) +{ + /* PORTA.0 Input - Photocell PORTA.1 Output - LED Row6 PORTA.2 Output + * - LED Row5 PORTA.3 Output - LED Row4 PORTA.4 Input - Square Wave + * input from RTC PORTA.5 Output - LCD RW */ + TRISA = 0xD1; + + /* PORTB.0 Input - Zero Cross PORTB.1 Input - USB RXF# PORTB.2 Input + * USB TXE# PORTB.3 Output - Keypad Row Enable (74HC373 Output Control) + * PORTB.4 Output Keypad Row Gate (74HC373 Gate) PORTB.5 Output Switch + * Input Latch & Keypad Column Gate (74HC373 Gate) PORTB.6 Input - ICD + * connection PORTB.7 Input - ICD connection */ + TRISB = 0xC7; + + /* PORTC.0 Output - Pilot Latch PORTC.1 Output - Pilot Output Enable + * (low) PORTC.2 I/O - Piezo PORTC.3 Input - I2C clock PORTC.4 Input + * I2C data PORTC.5 Output RS232 enable (low) PORTC.6 Output - RS232 Tx + * PORTC.7 Input - RS232 Rx */ + TRISC = 0x9C; + + /* PORTD.0 I/O - Data bus PORTD.1 I/O - Data bus PORTD.2 I/O - Data + * bus PORTD.3 I/O - Data bus PORTD.4 I/O - Data bus PORTD.5 I/O - Data + * bus PORTD.6 I/O - Data bus PORTD.7 I/O - Data bus */ + TRISD = 0xFF; + + /* PORTE.0 Input - USB RD PORTE.1 Input - USB WR PORTE.2 Output - LCD + * RS PORTE.3 Output - 485 transmit enable PORTE.4 Output - Relay data + * latch PORTE.5 Output Switch Input Clock PORTE.6 Output - Switch + * Input High/Low PORTE.7 Input Switch Input Data */ + TRISE = 0x83; + + /* PORTF.0 Output - LED Row2 PORTF.1 Output - LED Row1 PORTF.2 Output + * - LED Col5 PORTF.3 Output - LED Col4 PORTF.4 Output - LED Col3 + * PORTF.5 Output - LED Col2 PORTF.6 Output - LED Col1 PORTF.7 Output + * LED Col0 */ + TRISF = 0x00; + + /* PORTG.0 Output - 485 receive enable PORTG.1 Output - 485 Tx PORTG.2 + * Input 485 Rx PORTG.3 Output - LCD E PORTG.4 Output - LED Row0 */ + TRISG = 0xE6; + + /* The initial state of the keypad enables and latches */ + KEYPAD_ROW_ENABLE = 1; + KEYPAD_ROW_LATCH = 0; + KEYPAD_COL_LATCH = 1; + + RELAY_LATCH = 0; + + /* Setup to read the switch inputs */ + SWITCH_COM = 1; + + /* Enable the RS232 transmitter */ + RS232_ENABLE = 0; + + /* Turn all leds off. These are the hardware pins */ + LED_ROW1 = 1; + LED_ROW2 = 1; + LED_ROW3 = 1; + LED_ROW4 = 1; + LED_ROW5 = 1; + LED_ROW6 = 1; + LEDPORT = 0x03; + + /* The initial values for the signals to the LCD */ + LCD_E = 1; + LCD_RW = 1; + LCD_RS = 1; + + /* The following gives us a PWM frequency of 1.990KHz with a 50% duty + * cycle It also serves to multiplex the LEDs. */ + PIEZO_OFF(); + CCPR1L = 0x4E; + CCP1CON = 0x2F; + setup_timer2(6, 156, 2); + PIE1bits.TMR2IE = 1; + + /* We will use Timer4 as our system tick timer. Our system tick is set + * to 1ms. Hold off on enabling the int. */ + setup_timer4(5, 250, 5); + + /* Setup our interrupt priorities */ + RCONbits.IPEN = 1; + IPR1 = 0; + IPR2 = 0; + IPR3 = 0; + + /* Setup TMR0 to be high priority */ + INTCON2 = 0xFC; + INTCON3 = 0; + + /* USART 1 high priority */ + IPR1bits.RC1IP = 1; + IPR1bits.TX1IP = 1; + + /* Finally enable our ints */ + Global_Int(INT_ENABLED); +} + +void Initialize_Variables(void) +{ + /* Check to see if we need to initialize our eeproms */ + ENABLE_TIMER4_INT(); + /* interrupts must be enabled before we read our inputs */ + Global_Int(INT_ENABLED); + BACnet_Service_Handlers_Init(); + dlmstp_init(); + /* Start our time from now */ + Milliseconds = 0; +} + +void MainTasks(void) +{ + static uint16_t millisecond_counter = 0; + /* Handle our millisecond counters */ + while (Milliseconds) { + millisecond_counter++; + --Milliseconds; + } + /* Handle our seconds counters */ + if (millisecond_counter > 1000) { + millisecond_counter -= 1000; + dcc_timer_seconds(1); + } +} + +void main(void) +{ + RCONbits.NOT_POR = 1; + RCONbits.NOT_RI = 1; + Hardware_Initialize(); + Initialize_Variables(); + /* Handle anything that needs to be done on powerup */ + /* Greet the BACnet world! */ + iam_send(&Handler_Transmit_Buffer[0]); + /* Main loop */ + while (TRUE) { + RESTART_WDT(); + dlmstp_task(); + MainTasks(); + Global_Int(INT_ENABLED); + ENABLE_TIMER4_INT(); + } +} diff --git a/bacnet-stack/ports/pic18f6720/mstp.c b/bacnet-stack/ports/pic18f6720/mstp.c index e69de29b..e586c210 100644 --- a/bacnet-stack/ports/pic18f6720/mstp.c +++ b/bacnet-stack/ports/pic18f6720/mstp.c @@ -0,0 +1,1706 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2003 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307 + USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +/* This clause describes a Master-Slave/Token-Passing (MS/TP) data link */ +/* protocol, which provides the same services to the network layer as */ +/* ISO 8802-2 Logical Link Control. It uses services provided by the */ +/* EIA-485 physical layer. Relevant clauses of EIA-485 are deemed to be */ +/* included in this standard by reference. The following hardware is assumed: */ +/* (a) A UART (Universal Asynchronous Receiver/Transmitter) capable of */ +/* transmitting and receiving eight data bits with one stop bit */ +/* and no parity. */ +/* (b) An EIA-485 transceiver whose driver may be disabled. */ +/* (c) A timer with a resolution of five milliseconds or less */ + +#include +#include +#if PRINT_ENABLED +#include +#endif +#include "mstp.h" +#include "bytes.h" +#include "crc.h" +#include "rs485.h" + +/* debug print statements */ +#if PRINT_ENABLED +#define PRINT_ENABLED_RECEIVE 0 +#define PRINT_ENABLED_RECEIVE_DATA 1 +#define PRINT_ENABLED_MASTER 0 +#else +#define PRINT_ENABLED_RECEIVE 0 +#define PRINT_ENABLED_RECEIVE_DATA 0 +#define PRINT_ENABLED_MASTER 0 +#endif + +/* MS/TP Frame Format */ +/* All frames are of the following format: */ +/* */ +/* Preamble: two octet preamble: X`55', X`FF' */ +/* Frame Type: one octet */ +/* Destination Address: one octet address */ +/* Source Address: one octet address */ +/* Length: two octets, most significant octet first, of the Data field */ +/* Header CRC: one octet */ +/* Data: (present only if Length is non-zero) */ +/* Data CRC: (present only if Length is non-zero) two octets, */ +/* least significant octet first */ +/* (pad): (optional) at most one octet of padding: X'FF' */ + +/* The number of tokens received or used before a Poll For Master cycle */ +/* is executed: 50. */ +#define Npoll 50 + +/* The number of retries on sending Token: 1. */ +#define Nretry_token 1 + +/* The minimum number of DataAvailable or ReceiveError events that must be */ +/* seen by a receiving node in order to declare the line "active": 4. */ +#define Nmin_octets 4 + +/* The minimum time without a DataAvailable or ReceiveError event within */ +/* a frame before a receiving node may discard the frame: 60 bit times. */ +/* (Implementations may use larger values for this timeout, */ +/* not to exceed 100 milliseconds.) */ +/* At 9600 baud, 60 bit times would be about 6.25 milliseconds */ +/* const uint16_t Tframe_abort = 1 + ((1000 * 60) / 9600); */ +#define Tframe_abort 30 + +/* The maximum idle time a sending node may allow to elapse between octets */ +/* of a frame the node is transmitting: 20 bit times. */ +#define Tframe_gap 20 + +/* The time without a DataAvailable or ReceiveError event before declaration */ +/* of loss of token: 500 milliseconds. */ +#define Tno_token 500 + +/* The maximum time after the end of the stop bit of the final */ +/* octet of a transmitted frame before a node must disable its */ +/* EIA-485 driver: 15 bit times. */ +#define Tpostdrive 15 + +/* The maximum time a node may wait after reception of a frame that expects */ +/* a reply before sending the first octet of a reply or Reply Postponed */ +/* frame: 250 milliseconds. */ +/* note: we always send a reply postponed since a message other than + the reply may be in the transmit queue */ +#define Treply_delay 10 + +/* The minimum time without a DataAvailable or ReceiveError event */ +/* that a node must wait for a station to begin replying to a */ +/* confirmed request: 255 milliseconds. (Implementations may use */ +/* larger values for this timeout, not to exceed 300 milliseconds.) */ +#define Treply_timeout 255 + +/* Repeater turnoff delay. The duration of a continuous logical one state */ +/* at the active input port of an MS/TP repeater after which the repeater */ +/* will enter the IDLE state: 29 bit times < Troff < 40 bit times. */ +#define Troff 30 + +/* The width of the time slot within which a node may generate a token: */ +/* 10 milliseconds. */ +#define Tslot 10 + +/* The maximum time a node may wait after reception of the token or */ +/* a Poll For Master frame before sending the first octet of a frame: */ +/* 15 milliseconds. */ +#define Tusage_delay 15 + +/* The minimum time without a DataAvailable or ReceiveError event that a */ +/* node must wait for a remote node to begin using a token or replying to */ +/* a Poll For Master frame: 20 milliseconds. (Implementations may use */ +/* larger values for this timeout, not to exceed 100 milliseconds.) */ +#define Tusage_timeout 20 + +/* we need to be able to increment without rolling over */ +#define INCREMENT_AND_LIMIT_UINT8(x) {if (x < 0xFF) x++;} + +bool MSTP_Line_Active(volatile struct mstp_port_struct_t *mstp_port) +{ + return (mstp_port->EventCount > Nmin_octets); +} + +unsigned MSTP_Create_Frame(uint8_t * buffer, /* where frame is loaded */ + unsigned buffer_len, /* amount of space available */ + uint8_t frame_type, /* type of frame to send - see defines */ + uint8_t destination, /* destination address */ + uint8_t source, /* source address */ + uint8_t * data, /* any data to be sent - may be null */ + unsigned data_len) +{ /* number of bytes of data (up to 501) */ + uint8_t crc8 = 0xFF; /* used to calculate the crc value */ + uint16_t crc16 = 0xFFFF; /* used to calculate the crc value */ + unsigned index = 0; /* used to load the data portion of the frame */ + + /* not enough to do a header */ + if (buffer_len < 8) + return 0; + + buffer[0] = 0x55; + buffer[1] = 0xFF; + buffer[2] = frame_type; + crc8 = CRC_Calc_Header(buffer[2], crc8); + buffer[3] = destination; + crc8 = CRC_Calc_Header(buffer[3], crc8); + buffer[4] = source; + crc8 = CRC_Calc_Header(buffer[4], crc8); + buffer[5] = data_len / 256; + crc8 = CRC_Calc_Header(buffer[5], crc8); + buffer[6] = data_len % 256; + crc8 = CRC_Calc_Header(buffer[6], crc8); + buffer[7] = ~crc8; + + index = 8; + while (data_len && data && (index < buffer_len)) { + buffer[index] = *data; + crc16 = CRC_Calc_Data(buffer[index], crc16); + data++; + index++; + data_len--; + } + /* append the data CRC if necessary */ + if (index > 8) { + if ((index + 2) <= buffer_len) { + crc16 = ~crc16; + buffer[index] = LO_BYTE(crc16); + index++; + buffer[index] = HI_BYTE(crc16); + index++; + } else + return 0; + } + + return index; /* returns the frame length */ +} + +void MSTP_Create_And_Send_Frame(volatile struct mstp_port_struct_t *mstp_port, /* port to send from */ + uint8_t frame_type, /* type of frame to send - see defines */ + uint8_t destination, /* destination address */ + uint8_t source, /* source address */ + uint8_t * data, /* any data to be sent - may be null */ + unsigned data_len) +{ /* number of bytes of data (up to 501) */ + uint8_t buffer[MAX_MPDU] = { 0 }; /* buffer for sending */ + uint16_t len = 0; /* number of bytes to send */ + + len = (uint16_t) MSTP_Create_Frame(&buffer[0], /* where frame is loaded */ + sizeof(buffer), /* amount of space available */ + frame_type, /* type of frame to send - see defines */ + destination, /* destination address */ + source, /* source address */ + data, /* any data to be sent - may be null */ + data_len); /* number of bytes of data (up to 501) */ + + RS485_Send_Frame(mstp_port, &buffer[0], len); + /* FIXME: be sure to reset SilenceTimer after each octet is sent! */ +} + +#if PRINT_ENABLED_RECEIVE +char *mstp_receive_state_text(int state) +{ + char *text = "unknown"; + + switch (state) { + case MSTP_RECEIVE_STATE_IDLE: + text = "IDLE"; + break; + case MSTP_RECEIVE_STATE_PREAMBLE: + text = "PREAMBLE"; + break; + case MSTP_RECEIVE_STATE_HEADER: + text = "HEADER"; + break; + case MSTP_RECEIVE_STATE_HEADER_CRC: + text = "HEADER_CRC"; + break; + case MSTP_RECEIVE_STATE_DATA: + text = "DATA"; + break; + default: + break; + } + + return text; +} +#endif + +void MSTP_Receive_Frame_FSM(volatile struct mstp_port_struct_t *mstp_port) +{ +#if PRINT_ENABLED_RECEIVE_DATA + static MSTP_RECEIVE_STATE receive_state = MSTP_RECEIVE_STATE_IDLE; +#endif +#if PRINT_ENABLED_RECEIVE + fprintf(stderr, + "MSTP Rx: State=%s Data=%02X hCRC=%02X Index=%u EC=%u DateLen=%u Silence=%u\n", + mstp_receive_state_text(mstp_port->receive_state), + mstp_port->DataRegister, mstp_port->HeaderCRC, mstp_port->Index, + mstp_port->EventCount, mstp_port->DataLength, + mstp_port->SilenceTimer); +#endif + switch (mstp_port->receive_state) { + /* In the IDLE state, the node waits for the beginning of a frame. */ + case MSTP_RECEIVE_STATE_IDLE: + /* EatAnError */ + if (mstp_port->ReceiveError == true) { + mstp_port->ReceiveError = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } else if (mstp_port->DataAvailable == true) { +#if PRINT_ENABLED_RECEIVE_DATA + fprintf(stderr, "MSTP Rx: %02X ", mstp_port->DataRegister); +#endif + /* Preamble1 */ + if (mstp_port->DataRegister == 0x55) { + mstp_port->DataAvailable = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* receive the remainder of the frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_PREAMBLE; + } + /* EatAnOctet */ + else { +#if PRINT_ENABLED_RECEIVE_DATA + fprintf(stderr, "\n"); +#endif + mstp_port->DataAvailable = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + } + break; + /* In the PREAMBLE state, the node waits for the second octet of the preamble. */ + case MSTP_RECEIVE_STATE_PREAMBLE: + /* Timeout */ + if (mstp_port->SilenceTimer > Tframe_abort) { + /* a correct preamble has not been received */ + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + /* Error */ + else if (mstp_port->ReceiveError == true) { + mstp_port->ReceiveError = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } else if (mstp_port->DataAvailable == true) { +#if PRINT_ENABLED_RECEIVE_DATA + fprintf(stderr, "%02X ", mstp_port->DataRegister); +#endif + /* Preamble2 */ + if (mstp_port->DataRegister == 0xFF) { + mstp_port->DataAvailable = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + mstp_port->Index = 0; + mstp_port->HeaderCRC = 0xFF; + /* receive the remainder of the frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_HEADER; + } + /* ignore RepeatedPreamble1 */ + else if (mstp_port->DataRegister == 0x55) { + mstp_port->DataAvailable = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* wait for the second preamble octet. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_PREAMBLE; + } + /* NotPreamble */ + else { + mstp_port->DataAvailable = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + } + break; + /* In the HEADER state, the node waits for the fixed message header. */ + case MSTP_RECEIVE_STATE_HEADER: + /* Timeout */ + if (mstp_port->SilenceTimer > Tframe_abort) { + /* indicate that an error has occurred during the reception of a frame */ + mstp_port->ReceivedInvalidFrame = true; + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + /* Error */ + else if (mstp_port->ReceiveError == true) { + mstp_port->ReceiveError = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* indicate that an error has occurred during the reception of a frame */ + mstp_port->ReceivedInvalidFrame = true; + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } else if (mstp_port->DataAvailable == true) { +#if PRINT_ENABLED_RECEIVE_DATA + fprintf(stderr, "%02X ", mstp_port->DataRegister); +#endif + /* FrameType */ + if (mstp_port->Index == 0) { + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + mstp_port->HeaderCRC = + CRC_Calc_Header(mstp_port->DataRegister, + mstp_port->HeaderCRC); + mstp_port->FrameType = mstp_port->DataRegister; + mstp_port->DataAvailable = false; + mstp_port->Index = 1; + mstp_port->receive_state = MSTP_RECEIVE_STATE_HEADER; + } + /* Destination */ + else if (mstp_port->Index == 1) { + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + mstp_port->HeaderCRC = + CRC_Calc_Header(mstp_port->DataRegister, + mstp_port->HeaderCRC); + mstp_port->DestinationAddress = mstp_port->DataRegister; + mstp_port->DataAvailable = false; + mstp_port->Index = 2; + mstp_port->receive_state = MSTP_RECEIVE_STATE_HEADER; + } + /* Source */ + else if (mstp_port->Index == 2) { + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + mstp_port->HeaderCRC = + CRC_Calc_Header(mstp_port->DataRegister, + mstp_port->HeaderCRC); + mstp_port->SourceAddress = mstp_port->DataRegister; + mstp_port->DataAvailable = false; + mstp_port->Index = 3; + mstp_port->receive_state = MSTP_RECEIVE_STATE_HEADER; + } + /* Length1 */ + else if (mstp_port->Index == 3) { + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + mstp_port->HeaderCRC = + CRC_Calc_Header(mstp_port->DataRegister, + mstp_port->HeaderCRC); + mstp_port->DataLength = mstp_port->DataRegister * 256; + mstp_port->DataAvailable = false; + mstp_port->Index = 4; + mstp_port->receive_state = MSTP_RECEIVE_STATE_HEADER; + } + /* Length2 */ + else if (mstp_port->Index == 4) { + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + mstp_port->HeaderCRC = + CRC_Calc_Header(mstp_port->DataRegister, + mstp_port->HeaderCRC); + mstp_port->DataLength += mstp_port->DataRegister; + mstp_port->DataAvailable = false; + mstp_port->Index = 5; + mstp_port->receive_state = MSTP_RECEIVE_STATE_HEADER; + } + /* HeaderCRC */ + else if (mstp_port->Index == 5) { + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + mstp_port->HeaderCRC = + CRC_Calc_Header(mstp_port->DataRegister, + mstp_port->HeaderCRC); + mstp_port->DataAvailable = false; + /* don't wait for next state - do it here */ + /* MSTP_RECEIVE_STATE_HEADER_CRC */ + if (mstp_port->HeaderCRC != 0x55) { + /* BadCRC */ + /* indicate that an error has occurred during the reception of a frame */ + mstp_port->ReceivedInvalidFrame = true; + /* wait for the start of the next frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } else { + if ((mstp_port->DestinationAddress == + mstp_port->This_Station) + || (mstp_port->DestinationAddress == + MSTP_BROADCAST_ADDRESS)) { + /* FrameTooLong */ + if (mstp_port->DataLength > MAX_MPDU) { + /* indicate that a frame with an illegal or */ + /* unacceptable data length has been received */ + mstp_port->ReceivedInvalidFrame = true; + /* wait for the start of the next frame. */ + mstp_port->receive_state = + MSTP_RECEIVE_STATE_IDLE; + } + /* NoData */ + else if (mstp_port->DataLength == 0) { + /* indicate that a frame with no data has been received */ + mstp_port->ReceivedValidFrame = true; + /* wait for the start of the next frame. */ + mstp_port->receive_state = + MSTP_RECEIVE_STATE_IDLE; + } + /* Data */ + else { + mstp_port->Index = 0; + mstp_port->DataCRC = 0xFFFF; + /* receive the data portion of the frame. */ + mstp_port->receive_state = + MSTP_RECEIVE_STATE_DATA; + } + } + /* NotForUs */ + else { + /* wait for the start of the next frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + } + + + } + /* not per MS/TP standard, but it is a case not covered */ + else { + mstp_port->ReceiveError = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* indicate that an error has occurred during */ + /* the reception of a frame */ + mstp_port->ReceivedInvalidFrame = true; + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + } + break; + /* In the HEADER_CRC state, the node validates the CRC on the fixed */ + /* message header. */ + case MSTP_RECEIVE_STATE_HEADER_CRC: + break; + /* In the DATA state, the node waits for the data portion of a frame. */ + case MSTP_RECEIVE_STATE_DATA: + /* Timeout */ + if (mstp_port->SilenceTimer > Tframe_abort) { + /* indicate that an error has occurred during the reception of a frame */ + mstp_port->ReceivedInvalidFrame = true; + /* wait for the start of the next frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + /* Error */ + else if (mstp_port->ReceiveError == true) { + mstp_port->ReceiveError = false; + mstp_port->SilenceTimer = 0; + /* indicate that an error has occurred during the reception of a frame */ + mstp_port->ReceivedInvalidFrame = true; + /* wait for the start of the next frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } else if (mstp_port->DataAvailable == true) { +#if PRINT_ENABLED_RECEIVE_DATA + fprintf(stderr, "%02X ", mstp_port->DataRegister); +#endif + /* DataOctet */ + if (mstp_port->Index < mstp_port->DataLength) { + mstp_port->DataCRC = CRC_Calc_Data(mstp_port->DataRegister, + mstp_port->DataCRC); + mstp_port->InputBuffer[mstp_port->Index] = + mstp_port->DataRegister; + mstp_port->Index++; + mstp_port->receive_state = MSTP_RECEIVE_STATE_DATA; + } + /* CRC1 */ + else if (mstp_port->Index == mstp_port->DataLength) { + mstp_port->DataCRC = CRC_Calc_Data(mstp_port->DataRegister, + mstp_port->DataCRC); + mstp_port->Index++; + mstp_port->receive_state = MSTP_RECEIVE_STATE_DATA; + } + /* CRC2 */ + else if (mstp_port->Index == (mstp_port->DataLength + 1)) { + mstp_port->DataCRC = CRC_Calc_Data(mstp_port->DataRegister, + mstp_port->DataCRC); + /* STATE DATA CRC - no need for new state */ + /* indicate the complete reception of a valid frame */ + if (mstp_port->DataCRC == 0xF0B8) + mstp_port->ReceivedValidFrame = true; + else + mstp_port->ReceivedInvalidFrame = true; + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + mstp_port->DataAvailable = false; + mstp_port->SilenceTimer = 0; + } + break; + default: + /* shouldn't get here - but if we do... */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + break; + } +#if PRINT_ENABLED_RECEIVE_DATA + if ((receive_state != MSTP_RECEIVE_STATE_IDLE) && + (mstp_port->receive_state == MSTP_RECEIVE_STATE_IDLE)) { + fprintf(stderr, "\n"); + fflush(stderr); + } + receive_state = mstp_port->receive_state; +#endif + + return; +} + +#if PRINT_ENABLED +char *mstp_master_state_text(int state) +{ + char *text = "unknown"; + + switch (state) { + case MSTP_MASTER_STATE_INITIALIZE: + text = "INITIALIZE"; + break; + case MSTP_MASTER_STATE_IDLE: + text = "IDLE"; + break; + case MSTP_MASTER_STATE_USE_TOKEN: + text = "USE_TOKEN"; + break; + case MSTP_MASTER_STATE_WAIT_FOR_REPLY: + text = "WAIT_FOR_REPLY"; + break; + case MSTP_MASTER_STATE_DONE_WITH_TOKEN: + text = "IDLE"; + break; + case MSTP_MASTER_STATE_PASS_TOKEN: + text = "DONE_WITH_TOKEN"; + break; + case MSTP_MASTER_STATE_NO_TOKEN: + text = "NO_TOKEN"; + break; + case MSTP_MASTER_STATE_POLL_FOR_MASTER: + text = "POLL_FOR_MASTER"; + break; + case MSTP_MASTER_STATE_ANSWER_DATA_REQUEST: + text = "ANSWER_DATA_REQUEST"; + break; + default: + break; + } + + return text; +} +#endif + +#if PRINT_ENABLED +char *mstp_frame_type_text(int type) +{ + char *text = "unknown"; + + switch (type) { + case FRAME_TYPE_TOKEN: + text = "TOKEN"; + break; + case FRAME_TYPE_POLL_FOR_MASTER: + text = "POLL_FOR_MASTER"; + break; + case FRAME_TYPE_REPLY_TO_POLL_FOR_MASTER: + text = "REPLY_TO_POLL_FOR_MASTER"; + break; + case FRAME_TYPE_TEST_REQUEST: + text = "TEST_REQUEST"; + break; + case FRAME_TYPE_TEST_RESPONSE: + text = "TEST_RESPONSE"; + break; + case FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY: + text = "BACNET_DATA_EXPECTING_REPLY"; + break; + case FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY: + text = "BACNET_DATA_NOT_EXPECTING_REPLY"; + break; + case FRAME_TYPE_REPLY_POSTPONED: + text = "REPLY_POSTPONED"; + break; + default: + if ((type >= FRAME_TYPE_PROPRIETARY_MIN) && + (type <= FRAME_TYPE_PROPRIETARY_MAX)) + text = "PROPRIETARY"; + break; + } + + return text; +} +#endif + +/* returns true if we need to transition immediately */ +bool MSTP_Master_Node_FSM(volatile struct mstp_port_struct_t * mstp_port) +{ + int mtu_len = 0; + int frame_type = 0; + uint8_t next_poll_station = 0; + uint8_t next_this_station = 0; + uint8_t next_next_station = 0; + uint16_t my_timeout = 10, ns_timeout = 0; + /* transition immediately to the next state */ + bool transition_now = false; +#if PRINT_ENABLED_MASTER + static MSTP_MASTER_STATE master_state = MSTP_MASTER_STATE_INITIALIZE; +#endif + + /* some calculations that several states need */ + next_poll_station = (mstp_port->Poll_Station + 1) % + (mstp_port->Nmax_master + 1); + next_this_station = (mstp_port->This_Station + 1) % + (mstp_port->Nmax_master + 1); + next_next_station = (mstp_port->Next_Station + 1) % + (mstp_port->Nmax_master + 1); +#if PRINT_ENABLED_MASTER + if (mstp_port->master_state != master_state) { + master_state = mstp_port->master_state; + fprintf(stderr, + "MSTP: TS=%02X[%02X] NS=%02X[%02X] PS=%02X[%02X] EC=%u TC=%u ST=%u %s\n", + mstp_port->This_Station, + next_this_station, + mstp_port->Next_Station, + next_next_station, + mstp_port->Poll_Station, + next_poll_station, + mstp_port->EventCount, + mstp_port->TokenCount, + mstp_port->SilenceTimer, + mstp_master_state_text(mstp_port->master_state)); + } +#endif + + switch (mstp_port->master_state) { + case MSTP_MASTER_STATE_INITIALIZE: + /* DoneInitializing */ + /* indicate that the next station is unknown */ + mstp_port->Next_Station = mstp_port->This_Station; + mstp_port->Poll_Station = mstp_port->This_Station; + /* cause a Poll For Master to be sent when this node first */ + /* receives the token */ + mstp_port->TokenCount = Npoll; + mstp_port->SoleMaster = false; + mstp_port->ReceivedValidFrame = false; + mstp_port->ReceivedInvalidFrame = false; + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + transition_now = true; + break; + /* In the IDLE state, the node waits for a frame. */ + case MSTP_MASTER_STATE_IDLE: + /* LostToken */ + if (mstp_port->SilenceTimer >= Tno_token) { + /* assume that the token has been lost */ + mstp_port->EventCount = 0; /* Addendum 135-2004d-8 */ + mstp_port->master_state = MSTP_MASTER_STATE_NO_TOKEN; + transition_now = true; + } + /* ReceivedInvalidFrame */ + else if (mstp_port->ReceivedInvalidFrame == true) { + /* invalid frame was received */ + mstp_port->ReceivedInvalidFrame = false; + /* wait for the next frame - remain in IDLE */ + } else if (mstp_port->ReceivedValidFrame == true) { +#if PRINT_ENABLED_MASTER + fprintf(stderr, + "MSTP: ReceivedValidFrame Src=%02X Dest=%02X DataLen=%u FC=%u ST=%u Type=%s\n", + mstp_port->SourceAddress, + mstp_port->DestinationAddress, + mstp_port->DataLength, + mstp_port->FrameCount, + mstp_port->SilenceTimer, + mstp_frame_type_text(mstp_port->FrameType)); +#endif + /* destined for me! */ + if ((mstp_port->DestinationAddress == + mstp_port->This_Station) || + (mstp_port->DestinationAddress == + MSTP_BROADCAST_ADDRESS)) { + switch (mstp_port->FrameType) { + /* ReceivedToken */ + case FRAME_TYPE_TOKEN: + /* tokens can't be broadcast */ + if (mstp_port->DestinationAddress == + MSTP_BROADCAST_ADDRESS) + break; + mstp_port->ReceivedValidFrame = false; + mstp_port->FrameCount = 0; + mstp_port->SoleMaster = false; + mstp_port->master_state = MSTP_MASTER_STATE_USE_TOKEN; + transition_now = true; + break; + /* ReceivedPFM */ + case FRAME_TYPE_POLL_FOR_MASTER: + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_REPLY_TO_POLL_FOR_MASTER, + mstp_port->SourceAddress, mstp_port->This_Station, + NULL, 0); + break; + case FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY: + /* indicate successful reception to the higher layers */ + dlmstp_put_receive(mstp_port->SourceAddress, + (uint8_t *) & mstp_port->InputBuffer[0], + mstp_port->DataLength); + break; + case FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY: + /*mstp_port->ReplyPostponedTimer = 0; */ + /* indicate successful reception to the higher layers */ + dlmstp_put_receive(mstp_port->SourceAddress, + (uint8_t *) & mstp_port->InputBuffer[0], + mstp_port->DataLength); + /* broadcast DER just remains IDLE */ + if (mstp_port->DestinationAddress != + MSTP_BROADCAST_ADDRESS) { + mstp_port->master_state = + MSTP_MASTER_STATE_ANSWER_DATA_REQUEST; + } + break; + case FRAME_TYPE_TEST_REQUEST: + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_TEST_RESPONSE, + mstp_port->SourceAddress, mstp_port->This_Station, + NULL, 0); + break; + case FRAME_TYPE_TEST_RESPONSE: + default: + break; + } + } + mstp_port->ReceivedValidFrame = false; + } + break; + /* In the USE_TOKEN state, the node is allowed to send one or */ + /* more data frames. These may be BACnet Data frames or */ + /* proprietary frames. */ + case MSTP_MASTER_STATE_USE_TOKEN: + if (!mstp_port->TxReady) { + /* NothingToSend */ + mstp_port->FrameCount = mstp_port->Nmax_info_frames; + mstp_port->master_state = MSTP_MASTER_STATE_DONE_WITH_TOKEN; + transition_now = true; + } else if (mstp_port->SilenceTimer > Tusage_delay) { + /* if we missed our timing deadline, another token will be sent */ + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + } else { + /* don't send it if we are too late in getting out */ + uint8_t destination = mstp_port->TxBuffer[3]; + RS485_Send_Frame(mstp_port, + (uint8_t *) & mstp_port->TxBuffer[0], mstp_port->TxLength); + mstp_port->FrameCount++; + switch (mstp_port->TxFrameType) { + case FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY: + /* SendAndWait */ + if (destination == MSTP_BROADCAST_ADDRESS) + mstp_port->master_state = + MSTP_MASTER_STATE_DONE_WITH_TOKEN; + else + mstp_port->master_state = + MSTP_MASTER_STATE_WAIT_FOR_REPLY; + break; + case FRAME_TYPE_TEST_REQUEST: + mstp_port->master_state = MSTP_MASTER_STATE_WAIT_FOR_REPLY; + break; + case FRAME_TYPE_TEST_RESPONSE: + case FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY: + default: + /* SendNoWait */ + mstp_port->master_state = + MSTP_MASTER_STATE_DONE_WITH_TOKEN; + break; + } + mstp_port->TxReady = false; + } + break; + /* In the WAIT_FOR_REPLY state, the node waits for */ + /* a reply from another node. */ + case MSTP_MASTER_STATE_WAIT_FOR_REPLY: + if (mstp_port->SilenceTimer >= Treply_timeout) { + /* ReplyTimeout */ + /* assume that the request has failed */ + mstp_port->FrameCount = mstp_port->Nmax_info_frames; + mstp_port->master_state = MSTP_MASTER_STATE_DONE_WITH_TOKEN; + /* Any retry of the data frame shall await the next entry */ + /* to the USE_TOKEN state. (Because of the length of the timeout, */ + /* this transition will cause the token to be passed regardless */ + /* of the initial value of FrameCount.) */ + transition_now = true; + } else { + if (mstp_port->ReceivedInvalidFrame == true) { + /* InvalidFrame */ + /* error in frame reception */ + mstp_port->ReceivedInvalidFrame = false; + mstp_port->master_state = + MSTP_MASTER_STATE_DONE_WITH_TOKEN; + transition_now = true; + } else if (mstp_port->ReceivedValidFrame == true) { + if (mstp_port->DestinationAddress == + mstp_port->This_Station) { + switch (mstp_port->TxFrameType) { + case FRAME_TYPE_REPLY_POSTPONED: + /* ReceivedReplyPostponed */ + mstp_port->master_state = + MSTP_MASTER_STATE_DONE_WITH_TOKEN; + break; + case FRAME_TYPE_TEST_RESPONSE: + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + break; + case FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY: + /* ReceivedReply */ + /* or a proprietary type that indicates a reply */ + /* indicate successful reception to the higher layers */ + dlmstp_put_receive(mstp_port->SourceAddress, /* source MS/TP address */ + (uint8_t *) & mstp_port->InputBuffer[0], + mstp_port->DataLength); + mstp_port->master_state = + MSTP_MASTER_STATE_DONE_WITH_TOKEN; + break; + default: + /* if proprietary frame was expected, you might + need to transition to DONE WITH TOKEN */ + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + break; + } + } else { + /* ReceivedUnexpectedFrame */ + /* an unexpected frame was received */ + /* This may indicate the presence of multiple tokens. */ + /* Synchronize with the network. */ + /* This action drops the token. */ + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + } + mstp_port->ReceivedValidFrame = false; + transition_now = true; + } + } + break; + /* The DONE_WITH_TOKEN state either sends another data frame, */ + /* passes the token, or initiates a Poll For Master cycle. */ + case MSTP_MASTER_STATE_DONE_WITH_TOKEN: + /* SendAnotherFrame */ + if (mstp_port->FrameCount < mstp_port->Nmax_info_frames) { + /* then this node may send another information frame */ + /* before passing the token. */ + mstp_port->master_state = MSTP_MASTER_STATE_USE_TOKEN; + transition_now = true; + } + /* Npoll changed in Errata SSPC-135-2004 */ + else if (mstp_port->TokenCount < (Npoll - 1)) { + if ((mstp_port->SoleMaster == true) && + (mstp_port->Next_Station != next_this_station)) { + /* SoleMaster */ + /* there are no other known master nodes to */ + /* which the token may be sent (true master-slave operation). */ + mstp_port->FrameCount = 0; + mstp_port->TokenCount++; + mstp_port->master_state = MSTP_MASTER_STATE_USE_TOKEN; + transition_now = true; + } else { + /* SendToken */ + /* Npoll changed in Errata SSPC-135-2004 */ + /* The comparison of NS and TS+1 eliminates the Poll For Master */ + /* if there are no addresses between TS and NS, since there is no */ + /* address at which a new master node may be found in that case. */ + mstp_port->TokenCount++; + /* transmit a Token frame to NS */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_TOKEN, + mstp_port->Next_Station, + mstp_port->This_Station, NULL, 0); + mstp_port->RetryCount = 0; + mstp_port->EventCount = 0; + mstp_port->master_state = MSTP_MASTER_STATE_PASS_TOKEN; + } + } else if (next_poll_station == mstp_port->Next_Station) { + if (mstp_port->SoleMaster == true) { + /* SoleMasterRestartMaintenancePFM */ + mstp_port->Poll_Station = next_poll_station; + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_POLL_FOR_MASTER, mstp_port->Poll_Station, + mstp_port->This_Station, NULL, 0); + /* no known successor node */ + mstp_port->Next_Station = mstp_port->This_Station; + mstp_port->RetryCount = 0; + mstp_port->TokenCount = 1; /* changed in Errata SSPC-135-2004 */ + /* mstp_port->EventCount = 0; removed in Addendum 135-2004d-8 */ + /* find a new successor to TS */ + mstp_port->master_state = + MSTP_MASTER_STATE_POLL_FOR_MASTER; + } else { + /* ResetMaintenancePFM */ + mstp_port->Poll_Station = mstp_port->This_Station; + /* transmit a Token frame to NS */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_TOKEN, + mstp_port->Next_Station, + mstp_port->This_Station, NULL, 0); + mstp_port->RetryCount = 0; + mstp_port->TokenCount = 1; /* changed in Errata SSPC-135-2004 */ + mstp_port->EventCount = 0; + mstp_port->master_state = MSTP_MASTER_STATE_PASS_TOKEN; + } + } else { + /* SendMaintenancePFM */ + mstp_port->Poll_Station = next_poll_station; + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_POLL_FOR_MASTER, + mstp_port->Poll_Station, mstp_port->This_Station, NULL, 0); + mstp_port->RetryCount = 0; + mstp_port->master_state = MSTP_MASTER_STATE_POLL_FOR_MASTER; + } + break; + /* The PASS_TOKEN state listens for a successor to begin using */ + /* the token that this node has just attempted to pass. */ + case MSTP_MASTER_STATE_PASS_TOKEN: + if (mstp_port->SilenceTimer < Tusage_timeout) { + if (mstp_port->EventCount > Nmin_octets) { + /* SawTokenUser */ + /* Assume that a frame has been sent by the new token user. */ + /* Enter the IDLE state to process the frame. */ + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + transition_now = true; + } + } else { + if (mstp_port->RetryCount < Nretry_token) { + /* RetrySendToken */ + mstp_port->RetryCount++; + /* Transmit a Token frame to NS */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_TOKEN, + mstp_port->Next_Station, mstp_port->This_Station, NULL, + 0); + mstp_port->EventCount = 0; + /* re-enter the current state to listen for NS */ + /* to begin using the token. */ + } else { + /* FindNewSuccessor */ + /* Assume that NS has failed. */ + mstp_port->Poll_Station = next_next_station; + /* Transmit a Poll For Master frame to PS. */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_POLL_FOR_MASTER, + mstp_port->Poll_Station, mstp_port->This_Station, NULL, + 0); + /* no known successor node */ + mstp_port->Next_Station = mstp_port->This_Station; + mstp_port->RetryCount = 0; + mstp_port->TokenCount = 0; + /* mstp_port->EventCount = 0; removed in Addendum 135-2004d-8 */ + /* find a new successor to TS */ + mstp_port->master_state = + MSTP_MASTER_STATE_POLL_FOR_MASTER; + } + } + break; + /* The NO_TOKEN state is entered if mstp_port->SilenceTimer becomes greater */ + /* than Tno_token, indicating that there has been no network activity */ + /* for that period of time. The timeout is continued to determine */ + /* whether or not this node may create a token. */ + case MSTP_MASTER_STATE_NO_TOKEN: + my_timeout = Tno_token + (Tslot * mstp_port->This_Station); + if (mstp_port->SilenceTimer < my_timeout) { + if (mstp_port->EventCount > Nmin_octets) { + /* SawFrame */ + /* Some other node exists at a lower address. */ + /* Enter the IDLE state to receive and process the incoming frame. */ + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + transition_now = true; + } + } else { + ns_timeout = + Tno_token + (Tslot * (mstp_port->This_Station + 1)); + if (mstp_port->SilenceTimer < ns_timeout) { + /* GenerateToken */ + /* Assume that this node is the lowest numerical address */ + /* on the network and is empowered to create a token. */ + mstp_port->Poll_Station = next_this_station; + /* Transmit a Poll For Master frame to PS. */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_POLL_FOR_MASTER, + mstp_port->Poll_Station, mstp_port->This_Station, NULL, + 0); + /* indicate that the next station is unknown */ + mstp_port->Next_Station = mstp_port->This_Station; + mstp_port->RetryCount = 0; + mstp_port->TokenCount = 0; + /* mstp_port->EventCount = 0; removed Addendum 135-2004d-8 */ + /* enter the POLL_FOR_MASTER state to find a new successor to TS. */ + mstp_port->master_state = + MSTP_MASTER_STATE_POLL_FOR_MASTER; + } + } + break; + /* In the POLL_FOR_MASTER state, the node listens for a reply to */ + /* a previously sent Poll For Master frame in order to find */ + /* a successor node. */ + case MSTP_MASTER_STATE_POLL_FOR_MASTER: + if (mstp_port->ReceivedValidFrame == true) { + if ((mstp_port->DestinationAddress == mstp_port->This_Station) + && (mstp_port->FrameType == + FRAME_TYPE_REPLY_TO_POLL_FOR_MASTER)) { + /* ReceivedReplyToPFM */ + mstp_port->SoleMaster = false; + mstp_port->Next_Station = mstp_port->SourceAddress; + mstp_port->EventCount = 0; + /* Transmit a Token frame to NS */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_TOKEN, + mstp_port->Next_Station, mstp_port->This_Station, NULL, + 0); + mstp_port->Poll_Station = mstp_port->This_Station; + mstp_port->TokenCount = 0; + mstp_port->RetryCount = 0; + mstp_port->master_state = MSTP_MASTER_STATE_PASS_TOKEN; + } else { + /* ReceivedUnexpectedFrame */ + /* An unexpected frame was received. */ + /* This may indicate the presence of multiple tokens. */ + /* enter the IDLE state to synchronize with the network. */ + /* This action drops the token. */ + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + transition_now = true; + } + mstp_port->ReceivedValidFrame = false; + } else if ((mstp_port->SilenceTimer >= Tusage_timeout) || + (mstp_port->ReceivedInvalidFrame == true)) { + if (mstp_port->SoleMaster == true) { + /* SoleMaster */ + /* There was no valid reply to the periodic poll */ + /* by the sole known master for other masters. */ + mstp_port->FrameCount = 0; + /* mstp_port->TokenCount++; removed in 2004 */ + mstp_port->master_state = MSTP_MASTER_STATE_USE_TOKEN; + transition_now = true; + } else { + if (mstp_port->Next_Station != mstp_port->This_Station) { + /* DoneWithPFM */ + /* There was no valid reply to the maintenance */ + /* poll for a master at address PS. */ + mstp_port->EventCount = 0; + /* transmit a Token frame to NS */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_TOKEN, + mstp_port->Next_Station, mstp_port->This_Station, + NULL, 0); + mstp_port->RetryCount = 0; + mstp_port->master_state = MSTP_MASTER_STATE_PASS_TOKEN; + } else { + if (next_poll_station != mstp_port->This_Station) { + /* SendNextPFM */ + mstp_port->Poll_Station = next_poll_station; + /* Transmit a Poll For Master frame to PS. */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_POLL_FOR_MASTER, + mstp_port->Poll_Station, + mstp_port->This_Station, NULL, 0); + mstp_port->RetryCount = 0; + /* Re-enter the current state. */ + } else { + /* DeclareSoleMaster */ + /* to indicate that this station is the only master */ + mstp_port->SoleMaster = true; + mstp_port->FrameCount = 0; + mstp_port->master_state = + MSTP_MASTER_STATE_USE_TOKEN; + transition_now = true; + } + } + } + mstp_port->ReceivedInvalidFrame = false; + } + break; + /* The ANSWER_DATA_REQUEST state is entered when a */ + /* BACnet Data Expecting Reply, a Test_Request, or */ + /* a proprietary frame that expects a reply is received. */ + case MSTP_MASTER_STATE_ANSWER_DATA_REQUEST: + /* FIXME: if we knew the APDU type received, we could + see if the next message was that same APDU type */ + if ((mstp_port->SilenceTimer <= Treply_delay) && + mstp_port->TxReady) { + /* Reply */ + /* If a reply is available from the higher layers */ + /* within Treply_delay after the reception of the */ + /* final octet of the requesting frame */ + /* (the mechanism used to determine this is a local matter), */ + /* then call MSTP_Create_And_Send_Frame to transmit the reply frame */ + /* and enter the IDLE state to wait for the next frame. */ + if ((mstp_port->FrameType == + FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY) + && (mstp_port->TxReady)) { + RS485_Send_Frame(mstp_port, + (uint8_t *) & mstp_port->TxBuffer[0], + mstp_port->TxLength); + mstp_port->TxReady = false; + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + } + } + /* DeferredReply */ + /* If no reply will be available from the higher layers */ + /* within Treply_delay after the reception of the */ + /* final octet of the requesting frame (the mechanism */ + /* used to determine this is a local matter), */ + /* then an immediate reply is not possible. */ + /* Any reply shall wait until this node receives the token. */ + /* Call MSTP_Create_And_Send_Frame to transmit a Reply Postponed frame, */ + /* and enter the IDLE state. */ + else { + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_REPLY_POSTPONED, + mstp_port->SourceAddress, + mstp_port->This_Station, NULL, 0); + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + } + break; + default: + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + break; + } + + return transition_now; +} + +/* note: This_Station should be set with the MAC address */ +/* note: Nmax_info_frames should be set */ +/* note: Nmax_master should be set */ +void MSTP_Init(volatile struct mstp_port_struct_t *mstp_port) +{ + int i; /*loop counter */ + + if (mstp_port) { + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + mstp_port->master_state = MSTP_MASTER_STATE_INITIALIZE; + mstp_port->ReceiveError = false; + mstp_port->DataAvailable = false; + mstp_port->DataRegister = 0; + mstp_port->DataCRC = 0; + mstp_port->DataCRC = 0; + mstp_port->DataLength = 0; + mstp_port->DestinationAddress = 0; + mstp_port->EventCount = 0; + mstp_port->FrameType = FRAME_TYPE_TOKEN; + mstp_port->FrameCount = 0; + mstp_port->HeaderCRC = 0; + mstp_port->Index = 0; + mstp_port->Index = 0; + for (i = 0; i < sizeof(mstp_port->InputBuffer); i++) { + mstp_port->InputBuffer[i] = 0; + } + mstp_port->Next_Station = mstp_port->This_Station; + mstp_port->Poll_Station = mstp_port->This_Station; + mstp_port->ReceivedInvalidFrame = false; + mstp_port->ReceivedValidFrame = false; + mstp_port->RetryCount = 0; + mstp_port->SilenceTimer = 0; +/* mstp_port->ReplyPostponedTimer = 0; */ + mstp_port->SoleMaster = false; + mstp_port->SourceAddress = 0; + mstp_port->TokenCount = 0; +#if 0 + /* these are adjustable, so should already be set */ + mstp_port->Nmax_info_frames = DEFAULT_MAX_INFO_FRAMES; + mstp_port->Nmax_master = DEFAULT_MAX_MASTER; +#endif + + /* An array of octets, used to store PDU octets prior to being transmitted. */ + /* This array is only used for APDU messages */ + for (i = 0; i < sizeof(mstp_port->TxBuffer); i++) { + mstp_port->TxBuffer[i] = 0; + } + mstp_port->TxLength = 0; + mstp_port->TxReady = false; + mstp_port->TxFrameType = 0; + + } +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +/* test stub functions */ +void RS485_Send_Frame(volatile struct mstp_port_struct_t *mstp_port, /* port specific data */ + uint8_t * buffer, /* frame to send (up to 501 bytes of data) */ + uint16_t nbytes) +{ /* number of bytes of data (up to 501) */ + (void) mstp_port; + (void) buffer; + (void) nbytes; +} + +#define RING_BUFFER_DATA_SIZE 1 +#define RING_BUFFER_SIZE MAX_MPDU +static RING_BUFFER Test_Buffer; +static uint8_t Test_Buffer_Data[RING_BUFFER_DATA_SIZE * RING_BUFFER_SIZE]; +static void Load_Input_Buffer(uint8_t * buffer, size_t len) +{ + static bool initialized = false; /* tracks our init */ + + + if (!initialized) { + initialized = true; + Ringbuf_Init(&Test_Buffer, + (char *) Test_Buffer_Data, + RING_BUFFER_DATA_SIZE, RING_BUFFER_SIZE); + } + /* empty any the existing data */ + while (!Ringbuf_Empty(&Test_Buffer)) { + (void) Ringbuf_Pop_Front(&Test_Buffer); + } + + if (buffer) { + while (len) { + (void) Ringbuf_Put(&Test_Buffer, (char *) buffer); + len--; + buffer++; + } + } +} + +void RS485_Check_UART_Data(volatile struct mstp_port_struct_t *mstp_port) +{ /* port specific data */ + char *data; + if (!Ringbuf_Empty(&Test_Buffer) && mstp_port && + (mstp_port->DataAvailable == false)) { + data = Ringbuf_Pop_Front(&Test_Buffer); + if (data) { + mstp_port->DataRegister = *data; + mstp_port->DataAvailable = true; + } + } +} + +void testReceiveNodeFSM(Test * pTest) +{ + volatile struct mstp_port_struct_t mstp_port; /* port data */ + unsigned EventCount = 0; /* local counter */ + uint8_t my_mac = 0x05; /* local MAC address */ + uint8_t HeaderCRC = 0; /* for local CRC calculation */ + uint8_t FrameType = 0; /* type of packet that was sent */ + unsigned len; /* used for the size of the message packet */ + size_t i; /* used to loop through the message bytes */ + uint8_t buffer[MAX_MPDU] = { 0 }; + uint8_t data[MAX_MPDU - 8 /*header */ - 2 /*CRC*/] = { 0 }; + + MSTP_Init(&mstp_port, my_mac); + + /* check the receive error during idle */ + mstp_port.receive_state = MSTP_RECEIVE_STATE_IDLE; + mstp_port.ReceiveError = true; + mstp_port.SilenceTimer = 255; + mstp_port.EventCount = 0; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.ReceiveError == false); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* check for bad packet header */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x11; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* check for good packet header, but timeout */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + /* force the timeout */ + mstp_port.SilenceTimer = Tframe_abort + 1; + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* check for good packet header preamble, but receive error */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + /* force the error */ + mstp_port.ReceiveError = true; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.ReceiveError == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* check for good packet header preamble1, but bad preamble2 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + MSTP_Receive_Frame_FSM(&mstp_port); + /* no change of state if no data yet */ + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + /* repeated preamble1 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + /* repeated preamble1 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + /* bad data */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x11; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.ReceiveError == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* check for good packet header preamble, but timeout in packet */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + MSTP_Receive_Frame_FSM(&mstp_port); + /* preamble2 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0xFF; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 0); + ct_test(pTest, mstp_port.HeaderCRC == 0xFF); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + /* force the timeout */ + mstp_port.SilenceTimer = Tframe_abort + 1; + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + ct_test(pTest, mstp_port.ReceivedInvalidFrame == true); + + /* check for good packet header preamble, but error in packet */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + MSTP_Receive_Frame_FSM(&mstp_port); + /* preamble2 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0xFF; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 0); + ct_test(pTest, mstp_port.HeaderCRC == 0xFF); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + /* force the error */ + mstp_port.ReceiveError = true; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.ReceiveError == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* check for good packet header preamble */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + MSTP_Receive_Frame_FSM(&mstp_port); + /* preamble2 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0xFF; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 0); + ct_test(pTest, mstp_port.HeaderCRC == 0xFF); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + /* no change of state if no data yet */ + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + /* Data is received - index is incremented */ + /* FrameType */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = FRAME_TYPE_TOKEN; + HeaderCRC = 0xFF; + HeaderCRC = CRC_Calc_Header(mstp_port.DataRegister, HeaderCRC); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 1); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + ct_test(pTest, FrameType == FRAME_TYPE_TOKEN); + /* Destination */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x10; + HeaderCRC = CRC_Calc_Header(mstp_port.DataRegister, HeaderCRC); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 2); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + ct_test(pTest, mstp_port.DestinationAddress == 0x10); + /* Source */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = my_mac; + HeaderCRC = CRC_Calc_Header(mstp_port.DataRegister, HeaderCRC); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 3); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + ct_test(pTest, mstp_port.SourceAddress == my_mac); + /* Length1 = length*256 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0; + HeaderCRC = CRC_Calc_Header(mstp_port.DataRegister, HeaderCRC); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 4); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + ct_test(pTest, mstp_port.DataLength == 0); + /* Length2 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0; + HeaderCRC = CRC_Calc_Header(mstp_port.DataRegister, HeaderCRC); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 5); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + ct_test(pTest, mstp_port.DataLength == 0); + /* HeaderCRC */ + mstp_port.DataAvailable = true; + ct_test(pTest, HeaderCRC == 0x73); /* per Annex G example */ + mstp_port.DataRegister = ~HeaderCRC; /* one's compliment of CRC is sent */ + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 5); + ct_test(pTest, + mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER_CRC); + ct_test(pTest, mstp_port.HeaderCRC == 0x55); + /* NotForUs */ + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* BadCRC in header check */ + mstp_port.ReceivedInvalidFrame = false; + mstp_port.ReceivedValidFrame = false; + len = MSTP_Create_Frame(buffer, sizeof(buffer), FRAME_TYPE_TOKEN, 0x10, /* destination */ + my_mac, /* source */ + NULL, /* data */ + 0); /* data size */ + ct_test(pTest, len > 0); + /* make the header CRC bad */ + buffer[7] = 0x00; + Load_Input_Buffer(buffer, len); + for (i = 0; i < len; i++) { + RS485_Check_UART_Data(&mstp_port); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + } + ct_test(pTest, + mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER_CRC); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.ReceivedInvalidFrame == true); + ct_test(pTest, mstp_port.ReceivedValidFrame == false); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* NoData for us */ + mstp_port.ReceivedInvalidFrame = false; + mstp_port.ReceivedValidFrame = false; + len = MSTP_Create_Frame(buffer, sizeof(buffer), FRAME_TYPE_TOKEN, my_mac, /* destination */ + my_mac, /* source */ + NULL, /* data */ + 0); /* data size */ + ct_test(pTest, len > 0); + Load_Input_Buffer(buffer, len); + for (i = 0; i < len; i++) { + RS485_Check_UART_Data(&mstp_port); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + } + ct_test(pTest, + mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER_CRC); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.ReceivedInvalidFrame == false); + ct_test(pTest, mstp_port.ReceivedValidFrame == true); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* FrameTooLong */ + mstp_port.ReceivedInvalidFrame = false; + mstp_port.ReceivedValidFrame = false; + len = MSTP_Create_Frame(buffer, sizeof(buffer), FRAME_TYPE_TOKEN, my_mac, /* destination */ + my_mac, /* source */ + NULL, /* data */ + 0); /* data size */ + ct_test(pTest, len > 0); + /* make the header data length bad */ + buffer[5] = 0x02; + Load_Input_Buffer(buffer, len); + for (i = 0; i < len; i++) { + RS485_Check_UART_Data(&mstp_port); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + } + ct_test(pTest, + mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER_CRC); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.ReceivedInvalidFrame == true); + ct_test(pTest, mstp_port.ReceivedValidFrame == false); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* Data */ + mstp_port.ReceivedInvalidFrame = false; + mstp_port.ReceivedValidFrame = false; + memset(data, 0, sizeof(data)); + len = MSTP_Create_Frame(buffer, sizeof(buffer), FRAME_TYPE_PROPRIETARY_MIN, my_mac, /* destination */ + my_mac, /* source */ + data, /* data */ + sizeof(data)); /* data size */ + ct_test(pTest, len > 0); + Load_Input_Buffer(buffer, len); + RS485_Check_UART_Data(&mstp_port); + MSTP_Receive_Frame_FSM(&mstp_port); + while (mstp_port.receive_state != MSTP_RECEIVE_STATE_IDLE) { + RS485_Check_UART_Data(&mstp_port); + MSTP_Receive_Frame_FSM(&mstp_port); + } + ct_test(pTest, mstp_port.DataLength == sizeof(data)); + ct_test(pTest, mstp_port.ReceivedInvalidFrame == false); + ct_test(pTest, mstp_port.ReceivedValidFrame == true); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + return; +} + +void testMasterNodeFSM(Test * pTest) +{ + volatile struct mstp_port_struct_t mstp_port; /* port data */ + uint8_t my_mac = 0x05; /* local MAC address */ + + MSTP_Init(&mstp_port, my_mac); + ct_test(pTest, mstp_port.master_state == MSTP_MASTER_STATE_INITIALIZE); + +} + +#endif + +#ifdef TEST_MSTP +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("mstp", NULL); + + /* individual tests */ + rc = ct_addTestFunction(pTest, testReceiveNodeFSM); + assert(rc); + rc = ct_addTestFunction(pTest, testMasterNodeFSM); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + + ct_destroy(pTest); + + return 0; +} +#endif diff --git a/bacnet-stack/ports/pic18f6720/mstp.h b/bacnet-stack/ports/pic18f6720/mstp.h index e69de29b..3a658cdd 100644 --- a/bacnet-stack/ports/pic18f6720/mstp.h +++ b/bacnet-stack/ports/pic18f6720/mstp.h @@ -0,0 +1,246 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307 + USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#ifndef MSTP_H +#define MSTP_H + +#include +#include +#include +#include "bacdef.h" +#include "dlmstp.h" + +/* The value 255 is used to denote broadcast when used as a */ +/* destination address but is not allowed as a value for a station. */ +/* Station addresses for master nodes can be 0-127. */ +/* Station addresses for slave nodes can be 127-254. */ +#define MSTP_BROADCAST_ADDRESS 255 + +/* MS/TP Frame Type */ +/* Frame Types 8 through 127 are reserved by ASHRAE. */ +#define FRAME_TYPE_TOKEN 0 +#define FRAME_TYPE_POLL_FOR_MASTER 1 +#define FRAME_TYPE_REPLY_TO_POLL_FOR_MASTER 2 +#define FRAME_TYPE_TEST_REQUEST 3 +#define FRAME_TYPE_TEST_RESPONSE 4 +#define FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY 5 +#define FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY 6 +#define FRAME_TYPE_REPLY_POSTPONED 7 +/* Frame Types 128 through 255: Proprietary Frames */ +/* These frames are available to vendors as proprietary (non-BACnet) frames. */ +/* The first two octets of the Data field shall specify the unique vendor */ +/* identification code, most significant octet first, for the type of */ +/* vendor-proprietary frame to be conveyed. The length of the data portion */ +/* of a Proprietary frame shall be in the range of 2 to 501 octets. */ +#define FRAME_TYPE_PROPRIETARY_MIN 128 +#define FRAME_TYPE_PROPRIETARY_MAX 255 +/* The initial CRC16 checksum value */ +#define CRC16_INITIAL_VALUE (0xFFFF) + + +/* receive FSM states */ +typedef enum { + MSTP_RECEIVE_STATE_IDLE = 0, + MSTP_RECEIVE_STATE_PREAMBLE = 1, + MSTP_RECEIVE_STATE_HEADER = 2, + MSTP_RECEIVE_STATE_HEADER_CRC = 3, + MSTP_RECEIVE_STATE_DATA = 4 +} MSTP_RECEIVE_STATE; + +/* master node FSM states */ +typedef enum { + MSTP_MASTER_STATE_INITIALIZE = 0, + MSTP_MASTER_STATE_IDLE = 1, + MSTP_MASTER_STATE_USE_TOKEN = 2, + MSTP_MASTER_STATE_WAIT_FOR_REPLY = 3, + MSTP_MASTER_STATE_DONE_WITH_TOKEN = 4, + MSTP_MASTER_STATE_PASS_TOKEN = 5, + MSTP_MASTER_STATE_NO_TOKEN = 6, + MSTP_MASTER_STATE_POLL_FOR_MASTER = 7, + MSTP_MASTER_STATE_ANSWER_DATA_REQUEST = 8 +} MSTP_MASTER_STATE; + +struct mstp_port_struct_t { + MSTP_RECEIVE_STATE receive_state; + /* When a master node is powered up or reset, */ + /* it shall unconditionally enter the INITIALIZE state. */ + MSTP_MASTER_STATE master_state; + /* A Boolean flag set to TRUE by the Receive State Machine */ + /* if an error is detected during the reception of a frame. */ + /* Set to FALSE by the main state machine. */ + unsigned ReceiveError:1; + /* There is data in the buffer */ + unsigned DataAvailable:1; + unsigned ReceivedInvalidFrame:1; + /* A Boolean flag set to TRUE by the Receive State Machine */ + /* if a valid frame is received. */ + /* Set to FALSE by the main state machine. */ + unsigned ReceivedValidFrame:1; + /* A Boolean flag set to TRUE by the master machine if this node is the */ + /* only known master node. */ + unsigned SoleMaster:1; + /* stores the latest received data */ + uint8_t DataRegister; + /* Used to accumulate the CRC on the data field of a frame. */ + uint16_t DataCRC; + /* Used to store the data length of a received frame. */ + unsigned DataLength; + /* Used to store the destination address of a received frame. */ + uint8_t DestinationAddress; + /* Used to count the number of received octets or errors. */ + /* This is used in the detection of link activity. */ + /* Compared to Nmin_octets */ + uint8_t EventCount; + /* Used to store the frame type of a received frame. */ + uint8_t FrameType; + /* The number of frames sent by this node during a single token hold. */ + /* When this counter reaches the value Nmax_info_frames, the node must */ + /* pass the token. */ + unsigned FrameCount; + /* Used to accumulate the CRC on the header of a frame. */ + uint8_t HeaderCRC; + /* Used as an index by the Receive State Machine, up to a maximum value of */ + /* InputBufferSize. */ + unsigned Index; + /* An array of octets, used to store octets as they are received. */ + /* InputBuffer is indexed from 0 to InputBufferSize-1. */ + /* The maximum size of a frame is 501 octets. */ + uint8_t *InputBuffer; + /* "Next Station," the MAC address of the node to which This Station passes */ + /* the token. If the Next_Station is unknown, Next_Station shall be equal to */ + /* This_Station. */ + uint8_t Next_Station; + /* "Poll Station," the MAC address of the node to which This Station last */ + /* sent a Poll For Master. This is used during token maintenance. */ + uint8_t Poll_Station; + /* A counter of transmission retries used for Token and Poll For Master */ + /* transmission. */ + unsigned RetryCount; + /* A timer with nominal 5 millisecond resolution used to measure and */ + /* generate silence on the medium between octets. It is incremented by a */ + /* timer process and is cleared by the Receive State Machine when activity */ + /* is detected and by the SendFrame procedure as each octet is transmitted. */ + /* Since the timer resolution is limited and the timer is not necessarily */ + /* synchronized to other machine events, a timer value of N will actually */ + /* denote intervals between N-1 and N */ + uint16_t SilenceTimer; + + /* A timer used to measure and generate Reply Postponed frames. It is */ + /* incremented by a timer process and is cleared by the Master Node State */ + /* Machine when a Data Expecting Reply Answer activity is completed. */ +/* note: we always send a reply postponed since a message other than + the reply may be in the transmit queue */ +/* uint16_t ReplyPostponedTimer; */ + + /* Used to store the Source Address of a received frame. */ + uint8_t SourceAddress; + + /* The number of tokens received by this node. When this counter reaches the */ + /* value Npoll, the node polls the address range between TS and NS for */ + /* additional master nodes. TokenCount is set to zero at the end of the */ + /* polling process. */ + unsigned TokenCount; + + /* "This Station," the MAC address of this node. TS is generally read from a */ + /* hardware DIP switch, or from nonvolatile memory. Valid values for TS are */ + /* 0 to 254. The value 255 is used to denote broadcast when used as a */ + /* destination address but is not allowed as a value for TS. */ + uint8_t This_Station; + + /* This parameter represents the value of the Max_Info_Frames property of */ + /* the node's Device object. The value of Max_Info_Frames specifies the */ + /* maximum number of information frames the node may send before it must */ + /* pass the token. Max_Info_Frames may have different values on different */ + /* nodes. This may be used to allocate more or less of the available link */ + /* bandwidth to particular nodes. If Max_Info_Frames is not writable in a */ + /* node, its value shall be 1. */ + unsigned Nmax_info_frames; + + /* This parameter represents the value of the Max_Master property of the */ + /* node's Device object. The value of Max_Master specifies the highest */ + /* allowable address for master nodes. The value of Max_Master shall be */ + /* less than or equal to 127. If Max_Master is not writable in a node, */ + /* its value shall be 127. */ + unsigned Nmax_master; + + /* An array of octets, used to store PDU octets prior to being transmitted. */ + /* This array is only used for APDU messages */ + uint8_t TxBuffer[MAX_MPDU]; + unsigned TxLength; + bool TxReady; /* true if ready to be sent or received */ + uint8_t TxFrameType; /* type of message - needed by MS/TP */ +}; + +#define DEFAULT_MAX_INFO_FRAMES 1 +#define DEFAULT_MAX_MASTER 127 +#define DEFAULT_MAC_ADDRESS 127 + +/* The minimum time after the end of the stop bit of the final octet of a */ +/* received frame before a node may enable its EIA-485 driver: 40 bit times. */ +/* At 9600 baud, 40 bit times would be about 4.166 milliseconds */ +/* At 19200 baud, 40 bit times would be about 2.083 milliseconds */ +/* At 38400 baud, 40 bit times would be about 1.041 milliseconds */ +/* At 57600 baud, 40 bit times would be about 0.694 milliseconds */ +/* At 76800 baud, 40 bit times would be about 0.520 milliseconds */ +/* At 115200 baud, 40 bit times would be about 0.347 milliseconds */ +/* 40 bits is 4 octets including a start and stop bit with each octet */ +#define Tturnaround 40 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + void MSTP_Init(volatile struct mstp_port_struct_t *mstp_port); + void MSTP_Receive_Frame_FSM(volatile struct mstp_port_struct_t + *mstp_port); + bool MSTP_Master_Node_FSM(volatile struct mstp_port_struct_t + *mstp_port); + + /* returns true if line is active */ + bool MSTP_Line_Active(volatile struct mstp_port_struct_t *mstp_port); + + unsigned MSTP_Create_Frame(uint8_t * buffer, /* where frame is loaded */ + unsigned buffer_len, /* amount of space available */ + uint8_t frame_type, /* type of frame to send - see defines */ + uint8_t destination, /* destination address */ + uint8_t source, /* source address */ + uint8_t * data, /* any data to be sent - may be null */ + unsigned data_len); /* number of bytes of data (up to 501) */ + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/ports/pic18f6720/rs485.c b/bacnet-stack/ports/pic18f6720/rs485.c index e69de29b..f2d7daac 100644 --- a/bacnet-stack/ports/pic18f6720/rs485.c +++ b/bacnet-stack/ports/pic18f6720/rs485.c @@ -0,0 +1,380 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ + +/* The module handles sending data out the RS-485 port */ +/* and handles receiving data from the RS-485 port. */ +/* Customize this file for your specific hardware */ +#include +#include +#include +#include +#include "hardware.h" +#include "mstp.h" +#include "rs485.h" + +/* public port info */ +extern volatile struct mstp_port_struct_t MSTP_Port; + +/* the baud rate is adjustable */ +uint32_t RS485_Baud_Rate = 38400; + +/* the ISR and other use this for status and control */ +COMSTAT RS485_Comstat; + +/*#pragma udata MSTPPortData + */ +/* the buffer for receiving characters */ +volatile uint8_t RS485_Rx_Buffer[MAX_MPDU]; + +/* UART transmission buffer and index */ +volatile uint8_t RS485_Tx_Buffer[MAX_MPDU]; + +/**************************************************************************** +* DESCRIPTION: Transmits a frame using the UART +* RETURN: none +* ALGORITHM: none +* NOTES: none +*****************************************************************************/ +void RS485_Send_Frame(volatile struct mstp_port_struct_t *mstp_port, /* port specific data */ + uint8_t * buffer, /* frame to send (up to 501 bytes of data) */ + uint16_t nbytes) +{ /* number of bytes of data (up to 501) */ + uint16_t i = 0; /* loop counter */ + uint8_t turnaround_time; + + if (!buffer) + return; + + /* bounds check */ + if (nbytes >= sizeof(RS485_Tx_Buffer)) + return; + + /* buffer is full. Wait for ISR to transmit. */ + while (RS485_Comstat.Tx_Bytes) { + }; + + /* wait 40 bit times since reception */ + if (RS485_Baud_Rate == 9600) + turnaround_time = 4; + else if (RS485_Baud_Rate == 19200) + turnaround_time = 2; + else + turnaround_time = 1; + + while (mstp_port->SilenceTimer < turnaround_time) { + }; + + RS485_Comstat.TxHead = 0; + memcpy((void *) &RS485_Tx_Buffer[0], (void *) buffer, nbytes); +#if 0 + for (i = 0; i < nbytes; i++) { + /* put the data into the buffer */ + RS485_Tx_Buffer[i] = *buffer; + buffer++; + } +#endif + RS485_Comstat.Tx_Bytes = nbytes; + /* disable the receiver */ + PIE3bits.RC2IE = 0; + RCSTA2bits.CREN = 0; + /* enable the transceiver */ + RS485_TX_ENABLE = 1; + RS485_RX_DISABLE = 1; + /* enable the transmitter */ + TXSTA2bits.TXEN = 1; + PIE3bits.TX2IE = 1; + /* per MSTP spec, sort of */ + mstp_port->SilenceTimer = 0; + + return; +} + +/**************************************************************************** +* DESCRIPTION: Checks for data on the receive UART, and handles errors +* RETURN: none +* ALGORITHM: none +* NOTES: none +*****************************************************************************/ +uint8_t RS485_Check_UART_Data(volatile struct mstp_port_struct_t * + mstp_port) +{ + /* check for data */ + if (RS485_Comstat.Rx_Bytes) { + mstp_port->DataRegister = RS485_Rx_Buffer[RS485_Comstat.RxTail]; + if (RS485_Comstat.RxTail >= (sizeof(RS485_Rx_Buffer) - 1)) + RS485_Comstat.RxTail = 0; + else + RS485_Comstat.RxTail++; + RS485_Comstat.Rx_Bytes--; + /* errors? let the state machine know */ + if (RS485_Comstat.Rx_Bufferoverrun) { + RS485_Comstat.Rx_Bufferoverrun = FALSE; + mstp_port->ReceiveError = TRUE; + } + /* We read a good byte */ + else + mstp_port->DataAvailable = TRUE; + } + + return RS485_Comstat.Rx_Bytes; +} + +/* ************************************************************************* + DESCRIPTION: Receives RS485 data stream + + RETURN: none + + ALGORITHM: none + + NOTES: none + *************************************************************************** */ +void RS485_Interrupt_Rx(void) +{ + char dummy; + + if ((RCSTA2bits.FERR) || (RCSTA2bits.OERR)) { + /* Clear the error */ + RCSTA2bits.CREN = 0; + RCSTA2bits.CREN = 1; + RS485_Comstat.Rx_Bufferoverrun = TRUE; + dummy = RCREG2; + } else if (RS485_Comstat.Rx_Bytes < sizeof(RS485_Rx_Buffer)) { + RS485_Rx_Buffer[RS485_Comstat.RxHead] = RCREG2; + if (RS485_Comstat.RxHead >= (sizeof(RS485_Rx_Buffer) - 1)) + RS485_Comstat.RxHead = 0; + else + RS485_Comstat.RxHead++; + RS485_Comstat.Rx_Bytes++; + } else { + RS485_Comstat.Rx_Bufferoverrun = TRUE; + dummy = RCREG2; + (void) dummy; + } +} + +/* ************************************************************************* + DESCRIPTION: Transmits a byte using the UART out the RS485 port + + RETURN: none + + ALGORITHM: none + + NOTES: none + *************************************************************************** */ +void RS485_Interrupt_Tx(void) +{ + if (RS485_Comstat.Tx_Bytes) { + /* Get the data byte */ + TXREG2 = RS485_Tx_Buffer[RS485_Comstat.TxHead]; + /* point to the next byte */ + RS485_Comstat.TxHead++; + /* reduce the buffer size */ + RS485_Comstat.Tx_Bytes--; + } else { + /* wait for the USART to be empty */ + while (!TXSTA2bits.TRMT); + /* disable this interrupt */ + PIE3bits.TX2IE = 0; + /* enable the receiver */ + RS485_TX_ENABLE = 0; + RS485_RX_DISABLE = 0; + /* FIXME: might not be necessary + */ + PIE3bits.RC2IE = 1; + RCSTA2bits.CREN = 1; + } +} + +/**************************************************************************** +* DESCRIPTION: Returns the baud rate that we are currently running at +* RETURN: none +* ALGORITHM: none +* NOTES: none +*****************************************************************************/ +uint32_t RS485_Get_Baud_Rate(void) +{ + return RS485_Baud_Rate; +} + +/**************************************************************************** +* DESCRIPTION: Sets the baud rate for the chip USART +* RETURN: none +* ALGORITHM: none +* NOTES: none +*****************************************************************************/ +bool RS485_Set_Baud_Rate(uint32_t baud) +{ + bool valid = true; + + switch (baud) { + case 9600: + case 19200: + case 38400: + case 57600: + case 76800: + case 115200: + RS485_Baud_Rate = baud; + break; + default: + valid = false; + break; + } + + if (valid) { + /* FIXME: store the baud rate */ + /* I2C_Write_Block( + EEPROM_DEVICE_ADDRESS, + (char *)&RS485_Baud_Rate, + sizeof(RS485_Baud_Rate), + EEPROM_MSTP_BAUD_RATE_ADDR); */ + } + + return valid; +} + +/**************************************************************************** +* DESCRIPTION: Initializes the RS485 hardware and variables, and starts in +* receive mode. +* RETURN: none +* ALGORITHM: none +* NOTES: none +*****************************************************************************/ +void RS485_Initialize_Port(void) +{ + + /* Reset USART registers to POR state */ + TXSTA2 = 0; + RCSTA2 = 0; + /* configure USART for receiving */ + /* since the TX will handle setting up for transmit */ + RCSTA2bits.CREN = 1; + /* Interrupt on receipt */ + PIE3bits.RC2IE = 1; + /* enable the transmitter, disable its interrupt */ + TXSTA2bits.TXEN = 1; + PIE3bits.TX2IE = 0; + /* setup USART Baud Rate Generator */ + /* see BAUD RATES FOR ASYNCHRONOUS MODE in Data Book */ + /* Fosc=20MHz + BRGH=1 BRGH=0 + Rate SPBRG Rate SPBRG + ------- ----- ------- ----- + 9615 129 9469 32 + 19230 64 19530 15 + 37878 32 78130 3 + 56818 21 104200 2 + 113630 10 312500 0 + 250000 4 + 625000 1 + 1250000 0 + */ + switch (RS485_Baud_Rate) { + case 19200: + SPBRG2 = 64; + TXSTA2bits.BRGH = 1; + break; + case 38400: + SPBRG2 = 32; + TXSTA2bits.BRGH = 1; + break; + case 57600: + SPBRG2 = 21; + TXSTA2bits.BRGH = 1; + break; + case 76800: + SPBRG2 = 3; + TXSTA2bits.BRGH = 0; + break; + case 115200: + SPBRG2 = 10; + TXSTA2bits.BRGH = 1; + break; + case 9600: + SPBRG2 = 129; + TXSTA2bits.BRGH = 1; + break; + default: + SPBRG2 = 129; + TXSTA2bits.BRGH = 1; + RS485_Set_Baud_Rate(9600); + break; + } + /* select async mode */ + TXSTA2bits.SYNC = 0; + /* enable transmitter */ + TXSTA2bits.TXEN = 1; + /* serial port enable */ + RCSTA2bits.SPEN = 1; + /* since we are using RS485, + we need to explicitly say + transmit enable or not */ + RS485_RX_DISABLE = 0; + RS485_TX_ENABLE = 0; +} + +/**************************************************************************** +* DESCRIPTION: Disables the RS485 hardware +* RETURN: none +* ALGORITHM: none +* NOTES: none +*****************************************************************************/ +void RS485_Disable_Port(void) +{ + RCSTA2 &= 0x4F; /* Disable the receiver */ + TXSTA2bits.TXEN = 0; /* and transmitter */ + PIE3 &= 0xCF; /* Disable both interrupts */ +} + +void RS485_Reinit(void) +{ + RS485_Set_Baud_Rate(38400); +} + +/**************************************************************************** +* DESCRIPTION: Initializes the data and the port +* RETURN: none +* ALGORITHM: none +* NOTES: none +*****************************************************************************/ +void RS485_Initialize(void) +{ + /* Init the Rs485 buffers */ + RS485_Comstat.RxHead = 0; + RS485_Comstat.RxTail = 0; + RS485_Comstat.Rx_Bytes = 0; + RS485_Comstat.Rx_Bufferoverrun = FALSE; + RS485_Comstat.TxHead = 0; + RS485_Comstat.TxTail = 0; + RS485_Comstat.Tx_Bytes = 0; + + /* FIXME: read the data from storage */ + /* I2C_Read_Block( + EEPROM_DEVICE_ADDRESS, + (char *)&RS485_Baud_Rate, + sizeof(RS485_Baud_Rate), + EEPROM_MSTP_BAUD_RATE_ADDR); */ + + RS485_Initialize_Port(); +} diff --git a/bacnet-stack/ports/pic18f6720/rs485.h b/bacnet-stack/ports/pic18f6720/rs485.h index e69de29b..b4dc293c 100644 --- a/bacnet-stack/ports/pic18f6720/rs485.h +++ b/bacnet-stack/ports/pic18f6720/rs485.h @@ -0,0 +1,84 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307 + USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#ifndef RS485_H +#define RS485_H + +#include +#include "mstp.h" + +typedef struct { + uint8_t RxHead; + uint8_t RxTail; + uint8_t Rx_Bytes; + uint8_t TxHead; + uint8_t TxTail; + uint8_t Tx_Bytes; + uint8_t Rx_Bufferoverrun:1; + uint8_t Tx_Bufferoverrun:1; +} COMSTAT; + +extern COMSTAT RS485_Comstat; +extern volatile uint8_t RS485_Rx_Buffer[MAX_MPDU]; +extern volatile uint8_t RS485_Tx_Buffer[MAX_MPDU]; +extern uint32_t RS485_Baud_Rate; + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + void RS485_Reinit(void); + void RS485_Initialize(void); + + void RS485_Disable(void); + + void RS485_Send_Frame(volatile struct mstp_port_struct_t *mstp_port, /* port specific data */ + uint8_t * buffer, /* frame to send (up to 501 bytes of data) */ + uint16_t nbytes); /* number of bytes of data (up to 501) */ + + uint8_t RS485_Check_UART_Data(volatile struct mstp_port_struct_t *mstp_port); /* port specific data */ + + void RS485_Interrupt_Rx(void); + + void RS485_Interrupt_Tx(void); + + uint32_t RS485_Get_Baud_Rate(void); + bool RS485_Set_Baud_Rate(uint32_t baud); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/ports/pic18f6720/stdbool.h b/bacnet-stack/ports/pic18f6720/stdbool.h index e69de29b..696ffd85 100644 --- a/bacnet-stack/ports/pic18f6720/stdbool.h +++ b/bacnet-stack/ports/pic18f6720/stdbool.h @@ -0,0 +1,28 @@ +#ifndef STDBOOL_H +#define STDBOOL_H + +/* C99 Boolean types for compilers without C99 support */ + +#ifndef __cplusplus +typedef char _Bool; +#ifndef bool +#define bool _Bool +#endif +#ifndef true +#define true 1 +#endif +#ifndef false +#define false 0 +#endif +#define __bool_true_false_are_defined 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#endif diff --git a/bacnet-stack/ports/pic18f6720/stdint.h b/bacnet-stack/ports/pic18f6720/stdint.h index e69de29b..e9629b20 100644 --- a/bacnet-stack/ports/pic18f6720/stdint.h +++ b/bacnet-stack/ports/pic18f6720/stdint.h @@ -0,0 +1,18 @@ +/* Defines the standard integer types that are used in code */ + +#ifndef STDINT_H +#define STDINT_H 1 + +#include + +typedef unsigned char uint8_t; /* 1 byte 0 to 255 */ +typedef signed char int8_t; /* 1 byte -127 to 127 */ +typedef unsigned short uint16_t; /* 2 bytes 0 to 65535 */ +typedef signed short int16_t; /* 2 bytes -32767 to 32767 */ +/*typedef unsigned short long uint24_t; // 3 bytes 0 to 16777215 */ +typedef unsigned long uint32_t; /* 4 bytes 0 to 4294967295 */ +typedef signed long int32_t; /* 4 bytes -2147483647 to 2147483647 */ +/* typedef signed long long int64_t; */ +/* typedef unsigned long long uint64_t; */ + +#endif /* STDINT_H */ diff --git a/bacnet-stack/ports/rtos32/bip-init.c b/bacnet-stack/ports/rtos32/bip-init.c index e69de29b..ed56face 100644 --- a/bacnet-stack/ports/rtos32/bip-init.c +++ b/bacnet-stack/ports/rtos32/bip-init.c @@ -0,0 +1,278 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#include /* for standard integer types uint8_t etc. */ +#include /* for the standard bool type. */ +#include "bacdcode.h" +#include "bip.h" + +static int interface = SOCKET_ERROR; /* SOCKET_ERROR means no open interface */ + +/*-----------------------------------*/ +static void Error(const char *Msg) +{ + int Code = WSAGetLastError(); +#ifdef HOST + printf("%s, error code: %i\n", Msg, Code); +#else + printf("%s, error code: %s\n", Msg, xn_geterror_string(Code)); +#endif + exit(1); +} + +#ifndef HOST +/*-----------------------------------*/ +void InterfaceCleanup(void) +{ + if (interface != SOCKET_ERROR) { + xn_interface_close(interface); + interface = SOCKET_ERROR; +#if DEVICE_ID == PRISM_PCMCIA_DEVICE + RTPCShutDown(); +#endif + } +} + +static void NetInitialize(void) +/* initialize the TCP/IP stack */ +{ + int Result; + +#ifndef HOST + if (!RTKDebugVersion()) /* switch of all diagnostics and error messages of RTIP-32 */ + xn_callbacks()->cb_wr_screen_string_fnc = NULL; + +#ifdef RTUSB_VER + RTURegisterCallback(USBAX172); /* ax172 and ax772 drivers */ + RTURegisterCallback(USBAX772); + RTURegisterCallback(USBKeyboard); /* support USB keyboards */ + FindUSBControllers(); /* install USB host controllers */ + Sleep(2000); /* give the USB stack time to enumerate devices */ +#endif + +#ifdef DHCP + XN_REGISTER_DHCP_CLI() /* and optionally the DHCP client */ +#endif + Result = xn_rtip_init(); /* Initialize the RTIP stack */ + if (Result != 0) + Error("xn_rtip_init failed"); + + atexit(InterfaceCleanup); /* make sure the driver is shut down properly */ + RTCallDebugger(RT_DBG_CALLRESET, (DWORD) exit, 0); /* even if we get restarted by the debugger */ + + Result = BIND_DRIVER(MINOR_0); /* tell RTIP what Ethernet driver we want (see netcfg.h) */ + if (Result != 0) + Error("driver initialization failed"); + +#if DEVICE_ID == PRISM_PCMCIA_DEVICE + /* if this is a PCMCIA device, start the PCMCIA driver */ + if (RTPCInit(-1, 0, 2, NULL) == 0) + Error("No PCMCIA controller found"); +#endif + + /* Open the interface */ + interface = + xn_interface_open_config(DEVICE_ID, MINOR_0, ED_IO_ADD, ED_IRQ, + ED_MEM_ADD); + if (interface == SOCKET_ERROR) + Error("xn_interface_open_config failed"); + else { + struct _iface_info ii; +#ifdef BACDL_ETHERNET + BACNET_ADDRESS my_address; + unsigned i; +#endif + xn_interface_info(interface, &ii); + printf + ("Interface opened, MAC address: %02x-%02x-%02x-%02x-%02x-%02x\n", + ii.my_ethernet_address[0], ii.my_ethernet_address[1], + ii.my_ethernet_address[2], ii.my_ethernet_address[3], + ii.my_ethernet_address[4], ii.my_ethernet_address[5]); +#ifdef BACDL_ETHERNET + for (i = 0; i < 6; i++) { + my_address.mac[i] = ii.my_ethernet_address[i]; + } + ethernet_set_my_address(&my_address); +#endif + } + +#if DEVICE_ID == PRISM_PCMCIA_DEVICE || DEVICE_ID == PRISM_DEVICE + xn_wlan_setup(interface, /* iface_no: value returned by xn_interface_open_config() */ + "network name", /* SSID : network name set in the access point */ + "station name", /* Name : name of this node */ + 0, /* Channel : 0 for access points, 1..14 for ad-hoc */ + 0, /* KeyIndex: 0 .. 3 */ + "12345", /* WEP Key : key to use (5 or 13 bytes) */ + 0); /* Flags : see manual and Wlanapi.h for details */ + Sleep(1000); /* wireless devices need a little time before they can be used */ +#endif /* WLAN device */ + +#if defined(AUTO_IP) /* use xn_autoip() to get an IP address */ + Result = xn_autoip(interface, MinIP, MaxIP, NetMask, TargetIP); + if (Result == SOCKET_ERROR) + Error("xn_autoip failed"); + else { + printf("Auto-assigned IP address %i.%i.%i.%i\n", TargetIP[0], + TargetIP[1], TargetIP[2], TargetIP[3]); + /* define default gateway and DNS server */ + xn_rt_add(RT_DEFAULT, ip_ffaddr, DefaultGateway, 1, interface, + RT_INF); + xn_set_server_list((DWORD *) DNSServer, 1); + } +#elif defined(DHCP) /* use DHCP */ + { + DHCP_param param[] = { {SUBNET_MASK, 1} + , {DNS_OP, 1} + , {ROUTER_OPTION, 1} + }; + DHCP_session DS; + DHCP_conf DC; + + xn_init_dhcp_conf(&DC); /* load default DHCP options */ + DC.plist = param; /* add MASK, DNS, and gateway options */ + DC.plist_entries = sizeof(param) / sizeof(param[0]); + printf("Contacting DHCP server, please wait...\n"); + Result = xn_dhcp(interface, &DS, &DC); /* contact DHCP server */ + if (Result == SOCKET_ERROR) + Error("xn_dhcp failed"); + memcpy(TargetIP, DS.client_ip, 4); + printf("My IP address is: %i.%i.%i.%i\n", TargetIP[0], TargetIP[1], + TargetIP[2], TargetIP[3]); + } +#else + /* Set the IP address and interface */ + printf("Using static IP address %i.%i.%i.%i\n", TargetIP[0], + TargetIP[1], TargetIP[2], TargetIP[3]); + Result = xn_set_ip(interface, TargetIP, NetMask); + /* define default gateway and DNS server */ + xn_rt_add(RT_DEFAULT, ip_ffaddr, DefaultGateway, 1, interface, RT_INF); + xn_set_server_list((DWORD *) DNSServer, 1); +#endif + +#else /* HOST defined, run on Windows */ + + WSADATA wd; + Result = WSAStartup(0x0101, &wd); + +#endif + + if (Result != 0) + Error("TCP/IP stack initialization failed"); +} +#endif + +/****************************************************************** +* DESCRIPTION: Converts the byte stored address to an inet address +* RETURN: none +* ALGORITHM: none +* NOTES: none +******************************************************************/ +static void RTIP_To_Network_Address(BYTE * octet_address, + struct in_addr *addr) +{ + uint32_t ip_address = 0; /* for decoding the subnet mask */ + + decode_unsigned32(octet_address, &ip_address); + addr->s_addr = htonl(ip_address); + + return; +} + +static void set_broadcast_address(uint32_t net_address) +{ + long broadcast_address = 0; + long mask = 0; + + /* Note: sometimes INADDR_BROADCAST does not let me get + any unicast messages. Not sure why... */ +#if USE_INADDR + (void) net_address; + bip_set_broadcast_addr(INADDR_BROADCAST); +#else + if (IN_CLASSA(ntohl(net_address))) + broadcast_address = + (ntohl(net_address) & ~IN_CLASSA_HOST) | IN_CLASSA_HOST; + else if (IN_CLASSB(ntohl(net_address))) + broadcast_address = + (ntohl(net_address) & ~IN_CLASSB_HOST) | IN_CLASSB_HOST; + else if (IN_CLASSC(ntohl(net_address))) + broadcast_address = + (ntohl(net_address) & ~IN_CLASSC_HOST) | IN_CLASSC_HOST; + else if (IN_CLASSD(ntohl(net_address))) + broadcast_address = + (ntohl(net_address) & ~IN_CLASSD_HOST) | IN_CLASSD_HOST; + else + broadcast_address = INADDR_BROADCAST; + bip_set_broadcast_addr(htonl(broadcast_address)); +#endif +} + +bool bip_init(char *ifname) +{ + int rv = 0; /* return from socket lib calls */ + struct sockaddr_in sin = { -1 }; + int value = 1; + int sock_fd = -1; + struct in_addr my_addr; + + (void)ifname; + + NetInitialize(); + + RTIP_To_Network_Address(TargetIP, &my_addr); + bip_set_addr(my_addr.s_addr); + set_broadcast_address(my_addr.s_addr); + bip_set_port(0xBAC0); + + /* assumes that the driver has already been initialized */ + sock_fd = socket(AF_INET, SOCK_DGRAM, IPROTO_UDP); + bip_set_socket(sock_fd); + if (sock_fd < 0) + return false; + + /* bind the socket to the local port number and IP address */ + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + sin.sin_port = htons(bip_get_port()); + memset(&(sin.sin_zero), '\0', 8); + rv = bind(sock_fd, + (const struct sockaddr *) &sin, sizeof(struct sockaddr)); + if (rv < 0) { + close(sock_fd); + bip_set_socket(-1); + return false; + } + + return true; +} diff --git a/bacnet-stack/ports/rtos32/dlmstp.c b/bacnet-stack/ports/rtos32/dlmstp.c index e69de29b..8775972e 100644 --- a/bacnet-stack/ports/rtos32/dlmstp.c +++ b/bacnet-stack/ports/rtos32/dlmstp.c @@ -0,0 +1,268 @@ +/************************************************************************** +* +* Copyright (C) 2006 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. +* +*********************************************************************/ +#include +#include +#include +#include +#if PRINT_ENABLED +#include +#endif +#include "bacdef.h" +#include "mstp.h" +#include "dlmstp.h" +#include "rs485.h" +#include "npdu.h" + +/* receive buffer */ +static DLMSTP_PACKET Receive_Buffer; +/* temp buffer for NPDU insertion */ +static uint8_t PDU_Buffer[MAX_MPDU]; +/* local MS/TP port data */ +static volatile struct mstp_port_struct_t MSTP_Port; + +void dlmstp_init(char *ifname) +{ + (void)ifname; + /* initialize buffer */ + Receive_Buffer.ready = false; + Receive_Buffer.pdu_len = 0; + /* initialize hardware */ + RS485_Initialize(); + MSTP_Init(&MSTP_Port, MSTP_Port.This_Station); +} + +void dlmstp_cleanup(void) +{ + /* nothing to do for static buffers */ +} + +/* returns number of bytes sent on success, zero on failure */ +int dlmstp_send_pdu(BACNET_ADDRESS * dest, /* destination address */ + BACNET_NPDU_DATA * npdu_data, /* network information */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len) +{ /* number of bytes of data */ + int bytes_sent = 0; + uint8_t frame_type = 0; + uint8_t destination = 0; /* destination address */ + BACNET_ADDRESS src; + unsigned mtu_len = 0; + + if (MSTP_Port.TxReady == false) { + if (npdu_data->confirmed_message) + MSTP_Port.TxFrameType = FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY; + else + MSTP_Port.TxFrameType = + FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY; + + /* load destination MAC address */ + if (dest && dest->mac_len == 1) { + destination = dest->mac[0]; + } else { +#if PRINT_ENABLED + fprintf(stderr, "mstp: invalid destination MAC address!\n"); +#endif + return -2; + } + /* header len */ + mtu_len = 8; + if ((mtu_len + pdu_len) > MAX_MPDU) { +#if PRINT_ENABLED + fprintf(stderr, "mstp: PDU is too big to send!\n"); +#endif + return -4; + } + memmove(&PDU_Buffer[mtu_len], pdu, pdu_len); + mtu_len += pdu_len; + bytes_sent = MSTP_Create_Frame( + (uint8_t *) & MSTP_Port.TxBuffer[0], + sizeof(MSTP_Port.TxBuffer), + MSTP_Port.TxFrameType, + destination, MSTP_Port.This_Station, &PDU_Buffer[0], mtu_len); + MSTP_Port.TxLength = bytes_sent; + MSTP_Port.TxReady = true; + } + + return bytes_sent; +} + +/* called about once a millisecond */ +void dlmstp_millisecond_timer(void) +{ + MSTP_Millisecond_Timer(&MSTP_Port); +} + +/* returns the number of octets in the PDU, or zero on failure */ +/* This function is expecting to be polled. */ +uint16_t dlmstp_receive(BACNET_ADDRESS * src, /* source address */ + uint8_t * pdu, /* PDU data */ + uint16_t max_pdu, /* amount of space available in the PDU */ + unsigned timeout) +{ + uint16_t pdu_len = 0; + + (void) timeout; + /* only do receive state machine while we don't have a frame */ + if ((MSTP_Port.ReceivedValidFrame == false) && + (MSTP_Port.ReceivedInvalidFrame == false)) { + RS485_Check_UART_Data(&MSTP_Port); + MSTP_Receive_Frame_FSM(&MSTP_Port); + } + /* only do master state machine while rx is idle */ + if (MSTP_Port.receive_state == MSTP_RECEIVE_STATE_IDLE) { + while (MSTP_Master_Node_FSM(&MSTP_Port)) { + }; + } + /* see if there is a packet available */ + if (Receive_Buffer.ready) { + memmove(src, &Receive_Buffer.address, + sizeof(Receive_Buffer.address)); + pdu_len = Receive_Buffer.pdu_len; + memmove(&pdu[0], &Receive_Buffer.pdu[0], max_pdu); + Receive_Buffer.ready = false; + } + + return pdu_len; +} + +void dlmstp_fill_bacnet_address(BACNET_ADDRESS * src, uint8_t mstp_address) +{ + int i = 0; + + if (mstp_address == MSTP_BROADCAST_ADDRESS) { + /* mac_len = 0 if broadcast address */ + src->mac_len = 0; + src->mac[0] = 0; + } else { + src->mac_len = 1; + src->mac[0] = mstp_address; + } + /* fill with 0's starting with index 1; index 0 filled above */ + for (i = 1; i < MAX_MAC_LEN; i++) { + src->mac[i] = 0; + } + src->net = 0; + src->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + src->adr[i] = 0; + } +} + +/* for the MS/TP state machine to use for putting received data */ +uint16_t dlmstp_put_receive(uint8_t src, /* source MS/TP address */ + uint8_t * pdu, /* PDU data */ + uint16_t pdu_len) +{ + if (Receive_Buffer.ready) { + /* FIXME: what to do when we miss a message? */ + pdu_len = 0; + } else if (pdu_len < sizeof(Receive_Buffer.pdu)) { + dlmstp_fill_bacnet_address(&Receive_Buffer.address, src); + Receive_Buffer.pdu_len = pdu_len; + memmove(Receive_Buffer.pdu, pdu, pdu_len); + Receive_Buffer.ready = true; + } else { + /* FIXME: message too large? */ + pdu_len = 0; + } + + return pdu_len; +} + +void dlmstp_set_my_address(uint8_t mac_address) +{ + /* FIXME: Master Nodes can only have address 1-127 */ + MSTP_Port.This_Station = mac_address; + + return; +} + +/* This parameter represents the value of the Max_Info_Frames property of */ +/* the node's Device object. The value of Max_Info_Frames specifies the */ +/* maximum number of information frames the node may send before it must */ +/* pass the token. Max_Info_Frames may have different values on different */ +/* nodes. This may be used to allocate more or less of the available link */ +/* bandwidth to particular nodes. If Max_Info_Frames is not writable in a */ +/* node, its value shall be 1. */ +void dlmstp_set_max_info_frames(unsigned max_info_frames) +{ + MSTP_Port.Nmax_info_frames = max_info_frames; + + return; +} + +unsigned dlmstp_max_info_frames(void) +{ + return MSTP_Port.Nmax_info_frames; +} + +/* This parameter represents the value of the Max_Master property of the */ +/* node's Device object. The value of Max_Master specifies the highest */ +/* allowable address for master nodes. The value of Max_Master shall be */ +/* less than or equal to 127. If Max_Master is not writable in a node, */ +/* its value shall be 127. */ +void dlmstp_set_max_master(uint8_t max_master) +{ + MSTP_Port.Nmax_master = max_master; + + return; +} + +uint8_t dlmstp_max_master(void) +{ + return MSTP_Port.Nmax_master; +} + +void dlmstp_get_my_address(BACNET_ADDRESS * my_address) +{ + int i = 0; /* counter */ + + my_address->mac_len = 1; + my_address->mac[0] = MSTP_Port.This_Station; + my_address->net = 0; /* local only, no routing */ + my_address->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + my_address->adr[i] = 0; + } + + return; +} + +void dlmstp_get_broadcast_address(BACNET_ADDRESS * dest) +{ /* destination address */ + int i = 0; /* counter */ + + if (dest) { + dest->mac_len = 1; + dest->mac[0] = MSTP_BROADCAST_ADDRESS; + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; /* len=0 denotes broadcast address */ + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = 0; + } + } + + return; +} diff --git a/bacnet-stack/ports/rtos32/ethernet.c b/bacnet-stack/ports/rtos32/ethernet.c index e69de29b..5b61f510 100644 --- a/bacnet-stack/ports/rtos32/ethernet.c +++ b/bacnet-stack/ports/rtos32/ethernet.c @@ -0,0 +1,337 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ + +#include /* for standard integer types uint8_t etc. */ +#include /* for the standard bool type. */ +#include /* for the standard bool type. */ +#include /* for the standard bool type. */ +#include +#include +#include +#include +#include +#include "ethernet.h" +#include "bacdcode.h" +#include "npdu.h" + +/* commonly used comparison address for ethernet */ +uint8_t Ethernet_Broadcast[MAX_MAC_LEN] = + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; +/* commonly used empty address for ethernet quick compare */ +uint8_t Ethernet_Empty_MAC[MAX_MAC_LEN] = { 0, 0, 0, 0, 0, 0 }; + +/* my local device data - MAC address */ +uint8_t Ethernet_MAC_Address[MAX_MAC_LEN] = { 0, 0, 0, 0, 0, 0 }; + +static SOCKET Ethernet_Socket = -1; +/* used for binding 802.2 */ +static struct sockaddr Ethernet_Address = { 0 }; + +bool ethernet_valid(void) +{ + return (Ethernet_Socket != -1); +} + +void ethernet_cleanup(void) +{ + if (ethernet_valid()) + closesocket(Ethernet_Socket); + Ethernet_Socket = -1; + + return; +} + +bool ethernet_init(char *interface_name) +{ + int value = 1; + + (void) interface_name; + /* setup the socket */ + Ethernet_Socket = socket(AF_INET, SOCK_RAW, 0); + /*Ethernet_Socket = socket(AF_INET, SOCK_STREAM, 0); */ + if (Ethernet_Socket < 0) + fprintf(stderr, "ethernet: failed to bind to socket!\r\n"); + Ethernet_Address.sa_family = AF_INET; + memset(Ethernet_Address.sa_data, 0, sizeof(Ethernet_Address.sa_data)); + if (bind(Ethernet_Socket, + &Ethernet_Address, sizeof(Ethernet_Address)) == SOCKET_ERROR) + fprintf(stderr, "ethernet: failed to bind to socket!\r\n"); + /*setsockopt(Ethernet_Socket,SOL_SOCKET,SO_802_2,(char *)&value,sizeof(value)); */ + + return ethernet_valid(); +} + +/* function to send a packet out the 802.2 socket */ +/* returns bytes sent on success, negative number on failure */ +int ethernet_send(BACNET_ADDRESS * dest, /* destination address */ + BACNET_ADDRESS * src, /* source address */ + BACNET_NPDU_DATA * npdu_data, /* network information */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len) +{ /* number of bytes of data */ + int bytes = 0; + uint8_t mtu[MAX_MPDU] = { 0 }; + int mtu_len = 0; + int i = 0; + + (void) npdu_data; + /* don't waste time if the socket is not valid */ + if (Ethernet_Socket < 0) { + fprintf(stderr, "ethernet: 802.2 socket is invalid!\n"); + return -1; + } + /* load destination ethernet MAC address */ + if (dest->mac_len == 6) { + for (i = 0; i < 6; i++) { + mtu[mtu_len] = dest->mac[i]; + mtu_len++; + } + } else { + fprintf(stderr, "ethernet: invalid destination MAC address!\n"); + return -2; + } + + /* load source ethernet MAC address */ + if (src->mac_len == 6) { + for (i = 0; i < 6; i++) { + mtu[mtu_len] = src->mac[i]; + mtu_len++; + } + } else { + fprintf(stderr, "ethernet: invalid source MAC address!\n"); + return -3; + } + if ((14 + 3 + pdu_len) > MAX_MPDU) { + fprintf(stderr, "ethernet: PDU is too big to send!\n"); + return -4; + } + /* packet length */ + mtu_len += encode_unsigned16(&mtu[12], + 3 /*DSAP,SSAP,LLC */ + pdu_len); + /* Logical PDU portion */ + mtu[mtu_len++] = 0x82; /* DSAP for BACnet */ + mtu[mtu_len++] = 0x82; /* SSAP for BACnet */ + mtu[mtu_len++] = 0x03; /* Control byte in header */ + mtu_len = 17; + if ((mtu_len + pdu_len) > MAX_MPDU) { + fprintf(stderr, "ethernet: PDU is too big to send!\n"); + return -4; + } + memcpy(&mtu[mtu_len], pdu, pdu_len); + mtu_len += pdu_len; + /* packet length - only the logical portion, not the address */ + encode_unsigned16(&mtu[12], 3 + pdu_len); + + /* Send the packet */ + bytes = send(Ethernet_Socket, (const char *) &mtu, mtu_len, 0); + /* did it get sent? */ + if (bytes < 0) + fprintf(stderr, "ethernet: Error sending packet: %s\n", + strerror(errno)); + + return bytes; +} + +/* function to send a packet out the 802.2 socket */ +/* returns bytes sent on success, negative number on failure */ +int ethernet_send_pdu(BACNET_ADDRESS * dest, /* destination address */ + BACNET_NPDU_DATA * npdu_data, /* network information */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len) +{ /* number of bytes of data */ + int i = 0; /* counter */ + BACNET_ADDRESS src = { 0 }; /* source address */ + + for (i = 0; i < 6; i++) { + src.mac[i] = Ethernet_MAC_Address[i]; + src.mac_len++; + } + + /* FIXME: npdu_data? */ + /* function to send a packet out the 802.2 socket */ + /* returns 1 on success, 0 on failure */ + return ethernet_send(dest, /* destination address */ + &src, /* source address */ + npdu_data, pdu, /* any data to be sent - may be null */ + pdu_len); /* number of bytes of data */ +} + +/* receives an 802.2 framed packet */ +/* returns the number of octets in the PDU, or zero on failure */ +uint16_t ethernet_receive(BACNET_ADDRESS * src, /* source address */ + uint8_t * pdu, /* PDU data */ + uint16_t max_pdu, /* amount of space available in the PDU */ + unsigned timeout) +{ /* number of milliseconds to wait for a packet */ + int received_bytes; + uint8_t buf[MAX_MPDU] = { 0 }; /* data */ + uint16_t pdu_len = 0; /* return value */ + fd_set read_fds; + int max; + struct timeval select_timeout; + + /* Make sure the socket is open */ + if (Ethernet_Socket <= 0) + return 0; + + /* we could just use a non-blocking socket, but that consumes all + the CPU time. We can use a timeout; it is only supported as + a select. */ + if (timeout >= 1000) { + select_timeout.tv_sec = timeout / 1000; + select_timeout.tv_usec = + 1000 * (timeout - select_timeout.tv_sec * 1000); + } else { + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 1000 * timeout; + } + FD_ZERO(&read_fds); + FD_SET(Ethernet_Socket, &read_fds); + max = Ethernet_Socket; + + if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) + received_bytes = + recv(Ethernet_Socket, (char *) &buf[0], MAX_MPDU, 0); + else + return 0; + + /* See if there is a problem */ + if (received_bytes < 0) { + /* EAGAIN Non-blocking I/O has been selected */ + /* using O_NONBLOCK and no data */ + /* was immediately available for reading. */ + if (errno != EAGAIN) + fprintf(stderr, + "ethernet: Read error in receiving packet: %s\n", + strerror(errno)); + return 0; + } + + if (received_bytes == 0) + return 0; + + /* the signature of an 802.2 BACnet packet */ + if ((buf[14] != 0x82) && (buf[15] != 0x82)) { + /*fprintf(stderr,"ethernet: Non-BACnet packet\n"); */ + return 0; + } + /* copy the source address */ + src->mac_len = 6; + memmove(src->mac, &buf[6], 6); + + /* check destination address for when */ + /* the Ethernet card is in promiscious mode */ + if ((memcmp(&buf[0], Ethernet_MAC_Address, 6) != 0) + && (memcmp(&buf[0], Ethernet_Broadcast, 6) != 0)) { + /*fprintf(stderr, "ethernet: This packet isn't for us\n"); */ + return 0; + } + + (void) decode_unsigned16(&buf[12], &pdu_len); + pdu_len -= 3 /* DSAP, SSAP, LLC Control */ ; + /* copy the buffer into the PDU */ + if (pdu_len < max_pdu) + memmove(&pdu[0], &buf[17], pdu_len); + /* ignore packets that are too large */ + /* client should check my max apdu first */ + else + pdu_len = 0; + + return pdu_len; +} + +void ethernet_get_my_address(BACNET_ADDRESS * my_address) +{ + int i = 0; + + my_address->mac_len = 0; + for (i = 0; i < 6; i++) { + my_address->mac[i] = Ethernet_MAC_Address[i]; + my_address->mac_len++; + } + my_address->net = 0; /* local only, no routing */ + my_address->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + my_address->adr[i] = 0; + } + + return; +} + +void ethernet_set_my_address(BACNET_ADDRESS * my_address) +{ + int i = 0; + + for (i = 0; i < 6; i++) { + Ethernet_MAC_Address[i] = my_address->mac[i]; + } + + return; +} + +void ethernet_get_broadcast_address(BACNET_ADDRESS * dest) +{ /* destination address */ + int i = 0; /* counter */ + + if (dest) { + for (i = 0; i < 6; i++) { + dest->mac[i] = Ethernet_Broadcast[i]; + } + dest->mac_len = 6; + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; /* denotes broadcast address */ + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = 0; + } + } + + return; +} + +void ethernet_debug_address(const char *info, BACNET_ADDRESS * dest) +{ + int i = 0; /* counter */ + + if (info) + fprintf(stderr, "%s", info); + if (dest) { + fprintf(stderr, "Address:\n"); + fprintf(stderr, " MAC Length=%d\n", dest->mac_len); + fprintf(stderr, " MAC Address="); + for (i = 0; i < MAX_MAC_LEN; i++) { + fprintf(stderr, "%02X ", (unsigned) dest->mac[i]); + } + fprintf(stderr, "\n"); + fprintf(stderr, " Net=%hu\n", dest->net); + fprintf(stderr, " Len=%d\n", dest->len); + fprintf(stderr, " Adr="); + for (i = 0; i < MAX_MAC_LEN; i++) { + fprintf(stderr, "%02X ", (unsigned) dest->adr[i]); + } + fprintf(stderr, "\n"); + } + + return; +} diff --git a/bacnet-stack/ports/rtos32/init.c b/bacnet-stack/ports/rtos32/init.c index e69de29b..e4da3c1b 100644 --- a/bacnet-stack/ports/rtos32/init.c +++ b/bacnet-stack/ports/rtos32/init.c @@ -0,0 +1,124 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ + +#include +#include +#include + +extern void RTEmuInit(void); + +#ifdef _MSC_VER +#define VOIDEXPORT _declspec(dllexport) void __cdecl +#else +#define VOIDEXPORT void __export __cdecl +#endif + +/* DISK SYSTEM */ +#ifdef DOC /* include DiskOnChip driver */ +#include +#define RTF_MAX_FILES 16 /* support for more open files (default is 8) */ +#define RTF_BUFFERS_IN_BSS /* we do not need file I/O before the run-time */ +#include /* system is initialized */ + + /*#define READ_HEAD_BUFFER_SIZE 2048+4 */ + + /*static BYTE ReadAheadBuffer[READ_HEAD_BUFFER_SIZE]; */ + +static RTFDrvFLPYData FLPYDriveAData = { 0 }; +static RTFDrvDOCData DOCDriveData = { 0 }; +static RTFDrvIDEData IDEDriveData = { 0 }; + +RTFDevice RTFDeviceList[] = { + /* type,number,flags,driver,driverdata */ + {RTF_DEVICE_FLOPPY, 0, 0, &RTFDrvFloppy, &FLPYDriveAData}, + {RTF_DEVICE_FDISK, 0, 0, &RTFDrvDOC, &DOCDriveData}, + {RTF_DEVICE_FDISK, 0, 0, &RTFDrvIDE, &IDEDriveData}, + {0, 0, 0, NULL, NULL} +}; +#endif +/* END OF DISK SYSTEM */ + +/* RTTarget only defines 64 Win32 handles, which are not enough for BACstac */ +/* the following code is from the RTTarget manual, page 106 */ +#define MAXHANDLES 1024 +#define MAXOBJECTS 1024 +#define MAXTYPES 32 + +RTW32Handle RTHandleTable[MAXHANDLES] = { {0} }; +int RTHandleCount = MAXHANDLES; + +RTW32Object RTObjectTable[MAXOBJECTS] = { {0} }; +int RTObjectCount = MAXOBJECTS; + +RTW32Types RTTypeTable[MAXTYPES] = { {0} }; +int RTTypeCount = MAXTYPES; + +#if 0 +/* We can embed some files in the RTB file, like a binary + file used for configuring a remote device, using + 'Locate File filename HighMem' in the config (.CFG) file. + However, the default setup for RTFiles and RTTarget + doesn't include the RAM files, so we need to specify + that here, as well as the LPT, console, and FAT. + From RTFiles-32 manual, ch. 7, "Using RTFiles-32 with + RTTarget-32" */ +RTFileSystem Console = { RT_FS_CONSOLE, 0, 0, &RTConsoleFileSystem }; + +RTFileSystem LPTFiles = { RT_FS_LPT_DEVICE, 0, 0, &RTLPTFileSystem }; + +/* logical drive Z: can be used to access the RAM drive */ +RTFileSystem RAMFiles = + { RT_FS_FILE, 1 << ('Z' - 'A'), 0, &RTRAMFileSystem }; + +/* logical drive A: through D: are reserved for FAT */ +RTFileSystem FATFiles = + { RT_FS_FILE | RT_FS_IS_DEFAULT, 0x0F, 0x03, &RTFilesFileSystem }; + +RTFileSystem *RTFileSystemList[] = { + &Console, + &LPTFiles, + &RAMFiles, + &FATFiles, + NULL, +}; +#endif + +/*-----------------------------------*/ +VOIDEXPORT Init(void) +{ + (void) RTSetFlags(RT_MM_VIRTUAL, 1); /* this is the better method */ + (void) RTCMOSExtendHeap(); /* get as much memory as we can */ + RTCMOSSetSystemTime(); /* get the right date and time */ + RTEmuInit(); /* set up floating point emulation */ + /* pizza - RTHaltCPL3 appears to cause problems with file handling */ + /*RTIdleHandler = (void RTTAPI *)RTHaltCPL3; // low power when idle */ + /* not needed with pre-emptive */ + /*RTKTimeSlice(2); // allow same priority task switch */ + RTKConfig.Flags |= RF_PREEMPTIVE; /* preemptive multitasking */ + RTKConfig.Flags |= RF_WIN32MUTEX_MUTEX; /* Win32 mutexes are RTK32 mutexes */ + RTKConfig.Flags |= RF_FPCONTEXT; /* saves floating point context for tasks */ + RTKConfig.HookedIRQs |= 1 << 1; /* hook the keyboard IRQ */ + RTKConfig.DefaultTaskStackSize = 1024 * 8; /* for Win32 task stacks req = 0 */ +} diff --git a/bacnet-stack/ports/rtos32/main.c b/bacnet-stack/ports/rtos32/main.c index e69de29b..9807c704 100644 --- a/bacnet-stack/ports/rtos32/main.c +++ b/bacnet-stack/ports/rtos32/main.c @@ -0,0 +1,164 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ + +/* This is one way to use the embedded BACnet stack under RTOS-32 */ +/* compiled with Borland C++ 5.02 */ +#include +#include +#include +#include /* for kbhit */ +#include "config.h" +#include "bacdef.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "handlers.h" +#include "datalink.h" +#include "iam.h" +#include "txbuf.h" + +/* RTOS-32 */ +#include "rtkernel.h" +#if defined(RTK32_VER) +#define _USER32_ +#define _KERNEL32_ +#include +#include /* for RTCMOSSetSystemTime */ +#include /* file system */ +#include /* file system */ +#include +#endif +#include /* serial port driver */ +#include /* time measurement & timer interrupt rate control */ +#include /* interrupt handler for the keyboard */ + +/* buffers used for transmit and receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +static void Init_Service_Handlers(void) +{ + /* we need to handle who-is to support dynamic device binding */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, + handler_who_is); + /* 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); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, + handler_write_property); +} + +void millisecond_task(void) +{ + Time ticks = 0; /* task cycle */ + int i = 0; /* loop counter */ + + ticks = RTKGetTime() + MilliSecsToTicks(1); + while (TRUE) { + RTKDelayUntil(ticks); + dlmstp_millisecond_timer(); + ticks += MilliSecsToTicks(1); + } +} + +void RTOS_Initialize(void) +{ + /* allow OS to setup IRQ 1 by using a dummy call */ + (void) kbhit(); + RTKernelInit(5); /* get the kernel going */ + RTKeybrdInit(); + /*(void)CPUMoniInit(); /* not needed - just monitor idle task */ */ + RTComInit(); + ITimerInit(); + + if (RTCallDebugger(RT_DBG_MONITOR, 0, 0) != -1) { + /* Win32 structured exception - if no handler is + installed, TerminateProcess() will be called, + which will reboot - a good thing in our case. */ + RTRaiseCPUException(0); /* Divide Error DIV and IDIV instructions. */ + RTRaiseCPUException(1); /* Debug Any code or data reference. */ + RTRaiseCPUException(2); /* NMI */ + RTRaiseCPUException(3); /* Breakpoint INT 3 instruction. */ + RTRaiseCPUException(4); /* Overflow INTO instruction. */ + RTRaiseCPUException(5); /* BOUND Range Exceeded BOUND instruction. */ + RTRaiseCPUException(6); /* Invalid Opcode (Undefined Opcode) */ + /* RTRaiseCPUException(7); // Device Not Available (No Math Coprocessor) */ + RTRaiseCPUException(8); /* Double Fault any exception instruction,NMI,INTR. */ + RTRaiseCPUException(9); /* Co-Processor overrun */ + RTRaiseCPUException(10); /* Invalid TSS Task switch or TSS access. */ + RTRaiseCPUException(11); /* Segment Not Present Loading segment registers */ + RTRaiseCPUException(12); /* Stack Seg Fault Stack ops /SS reg loads. */ + RTRaiseCPUException(13); /* General Protection Any memory reference */ + RTRaiseCPUException(14); /* Page Fault Any memory reference. */ + RTRaiseCPUException(15); /* reserved */ + RTRaiseCPUException(16); /* Floating-Point Error (Math Fault) */ + } + /* setup 1ms timer tick */ + SetTimerIntVal(1000); + /* per recommendation in manual */ + RTKDelay(1); + RTCMOSSetSystemTime(); /* get the right time-of-day */ + + /* create timer tick task */ + RTKCreateTask(millisecond_task, 16, 1024 * 8, "millisec task"); +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + uint16_t pdu_len = 0; + unsigned timeout = 100; /* milliseconds */ + + (void) argc; + (void) argv; + Device_Set_Object_Instance_Number(126); + Init_Service_Handlers(); + RTOS_Initialize(); + /* init the physical layer */ +#ifdef BACDL_MSTP + dlmstp_set_my_address(0x05); +#endif + datalink_init(NULL); + iam_send(&Handler_Transmit_Buffer[0]); + /* loop forever */ + for (;;) { + /* input */ + + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + /* output */ + + + + /* blink LEDs, Turn on or off outputs, etc */ + } +} diff --git a/bacnet-stack/ports/rtos32/mstp.c b/bacnet-stack/ports/rtos32/mstp.c index e69de29b..5303eabf 100644 --- a/bacnet-stack/ports/rtos32/mstp.c +++ b/bacnet-stack/ports/rtos32/mstp.c @@ -0,0 +1,1749 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2003 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307 + USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +/* This clause describes a Master-Slave/Token-Passing (MS/TP) data link */ +/* protocol, which provides the same services to the network layer as */ +/* ISO 8802-2 Logical Link Control. It uses services provided by the */ +/* EIA-485 physical layer. Relevant clauses of EIA-485 are deemed to be */ +/* included in this standard by reference. The following hardware is assumed: */ +/* (a) A UART (Universal Asynchronous Receiver/Transmitter) capable of */ +/* transmitting and receiving eight data bits with one stop bit */ +/* and no parity. */ +/* (b) An EIA-485 transceiver whose driver may be disabled. */ +/* (c) A timer with a resolution of five milliseconds or less */ + +#include +#include +#if PRINT_ENABLED +#include +#endif +#include "mstp.h" +#include "bytes.h" +#include "crc.h" +#include "rs485.h" + +/* debug print statements */ +#if PRINT_ENABLED +#define PRINT_ENABLED_RECEIVE 0 +#define PRINT_ENABLED_RECEIVE_DATA 1 +#define PRINT_ENABLED_MASTER 0 +#else +#define PRINT_ENABLED_RECEIVE 0 +#define PRINT_ENABLED_RECEIVE_DATA 0 +#define PRINT_ENABLED_MASTER 0 +#endif + +/* MS/TP Frame Format */ +/* All frames are of the following format: */ +/* */ +/* Preamble: two octet preamble: X`55', X`FF' */ +/* Frame Type: one octet */ +/* Destination Address: one octet address */ +/* Source Address: one octet address */ +/* Length: two octets, most significant octet first, of the Data field */ +/* Header CRC: one octet */ +/* Data: (present only if Length is non-zero) */ +/* Data CRC: (present only if Length is non-zero) two octets, */ +/* least significant octet first */ +/* (pad): (optional) at most one octet of padding: X'FF' */ + +/* The number of tokens received or used before a Poll For Master cycle */ +/* is executed: 50. */ +const unsigned Npoll = 50; + +/* The number of retries on sending Token: 1. */ +const unsigned Nretry_token = 1; + +/* The minimum number of DataAvailable or ReceiveError events that must be */ +/* seen by a receiving node in order to declare the line "active": 4. */ +const uint8_t Nmin_octets = 4; + +/* The minimum time without a DataAvailable or ReceiveError event within */ +/* a frame before a receiving node may discard the frame: 60 bit times. */ +/* (Implementations may use larger values for this timeout, */ +/* not to exceed 100 milliseconds.) */ +/* At 9600 baud, 60 bit times would be about 6.25 milliseconds */ +/* const uint16_t Tframe_abort = 1 + ((1000 * 60) / 9600); */ +const uint16_t Tframe_abort = 30; + +/* The maximum idle time a sending node may allow to elapse between octets */ +/* of a frame the node is transmitting: 20 bit times. */ +const unsigned Tframe_gap = 20; + +/* The time without a DataAvailable or ReceiveError event before declaration */ +/* of loss of token: 500 milliseconds. */ +const uint16_t Tno_token = 500; + +/* The maximum time after the end of the stop bit of the final */ +/* octet of a transmitted frame before a node must disable its */ +/* EIA-485 driver: 15 bit times. */ +const unsigned Tpostdrive = 15; + +/* The maximum time a node may wait after reception of a frame that expects */ +/* a reply before sending the first octet of a reply or Reply Postponed */ +/* frame: 250 milliseconds. */ +const uint16_t Treply_delay = 10; + +/* The minimum time without a DataAvailable or ReceiveError event */ +/* that a node must wait for a station to begin replying to a */ +/* confirmed request: 255 milliseconds. (Implementations may use */ +/* larger values for this timeout, not to exceed 300 milliseconds.) */ +const uint16_t Treply_timeout = 255; + +/* Repeater turnoff delay. The duration of a continuous logical one state */ +/* at the active input port of an MS/TP repeater after which the repeater */ +/* will enter the IDLE state: 29 bit times < Troff < 40 bit times. */ +const unsigned Troff = 30; + +/* The width of the time slot within which a node may generate a token: */ +/* 10 milliseconds. */ +const uint16_t Tslot = 10; + +/* The maximum time a node may wait after reception of the token or */ +/* a Poll For Master frame before sending the first octet of a frame: */ +/* 15 milliseconds. */ +const uint16_t Tusage_delay = 15; + +/* The minimum time without a DataAvailable or ReceiveError event that a */ +/* node must wait for a remote node to begin using a token or replying to */ +/* a Poll For Master frame: 20 milliseconds. (Implementations may use */ +/* larger values for this timeout, not to exceed 100 milliseconds.) */ +const uint16_t Tusage_timeout = 30; + +/* we need to be able to increment without rolling over */ +#define INCREMENT_AND_LIMIT_UINT8(x) {if (x < 0xFF) x++;} +#define INCREMENT_AND_LIMIT_UINT16(x) {if (x < 0xFFFF) x++;} + + +bool MSTP_Line_Active(volatile struct mstp_port_struct_t *mstp_port) +{ + return (mstp_port->EventCount > Nmin_octets); +} + +unsigned MSTP_Create_Frame(uint8_t * buffer, /* where frame is loaded */ + unsigned buffer_len, /* amount of space available */ + uint8_t frame_type, /* type of frame to send - see defines */ + uint8_t destination, /* destination address */ + uint8_t source, /* source address */ + uint8_t * data, /* any data to be sent - may be null */ + unsigned data_len) +{ /* number of bytes of data (up to 501) */ + uint8_t crc8 = 0xFF; /* used to calculate the crc value */ + uint16_t crc16 = 0xFFFF; /* used to calculate the crc value */ + unsigned index = 0; /* used to load the data portion of the frame */ + + /* not enough to do a header */ + if (buffer_len < 8) + return 0; + + buffer[0] = 0x55; + buffer[1] = 0xFF; + buffer[2] = frame_type; + crc8 = CRC_Calc_Header(buffer[2], crc8); + buffer[3] = destination; + crc8 = CRC_Calc_Header(buffer[3], crc8); + buffer[4] = source; + crc8 = CRC_Calc_Header(buffer[4], crc8); + buffer[5] = data_len / 256; + crc8 = CRC_Calc_Header(buffer[5], crc8); + buffer[6] = data_len % 256; + crc8 = CRC_Calc_Header(buffer[6], crc8); + buffer[7] = ~crc8; + + index = 8; + while (data_len && data && (index < buffer_len)) { + buffer[index] = *data; + crc16 = CRC_Calc_Data(buffer[index], crc16); + data++; + index++; + data_len--; + } + /* append the data CRC if necessary */ + if (index > 8) { + if ((index + 2) <= buffer_len) { + crc16 = ~crc16; + buffer[index] = LO_BYTE(crc16); + index++; + buffer[index] = HI_BYTE(crc16); + index++; + } else + return 0; + } + + return index; /* returns the frame length */ +} + +void MSTP_Create_And_Send_Frame(volatile struct mstp_port_struct_t *mstp_port, /* port to send from */ + uint8_t frame_type, /* type of frame to send - see defines */ + uint8_t destination, /* destination address */ + uint8_t source, /* source address */ + uint8_t * data, /* any data to be sent - may be null */ + unsigned data_len) +{ /* number of bytes of data (up to 501) */ + uint8_t buffer[MAX_MPDU] = { 0 }; /* buffer for sending */ + uint16_t len = 0; /* number of bytes to send */ + + len = (uint16_t) MSTP_Create_Frame(&buffer[0], /* where frame is loaded */ + sizeof(buffer), /* amount of space available */ + frame_type, /* type of frame to send - see defines */ + destination, /* destination address */ + source, /* source address */ + data, /* any data to be sent - may be null */ + data_len); /* number of bytes of data (up to 501) */ + + RS485_Send_Frame(mstp_port, &buffer[0], len); + /* FIXME: be sure to reset SilenceTimer after each octet is sent! */ +} + +/* Millisecond Timer - called every millisecond */ +void MSTP_Millisecond_Timer(volatile struct mstp_port_struct_t *mstp_port) +{ + INCREMENT_AND_LIMIT_UINT16(mstp_port->SilenceTimer); + INCREMENT_AND_LIMIT_UINT16(mstp_port->ReplyPostponedTimer); + return; +} + +#if PRINT_ENABLED_RECEIVE +char *mstp_receive_state_text(int state) +{ + char *text = "unknown"; + + switch (state) { + case MSTP_RECEIVE_STATE_IDLE: + text = "IDLE"; + break; + case MSTP_RECEIVE_STATE_PREAMBLE: + text = "PREAMBLE"; + break; + case MSTP_RECEIVE_STATE_HEADER: + text = "HEADER"; + break; + case MSTP_RECEIVE_STATE_HEADER_CRC: + text = "HEADER_CRC"; + break; + case MSTP_RECEIVE_STATE_DATA: + text = "DATA"; + break; + default: + break; + } + + return text; +} +#endif + +void MSTP_Receive_Frame_FSM(volatile struct mstp_port_struct_t *mstp_port) +{ +#if PRINT_ENABLED_RECEIVE_DATA + static MSTP_RECEIVE_STATE receive_state = MSTP_RECEIVE_STATE_IDLE; +#endif +#if PRINT_ENABLED_RECEIVE + fprintf(stderr, + "MSTP Rx: State=%s Data=%02X hCRC=%02X Index=%u EC=%u DateLen=%u Silence=%u\n", + mstp_receive_state_text(mstp_port->receive_state), + mstp_port->DataRegister, mstp_port->HeaderCRC, mstp_port->Index, + mstp_port->EventCount, mstp_port->DataLength, + mstp_port->SilenceTimer); +#endif + switch (mstp_port->receive_state) { + /* In the IDLE state, the node waits for the beginning of a frame. */ + case MSTP_RECEIVE_STATE_IDLE: + /* EatAnError */ + if (mstp_port->ReceiveError == true) { + mstp_port->ReceiveError = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } else if (mstp_port->DataAvailable == true) { +#if PRINT_ENABLED_RECEIVE_DATA + fprintf(stderr, "MSTP Rx: %02X ", mstp_port->DataRegister); +#endif + /* Preamble1 */ + if (mstp_port->DataRegister == 0x55) { + mstp_port->DataAvailable = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* receive the remainder of the frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_PREAMBLE; + } + /* EatAnOctet */ + else { +#if PRINT_ENABLED_RECEIVE_DATA + fprintf(stderr, "\n"); +#endif + mstp_port->DataAvailable = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + } + break; + /* In the PREAMBLE state, the node waits for the second octet of the preamble. */ + case MSTP_RECEIVE_STATE_PREAMBLE: + /* Timeout */ + if (mstp_port->SilenceTimer > Tframe_abort) { + /* a correct preamble has not been received */ + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + /* Error */ + else if (mstp_port->ReceiveError == true) { + mstp_port->ReceiveError = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } else if (mstp_port->DataAvailable == true) { +#if PRINT_ENABLED_RECEIVE_DATA + fprintf(stderr, "%02X ", mstp_port->DataRegister); +#endif + /* Preamble2 */ + if (mstp_port->DataRegister == 0xFF) { + mstp_port->DataAvailable = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + mstp_port->Index = 0; + mstp_port->HeaderCRC = 0xFF; + /* receive the remainder of the frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_HEADER; + } + /* ignore RepeatedPreamble1 */ + else if (mstp_port->DataRegister == 0x55) { + mstp_port->DataAvailable = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* wait for the second preamble octet. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_PREAMBLE; + } + /* NotPreamble */ + else { + mstp_port->DataAvailable = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + } + break; + /* In the HEADER state, the node waits for the fixed message header. */ + case MSTP_RECEIVE_STATE_HEADER: + /* Timeout */ + if (mstp_port->SilenceTimer > Tframe_abort) { + /* indicate that an error has occurred during the reception of a frame */ + mstp_port->ReceivedInvalidFrame = true; + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + /* Error */ + else if (mstp_port->ReceiveError == true) { + mstp_port->ReceiveError = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* indicate that an error has occurred during the reception of a frame */ + mstp_port->ReceivedInvalidFrame = true; + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } else if (mstp_port->DataAvailable == true) { +#if PRINT_ENABLED_RECEIVE_DATA + fprintf(stderr, "%02X ", mstp_port->DataRegister); +#endif + /* FrameType */ + if (mstp_port->Index == 0) { + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + mstp_port->HeaderCRC = + CRC_Calc_Header(mstp_port->DataRegister, + mstp_port->HeaderCRC); + mstp_port->FrameType = mstp_port->DataRegister; + mstp_port->DataAvailable = false; + mstp_port->Index = 1; + mstp_port->receive_state = MSTP_RECEIVE_STATE_HEADER; + } + /* Destination */ + else if (mstp_port->Index == 1) { + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + mstp_port->HeaderCRC = + CRC_Calc_Header(mstp_port->DataRegister, + mstp_port->HeaderCRC); + mstp_port->DestinationAddress = mstp_port->DataRegister; + mstp_port->DataAvailable = false; + mstp_port->Index = 2; + mstp_port->receive_state = MSTP_RECEIVE_STATE_HEADER; + } + /* Source */ + else if (mstp_port->Index == 2) { + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + mstp_port->HeaderCRC = + CRC_Calc_Header(mstp_port->DataRegister, + mstp_port->HeaderCRC); + mstp_port->SourceAddress = mstp_port->DataRegister; + mstp_port->DataAvailable = false; + mstp_port->Index = 3; + mstp_port->receive_state = MSTP_RECEIVE_STATE_HEADER; + } + /* Length1 */ + else if (mstp_port->Index == 3) { + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + mstp_port->HeaderCRC = + CRC_Calc_Header(mstp_port->DataRegister, + mstp_port->HeaderCRC); + mstp_port->DataLength = mstp_port->DataRegister * 256; + mstp_port->DataAvailable = false; + mstp_port->Index = 4; + mstp_port->receive_state = MSTP_RECEIVE_STATE_HEADER; + } + /* Length2 */ + else if (mstp_port->Index == 4) { + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + mstp_port->HeaderCRC = + CRC_Calc_Header(mstp_port->DataRegister, + mstp_port->HeaderCRC); + mstp_port->DataLength += mstp_port->DataRegister; + mstp_port->DataAvailable = false; + mstp_port->Index = 5; + mstp_port->receive_state = MSTP_RECEIVE_STATE_HEADER; + } + /* HeaderCRC */ + else if (mstp_port->Index == 5) { + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + mstp_port->HeaderCRC = + CRC_Calc_Header(mstp_port->DataRegister, + mstp_port->HeaderCRC); + mstp_port->DataAvailable = false; + /* don't wait for next state - do it here */ + /* MSTP_RECEIVE_STATE_HEADER_CRC */ + if (mstp_port->HeaderCRC != 0x55) { + /* BadCRC */ + /* indicate that an error has occurred during the reception of a frame */ + mstp_port->ReceivedInvalidFrame = true; + /* wait for the start of the next frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } else { + if ((mstp_port->DestinationAddress == + mstp_port->This_Station) + || (mstp_port->DestinationAddress == + MSTP_BROADCAST_ADDRESS)) { + /* FrameTooLong */ + if (mstp_port->DataLength > MAX_MPDU) { + /* indicate that a frame with an illegal or */ + /* unacceptable data length has been received */ + mstp_port->ReceivedInvalidFrame = true; + /* wait for the start of the next frame. */ + mstp_port->receive_state = + MSTP_RECEIVE_STATE_IDLE; + } + /* NoData */ + else if (mstp_port->DataLength == 0) { + /* CHEAT: it is very difficult to respond to + poll for master in the Master Node state machine + before Tusage_timeout, so we will do it here. */ + if ((mstp_port->FrameType == + FRAME_TYPE_POLL_FOR_MASTER) + && (mstp_port->DestinationAddress == + mstp_port->This_Station) + && (mstp_port->master_state == + MSTP_MASTER_STATE_IDLE)) { + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_REPLY_TO_POLL_FOR_MASTER, + mstp_port->SourceAddress, + mstp_port->This_Station, NULL, 0); + /* don't indicate that a frame has been received */ + mstp_port->ReceivedInvalidFrame = false; + mstp_port->ReceivedValidFrame = false; + } else { + /* indicate that a frame with no data has been received */ + mstp_port->ReceivedValidFrame = true; + } + /* wait for the start of the next frame. */ + mstp_port->receive_state = + MSTP_RECEIVE_STATE_IDLE; + } + /* Data */ + else { + mstp_port->Index = 0; + mstp_port->DataCRC = 0xFFFF; + /* receive the data portion of the frame. */ + mstp_port->receive_state = + MSTP_RECEIVE_STATE_DATA; + } + } + /* NotForUs */ + else { + /* wait for the start of the next frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + } + + + } + /* not per MS/TP standard, but it is a case not covered */ + else { + mstp_port->ReceiveError = false; + mstp_port->SilenceTimer = 0; + INCREMENT_AND_LIMIT_UINT8(mstp_port->EventCount); + /* indicate that an error has occurred during */ + /* the reception of a frame */ + mstp_port->ReceivedInvalidFrame = true; + /* wait for the start of a frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + } + break; + /* In the HEADER_CRC state, the node validates the CRC on the fixed */ + /* message header. */ + case MSTP_RECEIVE_STATE_HEADER_CRC: + break; + /* In the DATA state, the node waits for the data portion of a frame. */ + case MSTP_RECEIVE_STATE_DATA: + /* Timeout */ + if (mstp_port->SilenceTimer > Tframe_abort) { + /* indicate that an error has occurred during the reception of a frame */ + mstp_port->ReceivedInvalidFrame = true; + /* wait for the start of the next frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + /* Error */ + else if (mstp_port->ReceiveError == true) { + mstp_port->ReceiveError = false; + mstp_port->SilenceTimer = 0; + /* indicate that an error has occurred during the reception of a frame */ + mstp_port->ReceivedInvalidFrame = true; + /* wait for the start of the next frame. */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } else if (mstp_port->DataAvailable == true) { +#if PRINT_ENABLED_RECEIVE_DATA + fprintf(stderr, "%02X ", mstp_port->DataRegister); +#endif + /* DataOctet */ + if (mstp_port->Index < mstp_port->DataLength) { + mstp_port->DataCRC = CRC_Calc_Data(mstp_port->DataRegister, + mstp_port->DataCRC); + mstp_port->InputBuffer[mstp_port->Index] = + mstp_port->DataRegister; + mstp_port->Index++; + mstp_port->receive_state = MSTP_RECEIVE_STATE_DATA; + } + /* CRC1 */ + else if (mstp_port->Index == mstp_port->DataLength) { + mstp_port->DataCRC = CRC_Calc_Data(mstp_port->DataRegister, + mstp_port->DataCRC); + mstp_port->Index++; + mstp_port->receive_state = MSTP_RECEIVE_STATE_DATA; + } + /* CRC2 */ + else if (mstp_port->Index == (mstp_port->DataLength + 1)) { + mstp_port->DataCRC = CRC_Calc_Data(mstp_port->DataRegister, + mstp_port->DataCRC); + /* STATE DATA CRC - no need for new state */ + /* indicate the complete reception of a valid frame */ + if (mstp_port->DataCRC == 0xF0B8) + mstp_port->ReceivedValidFrame = true; + else + mstp_port->ReceivedInvalidFrame = true; + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + } + mstp_port->DataAvailable = false; + mstp_port->SilenceTimer = 0; + } + break; + default: + /* shouldn't get here - but if we do... */ + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + break; + } +#if PRINT_ENABLED_RECEIVE_DATA + if ((receive_state != MSTP_RECEIVE_STATE_IDLE) && + (mstp_port->receive_state == MSTP_RECEIVE_STATE_IDLE)) { + fprintf(stderr, "\n"); + fflush(stderr); + } + receive_state = mstp_port->receive_state; +#endif + + return; +} + +#if PRINT_ENABLED +char *mstp_master_state_text(int state) +{ + char *text = "unknown"; + + switch (state) { + case MSTP_MASTER_STATE_INITIALIZE: + text = "INITIALIZE"; + break; + case MSTP_MASTER_STATE_IDLE: + text = "IDLE"; + break; + case MSTP_MASTER_STATE_USE_TOKEN: + text = "USE_TOKEN"; + break; + case MSTP_MASTER_STATE_WAIT_FOR_REPLY: + text = "WAIT_FOR_REPLY"; + break; + case MSTP_MASTER_STATE_DONE_WITH_TOKEN: + text = "IDLE"; + break; + case MSTP_MASTER_STATE_PASS_TOKEN: + text = "DONE_WITH_TOKEN"; + break; + case MSTP_MASTER_STATE_NO_TOKEN: + text = "NO_TOKEN"; + break; + case MSTP_MASTER_STATE_POLL_FOR_MASTER: + text = "POLL_FOR_MASTER"; + break; + case MSTP_MASTER_STATE_ANSWER_DATA_REQUEST: + text = "ANSWER_DATA_REQUEST"; + break; + default: + break; + } + + return text; +} +#endif + +#if PRINT_ENABLED +char *mstp_frame_type_text(int type) +{ + char *text = "unknown"; + + switch (type) { + case FRAME_TYPE_TOKEN: + text = "TOKEN"; + break; + case FRAME_TYPE_POLL_FOR_MASTER: + text = "POLL_FOR_MASTER"; + break; + case FRAME_TYPE_REPLY_TO_POLL_FOR_MASTER: + text = "REPLY_TO_POLL_FOR_MASTER"; + break; + case FRAME_TYPE_TEST_REQUEST: + text = "TEST_REQUEST"; + break; + case FRAME_TYPE_TEST_RESPONSE: + text = "TEST_RESPONSE"; + break; + case FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY: + text = "BACNET_DATA_EXPECTING_REPLY"; + break; + case FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY: + text = "BACNET_DATA_NOT_EXPECTING_REPLY"; + break; + case FRAME_TYPE_REPLY_POSTPONED: + text = "REPLY_POSTPONED"; + break; + default: + if ((type >= FRAME_TYPE_PROPRIETARY_MIN) && + (type <= FRAME_TYPE_PROPRIETARY_MAX)) + text = "PROPRIETARY"; + break; + } + + return text; +} +#endif + +/* returns true if we need to transition immediately */ +bool MSTP_Master_Node_FSM(volatile struct mstp_port_struct_t * mstp_port) +{ + int mtu_len = 0; + int frame_type = 0; + uint8_t next_poll_station = 0; + uint8_t next_this_station = 0; + uint8_t next_next_station = 0; + uint16_t my_timeout = 10, ns_timeout = 0; + /* transition immediately to the next state */ + bool transition_now = false; +#if PRINT_ENABLED_MASTER + static MSTP_MASTER_STATE master_state = MSTP_MASTER_STATE_INITIALIZE; +#endif + + /* some calculations that several states need */ + next_poll_station = (mstp_port->Poll_Station + 1) % + (mstp_port->Nmax_master + 1); + next_this_station = (mstp_port->This_Station + 1) % + (mstp_port->Nmax_master + 1); + next_next_station = (mstp_port->Next_Station + 1) % + (mstp_port->Nmax_master + 1); +#if PRINT_ENABLED_MASTER + if (mstp_port->master_state != master_state) { + master_state = mstp_port->master_state; + fprintf(stderr, + "MSTP: TS=%02X[%02X] NS=%02X[%02X] PS=%02X[%02X] EC=%u TC=%u ST=%u %s\n", + mstp_port->This_Station, + next_this_station, + mstp_port->Next_Station, + next_next_station, + mstp_port->Poll_Station, + next_poll_station, + mstp_port->EventCount, + mstp_port->TokenCount, + mstp_port->SilenceTimer, + mstp_master_state_text(mstp_port->master_state)); + } +#endif + + switch (mstp_port->master_state) { + case MSTP_MASTER_STATE_INITIALIZE: + /* DoneInitializing */ + /* indicate that the next station is unknown */ + mstp_port->Next_Station = mstp_port->This_Station; + mstp_port->Poll_Station = mstp_port->This_Station; + /* cause a Poll For Master to be sent when this node first */ + /* receives the token */ + mstp_port->TokenCount = Npoll; + mstp_port->SoleMaster = false; + mstp_port->ReceivedValidFrame = false; + mstp_port->ReceivedInvalidFrame = false; + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + transition_now = true; + break; + /* In the IDLE state, the node waits for a frame. */ + case MSTP_MASTER_STATE_IDLE: + /* LostToken */ + if (mstp_port->SilenceTimer >= Tno_token) { + /* assume that the token has been lost */ + mstp_port->EventCount = 0; /* Addendum 135-2004d-8 */ + mstp_port->master_state = MSTP_MASTER_STATE_NO_TOKEN; + transition_now = true; + } + /* ReceivedInvalidFrame */ + else if (mstp_port->ReceivedInvalidFrame == true) { + /* invalid frame was received */ + mstp_port->ReceivedInvalidFrame = false; + /* wait for the next frame - remain in IDLE */ + } else if (mstp_port->ReceivedValidFrame == true) { +#if PRINT_ENABLED_MASTER + fprintf(stderr, + "MSTP: ReceivedValidFrame Src=%02X Dest=%02X DataLen=%u FC=%u ST=%u Type=%s\n", + mstp_port->SourceAddress, + mstp_port->DestinationAddress, + mstp_port->DataLength, + mstp_port->FrameCount, + mstp_port->SilenceTimer, + mstp_frame_type_text(mstp_port->FrameType)); +#endif + /* destined for me! */ + if ((mstp_port->DestinationAddress == + mstp_port->This_Station) || + (mstp_port->DestinationAddress == + MSTP_BROADCAST_ADDRESS)) { + switch (mstp_port->FrameType) { + /* ReceivedToken */ + case FRAME_TYPE_TOKEN: + /* tokens can't be broadcast */ + if (mstp_port->DestinationAddress == + MSTP_BROADCAST_ADDRESS) + break; + mstp_port->ReceivedValidFrame = false; + mstp_port->FrameCount = 0; + mstp_port->SoleMaster = false; + mstp_port->master_state = MSTP_MASTER_STATE_USE_TOKEN; + transition_now = true; + break; + /* ReceivedPFM */ + case FRAME_TYPE_POLL_FOR_MASTER: + /* CHEAT: we cheat a little and this is really handled in the + receive state machine since it is difficult to respond + quick enough (i.e. faster than Tusage_timeout of the + other node which could be 20ms). */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_REPLY_TO_POLL_FOR_MASTER, + mstp_port->SourceAddress, mstp_port->This_Station, + NULL, 0); + break; + case FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY: + /* indicate successful reception to the higher layers */ + dlmstp_put_receive(mstp_port->SourceAddress, + (uint8_t *) & mstp_port->InputBuffer[0], + mstp_port->DataLength); + break; + case FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY: + mstp_port->ReplyPostponedTimer = 0; + /* indicate successful reception to the higher layers */ + dlmstp_put_receive(mstp_port->SourceAddress, /* source MS/TP address */ + (uint8_t *) & mstp_port->InputBuffer[0], + mstp_port->DataLength); + /* broadcast DER just remains IDLE */ + if (mstp_port->DestinationAddress != + MSTP_BROADCAST_ADDRESS) { + mstp_port->master_state = + MSTP_MASTER_STATE_ANSWER_DATA_REQUEST; + transition_now = true; + } + break; + case FRAME_TYPE_TEST_REQUEST: + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_TEST_RESPONSE, + mstp_port->SourceAddress, mstp_port->This_Station, + NULL, 0); + break; + case FRAME_TYPE_TEST_RESPONSE: + default: + break; + } + } + mstp_port->ReceivedValidFrame = false; + } + break; + /* In the USE_TOKEN state, the node is allowed to send one or */ + /* more data frames. These may be BACnet Data frames or */ + /* proprietary frames. */ + case MSTP_MASTER_STATE_USE_TOKEN: + if (!mstp_port->TxReady) { + /* NothingToSend */ + mstp_port->FrameCount = mstp_port->Nmax_info_frames; + mstp_port->master_state = MSTP_MASTER_STATE_DONE_WITH_TOKEN; + } else { + uint8_t destination = mstp_port->TxBuffer[3]; + RS485_Send_Frame(mstp_port, + (uint8_t *) & mstp_port->TxBuffer[0], mstp_port->TxLength); + mstp_port->FrameCount++; + switch (mstp_port->TxFrameType) { + case FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY: + /* SendAndWait */ + if (destination == MSTP_BROADCAST_ADDRESS) + mstp_port->master_state = + MSTP_MASTER_STATE_DONE_WITH_TOKEN; + else + mstp_port->master_state = + MSTP_MASTER_STATE_WAIT_FOR_REPLY; + break; + case FRAME_TYPE_TEST_REQUEST: + mstp_port->master_state = MSTP_MASTER_STATE_WAIT_FOR_REPLY; + break; + case FRAME_TYPE_TEST_RESPONSE: + case FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY: + default: + /* SendNoWait */ + mstp_port->master_state = + MSTP_MASTER_STATE_DONE_WITH_TOKEN; + break; + } + mstp_port->TxReady = false; + } + transition_now = true; + break; + /* In the WAIT_FOR_REPLY state, the node waits for */ + /* a reply from another node. */ + case MSTP_MASTER_STATE_WAIT_FOR_REPLY: + if (mstp_port->SilenceTimer >= Treply_timeout) { + /* ReplyTimeout */ + /* assume that the request has failed */ + mstp_port->FrameCount = mstp_port->Nmax_info_frames; + mstp_port->master_state = MSTP_MASTER_STATE_DONE_WITH_TOKEN; + /* Any retry of the data frame shall await the next entry */ + /* to the USE_TOKEN state. (Because of the length of the timeout, */ + /* this transition will cause the token to be passed regardless */ + /* of the initial value of FrameCount.) */ + transition_now = true; + } else { + if (mstp_port->ReceivedInvalidFrame == true) { + /* InvalidFrame */ + /* error in frame reception */ + mstp_port->ReceivedInvalidFrame = false; + mstp_port->master_state = + MSTP_MASTER_STATE_DONE_WITH_TOKEN; + transition_now = true; + } else if (mstp_port->ReceivedValidFrame == true) { + if (mstp_port->DestinationAddress == + mstp_port->This_Station) { + switch (mstp_port->TxFrameType) { + case FRAME_TYPE_REPLY_POSTPONED: + /* ReceivedReplyPostponed */ + mstp_port->master_state = + MSTP_MASTER_STATE_DONE_WITH_TOKEN; + break; + case FRAME_TYPE_TEST_RESPONSE: + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + break; + case FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY: + /* ReceivedReply */ + /* or a proprietary type that indicates a reply */ + /* indicate successful reception to the higher layers */ + dlmstp_put_receive(mstp_port->SourceAddress, /* source MS/TP address */ + (uint8_t *) & mstp_port->InputBuffer[0], + mstp_port->DataLength); + mstp_port->master_state = + MSTP_MASTER_STATE_DONE_WITH_TOKEN; + break; + default: + /* if proprietary frame was expected, you might + need to transition to DONE WITH TOKEN */ + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + break; + } + } else { + /* ReceivedUnexpectedFrame */ + /* an unexpected frame was received */ + /* This may indicate the presence of multiple tokens. */ + /* Synchronize with the network. */ + /* This action drops the token. */ + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + } + mstp_port->ReceivedValidFrame = false; + transition_now = true; + } + } + break; + /* The DONE_WITH_TOKEN state either sends another data frame, */ + /* passes the token, or initiates a Poll For Master cycle. */ + case MSTP_MASTER_STATE_DONE_WITH_TOKEN: + /* SendAnotherFrame */ + if (mstp_port->FrameCount < mstp_port->Nmax_info_frames) { + /* then this node may send another information frame */ + /* before passing the token. */ + mstp_port->master_state = MSTP_MASTER_STATE_USE_TOKEN; + } + /* Npoll changed in Errata SSPC-135-2004 */ + else if (mstp_port->TokenCount < (Npoll - 1)) { + if ((mstp_port->SoleMaster == true) && + (mstp_port->Next_Station != next_this_station)) { + /* SoleMaster */ + /* there are no other known master nodes to */ + /* which the token may be sent (true master-slave operation). */ + mstp_port->FrameCount = 0; + mstp_port->TokenCount++; + mstp_port->master_state = MSTP_MASTER_STATE_USE_TOKEN; + } else { + /* SendToken */ + /* Npoll changed in Errata SSPC-135-2004 */ + /* The comparison of NS and TS+1 eliminates the Poll For Master */ + /* if there are no addresses between TS and NS, since there is no */ + /* address at which a new master node may be found in that case. */ + mstp_port->TokenCount++; + /* transmit a Token frame to NS */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_TOKEN, + mstp_port->Next_Station, + mstp_port->This_Station, NULL, 0); + mstp_port->RetryCount = 0; + mstp_port->EventCount = 0; + mstp_port->master_state = MSTP_MASTER_STATE_PASS_TOKEN; + } + } else if (next_poll_station == mstp_port->Next_Station) { + if (mstp_port->SoleMaster == true) { + /* SoleMasterRestartMaintenancePFM */ + mstp_port->Poll_Station = next_poll_station; + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_POLL_FOR_MASTER, mstp_port->Poll_Station, + mstp_port->This_Station, NULL, 0); + /* no known successor node */ + mstp_port->Next_Station = mstp_port->This_Station; + mstp_port->RetryCount = 0; + mstp_port->TokenCount = 1; /* changed in Errata SSPC-135-2004 */ + /* mstp_port->EventCount = 0; removed in Addendum 135-2004d-8 */ + /* find a new successor to TS */ + mstp_port->master_state = + MSTP_MASTER_STATE_POLL_FOR_MASTER; + } else { + /* ResetMaintenancePFM */ + mstp_port->Poll_Station = mstp_port->This_Station; + /* transmit a Token frame to NS */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_TOKEN, + mstp_port->Next_Station, + mstp_port->This_Station, NULL, 0); + mstp_port->RetryCount = 0; + mstp_port->TokenCount = 1; /* changed in Errata SSPC-135-2004 */ + mstp_port->EventCount = 0; + mstp_port->master_state = MSTP_MASTER_STATE_PASS_TOKEN; + } + } else { + /* SendMaintenancePFM */ + mstp_port->Poll_Station = next_poll_station; + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_POLL_FOR_MASTER, + mstp_port->Poll_Station, mstp_port->This_Station, NULL, 0); + mstp_port->RetryCount = 0; + mstp_port->master_state = MSTP_MASTER_STATE_POLL_FOR_MASTER; + } + transition_now = true; + break; + /* The PASS_TOKEN state listens for a successor to begin using */ + /* the token that this node has just attempted to pass. */ + case MSTP_MASTER_STATE_PASS_TOKEN: + if (mstp_port->SilenceTimer < Tusage_timeout) { + if (mstp_port->EventCount > Nmin_octets) { + /* SawTokenUser */ + /* Assume that a frame has been sent by the new token user. */ + /* Enter the IDLE state to process the frame. */ + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + transition_now = true; + } + } else { + if (mstp_port->RetryCount < Nretry_token) { + /* RetrySendToken */ + mstp_port->RetryCount++; + /* Transmit a Token frame to NS */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_TOKEN, + mstp_port->Next_Station, mstp_port->This_Station, NULL, + 0); + mstp_port->EventCount = 0; + /* re-enter the current state to listen for NS */ + /* to begin using the token. */ + } else { + /* FindNewSuccessor */ + /* Assume that NS has failed. */ + mstp_port->Poll_Station = next_next_station; + /* Transmit a Poll For Master frame to PS. */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_POLL_FOR_MASTER, + mstp_port->Poll_Station, mstp_port->This_Station, NULL, + 0); + /* no known successor node */ + mstp_port->Next_Station = mstp_port->This_Station; + mstp_port->RetryCount = 0; + mstp_port->TokenCount = 0; + /* mstp_port->EventCount = 0; removed in Addendum 135-2004d-8 */ + /* find a new successor to TS */ + mstp_port->master_state = + MSTP_MASTER_STATE_POLL_FOR_MASTER; + transition_now = true; + } + } + break; + /* The NO_TOKEN state is entered if mstp_port->SilenceTimer becomes greater */ + /* than Tno_token, indicating that there has been no network activity */ + /* for that period of time. The timeout is continued to determine */ + /* whether or not this node may create a token. */ + case MSTP_MASTER_STATE_NO_TOKEN: + my_timeout = Tno_token + (Tslot * mstp_port->This_Station); + if (mstp_port->SilenceTimer < my_timeout) { + if (mstp_port->EventCount > Nmin_octets) { + /* SawFrame */ + /* Some other node exists at a lower address. */ + /* Enter the IDLE state to receive and process the incoming frame. */ + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + transition_now = true; + } + } else { + ns_timeout = + Tno_token + (Tslot * (mstp_port->This_Station + 1)); + if (mstp_port->SilenceTimer < ns_timeout) { + /* GenerateToken */ + /* Assume that this node is the lowest numerical address */ + /* on the network and is empowered to create a token. */ + mstp_port->Poll_Station = next_this_station; + /* Transmit a Poll For Master frame to PS. */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_POLL_FOR_MASTER, + mstp_port->Poll_Station, mstp_port->This_Station, NULL, + 0); + /* indicate that the next station is unknown */ + mstp_port->Next_Station = mstp_port->This_Station; + mstp_port->RetryCount = 0; + mstp_port->TokenCount = 0; + /* mstp_port->EventCount = 0; removed Addendum 135-2004d-8 */ + /* enter the POLL_FOR_MASTER state to find a new successor to TS. */ + mstp_port->master_state = + MSTP_MASTER_STATE_POLL_FOR_MASTER; + transition_now = true; + } + } + break; + /* In the POLL_FOR_MASTER state, the node listens for a reply to */ + /* a previously sent Poll For Master frame in order to find */ + /* a successor node. */ + case MSTP_MASTER_STATE_POLL_FOR_MASTER: + if (mstp_port->ReceivedValidFrame == true) { + if ((mstp_port->DestinationAddress == mstp_port->This_Station) + && (mstp_port->FrameType == + FRAME_TYPE_REPLY_TO_POLL_FOR_MASTER)) { + /* ReceivedReplyToPFM */ + mstp_port->SoleMaster = false; + mstp_port->Next_Station = mstp_port->SourceAddress; + mstp_port->EventCount = 0; + /* Transmit a Token frame to NS */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_TOKEN, + mstp_port->Next_Station, mstp_port->This_Station, NULL, + 0); + mstp_port->Poll_Station = mstp_port->This_Station; + mstp_port->TokenCount = 0; + mstp_port->RetryCount = 0; + mstp_port->master_state = MSTP_MASTER_STATE_PASS_TOKEN; + } else { + /* ReceivedUnexpectedFrame */ + /* An unexpected frame was received. */ + /* This may indicate the presence of multiple tokens. */ + /* enter the IDLE state to synchronize with the network. */ + /* This action drops the token. */ + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + } + mstp_port->ReceivedValidFrame = false; + transition_now = true; + } else if ((mstp_port->SilenceTimer >= Tusage_timeout) || + (mstp_port->ReceivedInvalidFrame == true)) { + if (mstp_port->SoleMaster == true) { + /* SoleMaster */ + /* There was no valid reply to the periodic poll */ + /* by the sole known master for other masters. */ + mstp_port->FrameCount = 0; + /* mstp_port->TokenCount++; removed in 2004 */ + mstp_port->master_state = MSTP_MASTER_STATE_USE_TOKEN; + } else { + if (mstp_port->Next_Station != mstp_port->This_Station) { + /* DoneWithPFM */ + /* There was no valid reply to the maintenance */ + /* poll for a master at address PS. */ + mstp_port->EventCount = 0; + /* transmit a Token frame to NS */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_TOKEN, + mstp_port->Next_Station, mstp_port->This_Station, + NULL, 0); + mstp_port->RetryCount = 0; + mstp_port->master_state = MSTP_MASTER_STATE_PASS_TOKEN; + } else { + if (next_poll_station != mstp_port->This_Station) { + /* SendNextPFM */ + mstp_port->Poll_Station = next_poll_station; + /* Transmit a Poll For Master frame to PS. */ + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_POLL_FOR_MASTER, + mstp_port->Poll_Station, + mstp_port->This_Station, NULL, 0); + mstp_port->RetryCount = 0; + /* Re-enter the current state. */ + } else { + /* DeclareSoleMaster */ + /* to indicate that this station is the only master */ + mstp_port->SoleMaster = true; + mstp_port->FrameCount = 0; + mstp_port->master_state = + MSTP_MASTER_STATE_USE_TOKEN; + } + } + } + mstp_port->ReceivedInvalidFrame = false; + transition_now = true; + } + break; + /* The ANSWER_DATA_REQUEST state is entered when a */ + /* BACnet Data Expecting Reply, a Test_Request, or */ + /* a proprietary frame that expects a reply is received. */ + case MSTP_MASTER_STATE_ANSWER_DATA_REQUEST: + if (mstp_port->ReplyPostponedTimer <= Treply_delay) { + /* Reply */ + /* If a reply is available from the higher layers */ + /* within Treply_delay after the reception of the */ + /* final octet of the requesting frame */ + /* (the mechanism used to determine this is a local matter), */ + /* then call MSTP_Create_And_Send_Frame to transmit the reply frame */ + /* and enter the IDLE state to wait for the next frame. */ + if ((mstp_port->FrameType == + FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY) + && (mstp_port->TxReady)) { + RS485_Send_Frame(mstp_port, + (uint8_t *) & mstp_port->TxBuffer[0], + mstp_port->TxLength); + mstp_port->TxReady = false; + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + transition_now = true; + } + /* Test Request */ + /* If a receiving node can successfully receive and return */ + /* the information field, it shall do so. If it cannot receive */ + /* and return the entire information field but can detect */ + /* the reception of a valid Test_Request frame */ + /* (for example, by computing the CRC on octets as */ + /* they are received), then the receiving node shall discard */ + /* the information field and return a Test_Response containing */ + /* no information field. If the receiving node cannot detect */ + /* the valid reception of frames with overlength information fields, */ + /* then no response shall be returned. */ + else if (mstp_port->FrameType == FRAME_TYPE_TEST_REQUEST) { + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_TEST_RESPONSE, + mstp_port->SourceAddress, + mstp_port->This_Station, + (uint8_t *) & mstp_port->InputBuffer[0], + mstp_port->DataLength); + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + transition_now = true; + } + } + /* DeferredReply */ + /* If no reply will be available from the higher layers */ + /* within Treply_delay after the reception of the */ + /* final octet of the requesting frame (the mechanism */ + /* used to determine this is a local matter), */ + /* then an immediate reply is not possible. */ + /* Any reply shall wait until this node receives the token. */ + /* Call MSTP_Create_And_Send_Frame to transmit a Reply Postponed frame, */ + /* and enter the IDLE state. */ + else { + MSTP_Create_And_Send_Frame(mstp_port, + FRAME_TYPE_REPLY_POSTPONED, + mstp_port->SourceAddress, + mstp_port->This_Station, NULL, 0); + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + transition_now = true; + } + break; + default: + mstp_port->master_state = MSTP_MASTER_STATE_IDLE; + break; + } + + return transition_now; +} + +void MSTP_Init(volatile struct mstp_port_struct_t *mstp_port, + uint8_t this_station_mac) +{ + int i; /*loop counter */ + + if (mstp_port) { + mstp_port->receive_state = MSTP_RECEIVE_STATE_IDLE; + mstp_port->master_state = MSTP_MASTER_STATE_INITIALIZE; + mstp_port->ReceiveError = false; + mstp_port->DataAvailable = false; + mstp_port->DataRegister = 0; + mstp_port->DataCRC = 0; + mstp_port->DataCRC = 0; + mstp_port->DataLength = 0; + mstp_port->DestinationAddress = 0; + mstp_port->EventCount = 0; + mstp_port->FrameType = FRAME_TYPE_TOKEN; + mstp_port->FrameCount = 0; + mstp_port->HeaderCRC = 0; + mstp_port->Index = 0; + mstp_port->Index = 0; + for (i = 0; i < sizeof(mstp_port->InputBuffer); i++) { + mstp_port->InputBuffer[i] = 0; + } + mstp_port->Next_Station = this_station_mac; + mstp_port->Poll_Station = this_station_mac; + mstp_port->ReceivedInvalidFrame = false; + mstp_port->ReceivedValidFrame = false; + mstp_port->RetryCount = 0; + mstp_port->SilenceTimer = 0; + mstp_port->ReplyPostponedTimer = 0; + mstp_port->SoleMaster = false; + mstp_port->SourceAddress = 0; + mstp_port->TokenCount = 0; + mstp_port->This_Station = this_station_mac; + mstp_port->Nmax_info_frames = DEFAULT_MAX_INFO_FRAMES; + mstp_port->Nmax_master = DEFAULT_MAX_MASTER; + + /* An array of octets, used to store PDU octets prior to being transmitted. */ + /* This array is only used for APDU messages */ + for (i = 0; i < sizeof(mstp_port->TxBuffer); i++) { + mstp_port->TxBuffer[i] = 0; + } + mstp_port->TxLength = 0; + mstp_port->TxReady = false; + mstp_port->TxFrameType = 0; + + } +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +/* test stub functions */ +void RS485_Send_Frame(volatile struct mstp_port_struct_t *mstp_port, /* port specific data */ + uint8_t * buffer, /* frame to send (up to 501 bytes of data) */ + uint16_t nbytes) +{ /* number of bytes of data (up to 501) */ + (void) mstp_port; + (void) buffer; + (void) nbytes; +} + +#define RING_BUFFER_DATA_SIZE 1 +#define RING_BUFFER_SIZE MAX_MPDU +static RING_BUFFER Test_Buffer; +static uint8_t Test_Buffer_Data[RING_BUFFER_DATA_SIZE * RING_BUFFER_SIZE]; +static void Load_Input_Buffer(uint8_t * buffer, size_t len) +{ + static bool initialized = false; /* tracks our init */ + + + if (!initialized) { + initialized = true; + Ringbuf_Init(&Test_Buffer, + (char *) Test_Buffer_Data, + RING_BUFFER_DATA_SIZE, RING_BUFFER_SIZE); + } + /* empty any the existing data */ + while (!Ringbuf_Empty(&Test_Buffer)) { + (void) Ringbuf_Pop_Front(&Test_Buffer); + } + + if (buffer) { + while (len) { + (void) Ringbuf_Put(&Test_Buffer, (char *) buffer); + len--; + buffer++; + } + } +} + +void RS485_Check_UART_Data(volatile struct mstp_port_struct_t *mstp_port) +{ /* port specific data */ + char *data; + if (!Ringbuf_Empty(&Test_Buffer) && mstp_port && + (mstp_port->DataAvailable == false)) { + data = Ringbuf_Pop_Front(&Test_Buffer); + if (data) { + mstp_port->DataRegister = *data; + mstp_port->DataAvailable = true; + } + } +} + +void testReceiveNodeFSM(Test * pTest) +{ + volatile struct mstp_port_struct_t mstp_port; /* port data */ + unsigned EventCount = 0; /* local counter */ + uint8_t my_mac = 0x05; /* local MAC address */ + uint8_t HeaderCRC = 0; /* for local CRC calculation */ + uint8_t FrameType = 0; /* type of packet that was sent */ + unsigned len; /* used for the size of the message packet */ + size_t i; /* used to loop through the message bytes */ + uint8_t buffer[MAX_MPDU] = { 0 }; + uint8_t data[MAX_MPDU - 8 /*header */ - 2 /*CRC*/] = { 0 }; + + MSTP_Init(&mstp_port, my_mac); + + /* check the receive error during idle */ + mstp_port.receive_state = MSTP_RECEIVE_STATE_IDLE; + mstp_port.ReceiveError = true; + mstp_port.SilenceTimer = 255; + mstp_port.EventCount = 0; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.ReceiveError == false); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* check for bad packet header */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x11; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* check for good packet header, but timeout */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + /* force the timeout */ + mstp_port.SilenceTimer = Tframe_abort + 1; + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* check for good packet header preamble, but receive error */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + /* force the error */ + mstp_port.ReceiveError = true; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.ReceiveError == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* check for good packet header preamble1, but bad preamble2 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + MSTP_Receive_Frame_FSM(&mstp_port); + /* no change of state if no data yet */ + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + /* repeated preamble1 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + /* repeated preamble1 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + /* bad data */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x11; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.ReceiveError == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* check for good packet header preamble, but timeout in packet */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + MSTP_Receive_Frame_FSM(&mstp_port); + /* preamble2 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0xFF; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 0); + ct_test(pTest, mstp_port.HeaderCRC == 0xFF); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + /* force the timeout */ + mstp_port.SilenceTimer = Tframe_abort + 1; + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + ct_test(pTest, mstp_port.ReceivedInvalidFrame == true); + + /* check for good packet header preamble, but error in packet */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + MSTP_Receive_Frame_FSM(&mstp_port); + /* preamble2 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0xFF; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 0); + ct_test(pTest, mstp_port.HeaderCRC == 0xFF); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + /* force the error */ + mstp_port.ReceiveError = true; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.ReceiveError == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* check for good packet header preamble */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x55; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_PREAMBLE); + MSTP_Receive_Frame_FSM(&mstp_port); + /* preamble2 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0xFF; + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 0); + ct_test(pTest, mstp_port.HeaderCRC == 0xFF); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + /* no change of state if no data yet */ + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + /* Data is received - index is incremented */ + /* FrameType */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = FRAME_TYPE_TOKEN; + HeaderCRC = 0xFF; + HeaderCRC = CRC_Calc_Header(mstp_port.DataRegister, HeaderCRC); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 1); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + ct_test(pTest, FrameType == FRAME_TYPE_TOKEN); + /* Destination */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0x10; + HeaderCRC = CRC_Calc_Header(mstp_port.DataRegister, HeaderCRC); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 2); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + ct_test(pTest, mstp_port.DestinationAddress == 0x10); + /* Source */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = my_mac; + HeaderCRC = CRC_Calc_Header(mstp_port.DataRegister, HeaderCRC); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 3); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + ct_test(pTest, mstp_port.SourceAddress == my_mac); + /* Length1 = length*256 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0; + HeaderCRC = CRC_Calc_Header(mstp_port.DataRegister, HeaderCRC); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 4); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + ct_test(pTest, mstp_port.DataLength == 0); + /* Length2 */ + mstp_port.DataAvailable = true; + mstp_port.DataRegister = 0; + HeaderCRC = CRC_Calc_Header(mstp_port.DataRegister, HeaderCRC); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 5); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER); + ct_test(pTest, mstp_port.DataLength == 0); + /* HeaderCRC */ + mstp_port.DataAvailable = true; + ct_test(pTest, HeaderCRC == 0x73); /* per Annex G example */ + mstp_port.DataRegister = ~HeaderCRC; /* one's compliment of CRC is sent */ + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + ct_test(pTest, mstp_port.Index == 5); + ct_test(pTest, + mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER_CRC); + ct_test(pTest, mstp_port.HeaderCRC == 0x55); + /* NotForUs */ + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* BadCRC in header check */ + mstp_port.ReceivedInvalidFrame = false; + mstp_port.ReceivedValidFrame = false; + len = MSTP_Create_Frame(buffer, sizeof(buffer), FRAME_TYPE_TOKEN, 0x10, /* destination */ + my_mac, /* source */ + NULL, /* data */ + 0); /* data size */ + ct_test(pTest, len > 0); + /* make the header CRC bad */ + buffer[7] = 0x00; + Load_Input_Buffer(buffer, len); + for (i = 0; i < len; i++) { + RS485_Check_UART_Data(&mstp_port); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + } + ct_test(pTest, + mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER_CRC); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.ReceivedInvalidFrame == true); + ct_test(pTest, mstp_port.ReceivedValidFrame == false); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* NoData for us */ + mstp_port.ReceivedInvalidFrame = false; + mstp_port.ReceivedValidFrame = false; + len = MSTP_Create_Frame(buffer, sizeof(buffer), FRAME_TYPE_TOKEN, my_mac, /* destination */ + my_mac, /* source */ + NULL, /* data */ + 0); /* data size */ + ct_test(pTest, len > 0); + Load_Input_Buffer(buffer, len); + for (i = 0; i < len; i++) { + RS485_Check_UART_Data(&mstp_port); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + } + ct_test(pTest, + mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER_CRC); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.ReceivedInvalidFrame == false); + ct_test(pTest, mstp_port.ReceivedValidFrame == true); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* FrameTooLong */ + mstp_port.ReceivedInvalidFrame = false; + mstp_port.ReceivedValidFrame = false; + len = MSTP_Create_Frame(buffer, sizeof(buffer), FRAME_TYPE_TOKEN, my_mac, /* destination */ + my_mac, /* source */ + NULL, /* data */ + 0); /* data size */ + ct_test(pTest, len > 0); + /* make the header data length bad */ + buffer[5] = 0x02; + Load_Input_Buffer(buffer, len); + for (i = 0; i < len; i++) { + RS485_Check_UART_Data(&mstp_port); + INCREMENT_AND_LIMIT_UINT8(EventCount); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.DataAvailable == false); + ct_test(pTest, mstp_port.SilenceTimer == 0); + ct_test(pTest, mstp_port.EventCount == EventCount); + } + ct_test(pTest, + mstp_port.receive_state == MSTP_RECEIVE_STATE_HEADER_CRC); + MSTP_Receive_Frame_FSM(&mstp_port); + ct_test(pTest, mstp_port.ReceivedInvalidFrame == true); + ct_test(pTest, mstp_port.ReceivedValidFrame == false); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + /* Data */ + mstp_port.ReceivedInvalidFrame = false; + mstp_port.ReceivedValidFrame = false; + memset(data, 0, sizeof(data)); + len = MSTP_Create_Frame(buffer, sizeof(buffer), FRAME_TYPE_PROPRIETARY_MIN, my_mac, /* destination */ + my_mac, /* source */ + data, /* data */ + sizeof(data)); /* data size */ + ct_test(pTest, len > 0); + Load_Input_Buffer(buffer, len); + RS485_Check_UART_Data(&mstp_port); + MSTP_Receive_Frame_FSM(&mstp_port); + while (mstp_port.receive_state != MSTP_RECEIVE_STATE_IDLE) { + RS485_Check_UART_Data(&mstp_port); + MSTP_Receive_Frame_FSM(&mstp_port); + } + ct_test(pTest, mstp_port.DataLength == sizeof(data)); + ct_test(pTest, mstp_port.ReceivedInvalidFrame == false); + ct_test(pTest, mstp_port.ReceivedValidFrame == true); + ct_test(pTest, mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE); + + return; +} + +void testMasterNodeFSM(Test * pTest) +{ + volatile struct mstp_port_struct_t mstp_port; /* port data */ + uint8_t my_mac = 0x05; /* local MAC address */ + + MSTP_Init(&mstp_port, my_mac); + ct_test(pTest, mstp_port.master_state == MSTP_MASTER_STATE_INITIALIZE); + +} + +#endif + +#ifdef TEST_MSTP +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("mstp", NULL); + + /* individual tests */ + rc = ct_addTestFunction(pTest, testReceiveNodeFSM); + assert(rc); + rc = ct_addTestFunction(pTest, testMasterNodeFSM); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + + ct_destroy(pTest); + + return 0; +} +#endif diff --git a/bacnet-stack/ports/rtos32/mstp.h b/bacnet-stack/ports/rtos32/mstp.h index e69de29b..d7c74bfd 100644 --- a/bacnet-stack/ports/rtos32/mstp.h +++ b/bacnet-stack/ports/rtos32/mstp.h @@ -0,0 +1,244 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307 + USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#ifndef MSTP_H +#define MSTP_H + +#include +#include +#include +#include "bacdef.h" +#include "dlmstp.h" + +/* The value 255 is used to denote broadcast when used as a */ +/* destination address but is not allowed as a value for a station. */ +/* Station addresses for master nodes can be 0-127. */ +/* Station addresses for slave nodes can be 127-254. */ +#define MSTP_BROADCAST_ADDRESS 255 + +/* MS/TP Frame Type */ +/* Frame Types 8 through 127 are reserved by ASHRAE. */ +#define FRAME_TYPE_TOKEN 0 +#define FRAME_TYPE_POLL_FOR_MASTER 1 +#define FRAME_TYPE_REPLY_TO_POLL_FOR_MASTER 2 +#define FRAME_TYPE_TEST_REQUEST 3 +#define FRAME_TYPE_TEST_RESPONSE 4 +#define FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY 5 +#define FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY 6 +#define FRAME_TYPE_REPLY_POSTPONED 7 +/* Frame Types 128 through 255: Proprietary Frames */ +/* These frames are available to vendors as proprietary (non-BACnet) frames. */ +/* The first two octets of the Data field shall specify the unique vendor */ +/* identification code, most significant octet first, for the type of */ +/* vendor-proprietary frame to be conveyed. The length of the data portion */ +/* of a Proprietary frame shall be in the range of 2 to 501 octets. */ +#define FRAME_TYPE_PROPRIETARY_MIN 128 +#define FRAME_TYPE_PROPRIETARY_MAX 255 +/* The initial CRC16 checksum value */ +#define CRC16_INITIAL_VALUE (0xFFFF) + + +/* receive FSM states */ +typedef enum { + MSTP_RECEIVE_STATE_IDLE, + MSTP_RECEIVE_STATE_PREAMBLE, + MSTP_RECEIVE_STATE_HEADER, + MSTP_RECEIVE_STATE_HEADER_CRC, + MSTP_RECEIVE_STATE_DATA +} MSTP_RECEIVE_STATE; + +/* master node FSM states */ +typedef enum { + MSTP_MASTER_STATE_INITIALIZE, + MSTP_MASTER_STATE_IDLE, + MSTP_MASTER_STATE_USE_TOKEN, + MSTP_MASTER_STATE_WAIT_FOR_REPLY, + MSTP_MASTER_STATE_DONE_WITH_TOKEN, + MSTP_MASTER_STATE_PASS_TOKEN, + MSTP_MASTER_STATE_NO_TOKEN, + MSTP_MASTER_STATE_POLL_FOR_MASTER, + MSTP_MASTER_STATE_ANSWER_DATA_REQUEST +} MSTP_MASTER_STATE; + +struct mstp_port_struct_t { + MSTP_RECEIVE_STATE receive_state; + /* When a master node is powered up or reset, */ + /* it shall unconditionally enter the INITIALIZE state. */ + MSTP_MASTER_STATE master_state; + /* A Boolean flag set to TRUE by the Receive State Machine */ + /* if an error is detected during the reception of a frame. */ + /* Set to FALSE by the main state machine. */ + unsigned ReceiveError:1; + /* There is data in the buffer */ + unsigned DataAvailable:1; + unsigned FramingError:1; /* TRUE if we got a framing error */ + unsigned ReceivedInvalidFrame:1; + /* A Boolean flag set to TRUE by the Receive State Machine */ + /* if a valid frame is received. */ + /* Set to FALSE by the main state machine. */ + unsigned ReceivedValidFrame:1; + /* A Boolean flag set to TRUE by the master machine if this node is the */ + /* only known master node. */ + unsigned SoleMaster:1; + /* After receiving a frame this value will be TRUE until Tturnaround */ + /* has expired */ + unsigned Turn_Around_Waiting:1; + /* stores the latest received data */ + uint8_t DataRegister; + /* Used to accumulate the CRC on the data field of a frame. */ + uint16_t DataCRC; + /* Used to store the data length of a received frame. */ + unsigned DataLength; + /* Used to store the destination address of a received frame. */ + uint8_t DestinationAddress; + /* Used to count the number of received octets or errors. */ + /* This is used in the detection of link activity. */ + /* Compared to Nmin_octets */ + uint8_t EventCount; + /* Used to store the frame type of a received frame. */ + uint8_t FrameType; + /* The number of frames sent by this node during a single token hold. */ + /* When this counter reaches the value Nmax_info_frames, the node must */ + /* pass the token. */ + unsigned FrameCount; + /* Used to accumulate the CRC on the header of a frame. */ + uint8_t HeaderCRC; + /* Used as an index by the Receive State Machine, up to a maximum value of */ + /* InputBufferSize. */ + unsigned Index; + /* An array of octets, used to store octets as they are received. */ + /* InputBuffer is indexed from 0 to InputBufferSize-1. */ + /* The maximum size of a frame is 501 octets. */ + uint8_t InputBuffer[MAX_MPDU]; + /* "Next Station," the MAC address of the node to which This Station passes */ + /* the token. If the Next_Station is unknown, Next_Station shall be equal to */ + /* This_Station. */ + uint8_t Next_Station; + /* "Poll Station," the MAC address of the node to which This Station last */ + /* sent a Poll For Master. This is used during token maintenance. */ + uint8_t Poll_Station; + /* A counter of transmission retries used for Token and Poll For Master */ + /* transmission. */ + unsigned RetryCount; + /* A timer with nominal 5 millisecond resolution used to measure and */ + /* generate silence on the medium between octets. It is incremented by a */ + /* timer process and is cleared by the Receive State Machine when activity */ + /* is detected and by the SendFrame procedure as each octet is transmitted. */ + /* Since the timer resolution is limited and the timer is not necessarily */ + /* synchronized to other machine events, a timer value of N will actually */ + /* denote intervals between N-1 and N */ + uint16_t SilenceTimer; + + /* A timer used to measure and generate Reply Postponed frames. It is */ + /* incremented by a timer process and is cleared by the Master Node State */ + /* Machine when a Data Expecting Reply Answer activity is completed. */ + uint16_t ReplyPostponedTimer; + + /* Used to store the Source Address of a received frame. */ + uint8_t SourceAddress; + + /* The number of tokens received by this node. When this counter reaches the */ + /* value Npoll, the node polls the address range between TS and NS for */ + /* additional master nodes. TokenCount is set to zero at the end of the */ + /* polling process. */ + unsigned TokenCount; + + /* "This Station," the MAC address of this node. TS is generally read from a */ + /* hardware DIP switch, or from nonvolatile memory. Valid values for TS are */ + /* 0 to 254. The value 255 is used to denote broadcast when used as a */ + /* destination address but is not allowed as a value for TS. */ + uint8_t This_Station; + + /* This parameter represents the value of the Max_Info_Frames property of */ + /* the node's Device object. The value of Max_Info_Frames specifies the */ + /* maximum number of information frames the node may send before it must */ + /* pass the token. Max_Info_Frames may have different values on different */ + /* nodes. This may be used to allocate more or less of the available link */ + /* bandwidth to particular nodes. If Max_Info_Frames is not writable in a */ + /* node, its value shall be 1. */ + unsigned Nmax_info_frames; + + /* This parameter represents the value of the Max_Master property of the */ + /* node's Device object. The value of Max_Master specifies the highest */ + /* allowable address for master nodes. The value of Max_Master shall be */ + /* less than or equal to 127. If Max_Master is not writable in a node, */ + /* its value shall be 127. */ + unsigned Nmax_master; + + /* An array of octets, used to store PDU octets prior to being transmitted. */ + /* This array is only used for APDU messages */ + uint8_t TxBuffer[MAX_MPDU]; + unsigned TxLength; + bool TxReady; /* true if ready to be sent or received */ + uint8_t TxFrameType; /* type of message - needed by MS/TP */ +}; + +#define DEFAULT_MAX_INFO_FRAMES 1 +#define DEFAULT_MAX_MASTER 127 + +/* The minimum time after the end of the stop bit of the final octet of a */ +/* received frame before a node may enable its EIA-485 driver: 40 bit times. */ +/* At 9600 baud, 40 bit times would be about 4.166 milliseconds */ +#define Tturnaround 40; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + void MSTP_Init(volatile struct mstp_port_struct_t *mstp_port, + uint8_t this_station_mac); + void MSTP_Millisecond_Timer(volatile struct mstp_port_struct_t + *mstp_port); + void MSTP_Receive_Frame_FSM(volatile struct mstp_port_struct_t + *mstp_port); + bool MSTP_Master_Node_FSM(volatile struct mstp_port_struct_t + *mstp_port); + + /* returns true if line is active */ + bool MSTP_Line_Active(volatile struct mstp_port_struct_t *mstp_port); + + unsigned MSTP_Create_Frame(uint8_t * buffer, /* where frame is loaded */ + unsigned buffer_len, /* amount of space available */ + uint8_t frame_type, /* type of frame to send - see defines */ + uint8_t destination, /* destination address */ + uint8_t source, /* source address */ + uint8_t * data, /* any data to be sent - may be null */ + unsigned data_len); /* number of bytes of data (up to 501) */ + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/ports/rtos32/net.h b/bacnet-stack/ports/rtos32/net.h index e69de29b..5f6b680d 100644 --- a/bacnet-stack/ports/rtos32/net.h +++ b/bacnet-stack/ports/rtos32/net.h @@ -0,0 +1,80 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#ifndef NET_H +#define NET_H + +/* network file for RTOS-32 from On-Time */ +#define WIN32_LEAN_AND_MEAN +#define STRICT + +#include +#include +#include +#include + +#include "bip.h" +#ifndef HOST +#include +#include "netcfg.h" +#include +#include +#include +#include + +/* + * Definitions of bits in internet address integers. + * On subnets, the decomposition of addresses to host and net parts + * is done according to subnet mask, not the masks here. + */ +#define IN_CLASSA(i) (((long)(i) & 0x80000000) == 0) +#define IN_CLASSA_NET 0xff000000 +#define IN_CLASSA_NSHIFT 24 +#define IN_CLASSA_HOST 0x00ffffff +#define IN_CLASSA_MAX 128 + +#define IN_CLASSB(i) (((long)(i) & 0xc0000000) == 0x80000000) +#define IN_CLASSB_NET 0xffff0000 +#define IN_CLASSB_NSHIFT 16 +#define IN_CLASSB_HOST 0x0000ffff +#define IN_CLASSB_MAX 65536 + +#define IN_CLASSC(i) (((long)(i) & 0xe0000000) == 0xc0000000) +#define IN_CLASSC_NET 0xffffff00 +#define IN_CLASSC_NSHIFT 8 +#define IN_CLASSC_HOST 0x000000ff + +#define IN_CLASSD(i) (((long)(i) & 0xf0000000) == 0xe0000000) +#define IN_CLASSD_NET 0xf0000000 /* These ones aren't really */ +#define IN_CLASSD_NSHIFT 28 /* net and host fields, but */ +#define IN_CLASSD_HOST 0x0fffffff /* routing needn't know. */ +#define IN_MULTICAST(i) IN_CLASSD(i) + +#else +#include +#endif +#define close closesocket +typedef size_t socklen_t; + +#endif diff --git a/bacnet-stack/ports/rtos32/netcfg.h b/bacnet-stack/ports/rtos32/netcfg.h index e69de29b..48e85000 100644 --- a/bacnet-stack/ports/rtos32/netcfg.h +++ b/bacnet-stack/ports/rtos32/netcfg.h @@ -0,0 +1,104 @@ +/**************************************************************************/ +/* */ +/* File: NetCfg.h Copyright (c) 1996,2004 */ +/* Version: 4.0 On Time Informatik GmbH */ +/* */ +/* */ +/* On Time /////////////----- */ +/* Informatik GmbH ///////////// */ +/* --------------------------------------------------///////////// */ +/* Real-Time and System Software */ +/* */ +/**************************************************************************/ + +/* Network environment configuration file for the On Time RTOS-32 RTIP-32 + demos. + + By default, the RTIP-32 demos use static IP address assignment. If this is + not what you want, uncomment either #define AUTO_IP or #define DHCP. In all + cases, make sure the IP addresses (NetMask, TargetIP, DefaultGateway, + DNSServer, etc) given in the respective section below are all correct for + your select and LAN configuration. If you choose to use DHCP, the library + Dhcpc.lib must also be linked. + + Please define symbol DEVICE_ID to match your target's ethernet + card and make sure that the card's hardware resource assigments are + correct (for PCI cards, the drivers will determine this information + automatically). + +*/ + +/* #define AUTO_IP // use xn_autoip() to get an IP address */ +/* #define DHCP // if you enable this, you must also link library dhcpc.lib */ + +#if defined(AUTO_IP) /* use xn_autoip() to get an IP address */ +static BYTE TargetIP[] = { 0, 0, 0, 0 }; /* will be filled at run-time */ +static BYTE NetMask[] = { 255, 255, 255, 0 }; +static BYTE MinIP[] = { 192, 168, 0, 128 }; +static BYTE MaxIP[] = { 192, 168, 0, 255 }; +static BYTE DefaultGateway[] = { 192, 168, 0, 1 }; /* set to zero if not available or required */ +static BYTE DNSServer[] = { 192, 168, 0, 1 }; /* ditto */ +#elif defined(DHCP) /* use DHCP */ +#include +static BYTE TargetIP[] = { 0, 0, 0, 0 }; /* will be filled at run-time */ +#else /* static IP address assignment (default) */ +static BYTE TargetIP[] = { 192, 168, 0, 50 }; +static BYTE NetMask[] = { 255, 255, 255, 0 }; +static BYTE DefaultGateway[] = { 192, 168, 0, 1 }; /* set to zero if not available or required */ +static BYTE DNSServer[] = { 192, 168, 0, 1 }; /* ditto */ +#endif + +#define DEVICE_ID DAVICOM_DEVICE /* define your device type here */ + +#ifndef DEVICE_ID +#error You must define Ethernet driver/resources and IP address/net mask here +#endif + +/* The following values are ignored for PCI devices (the BIOS supplies */ +/* them), but they must be set correctly for ISA/PCMCIA systems and for */ +/* PCI devices if you do not have a BIOS */ + +#define ED_IO_ADD 0x300 /* I/O address of the device */ +#define ED_IRQ 5 /* IRQ of the device */ +#define ED_MEM_ADD 0 /* Memory Window (only some devices) */ + +/* Define function to pull in the required driver */ + +#if DEVICE_ID == NE2000_DEVICE +#define BIND_DRIVER xn_bind_ne2000 +#elif DEVICE_ID == N83815_DEVICE +#define BIND_DRIVER xn_bind_n83815 +#elif DEVICE_ID == TC90X_DEVICE +#define BIND_DRIVER xn_bind_tc90x +#elif DEVICE_ID == SMC91C9X_DEVICE +#define BIND_DRIVER xn_bind_smc91c9x +#elif DEVICE_ID == LANCE_DEVICE +#define BIND_DRIVER xn_bind_rtlance +#elif DEVICE_ID == LANCE_ISA_DEVICE +#define BIND_DRIVER xn_bind_lance_isa +#elif DEVICE_ID == LAN_CS89X0_DEVICE +#define BIND_DRIVER xn_bind_cs +#elif DEVICE_ID == I82559_DEVICE +#define BIND_DRIVER xn_bind_i82559 +#elif DEVICE_ID == R8139_DEVICE +#define BIND_DRIVER xn_bind_r8139 +#elif DEVICE_ID == DAVICOM_DEVICE +#define BIND_DRIVER xn_bind_davicom +#elif DEVICE_ID == RHINE_DEVICE +#define BIND_DRIVER xn_bind_rhine +#elif DEVICE_ID == AX172_DEVICE +#include /* must also link Rtusb.lib and UsbInit.cpp */ +#define BIND_DRIVER xn_bind_ax172 +#elif DEVICE_ID == AX772_DEVICE +#include /* must also link Rtusb.lib and UsbInit.cpp */ +#define BIND_DRIVER xn_bind_ax772 +#elif DEVICE_ID == PRISM_DEVICE +#include /* must also link Wlan.lib */ +#define BIND_DRIVER xn_bind_prism +#elif DEVICE_ID == PRISM_PCMCIA_DEVICE +#include +#include /* must also link Wlan.lib */ +#define BIND_DRIVER xn_bind_prism_pcmcia +#else +#error Invalid DEVICE_ID value +#endif diff --git a/bacnet-stack/ports/rtos32/rs485.c b/bacnet-stack/ports/rtos32/rs485.c index e69de29b..fa5dde73 100644 --- a/bacnet-stack/ports/rtos32/rs485.c +++ b/bacnet-stack/ports/rtos32/rs485.c @@ -0,0 +1,218 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ +#if PRINT_ENABLED +#define PRINT_ENABLED_RS485 1 +#else +#define PRINT_ENABLED_RS485 0 +#endif + +#include +#include +#include +#include +#if PRINT_ENABLED_RS485 +#include +#endif +#include "mstp.h" + +/* note: use the RTKernel-C API so that it can use this library */ + +#define RS485_IO_ENABLE(p) ModemControl(p,0,DTR); +#define RS485_TRANSMIT_ENABLE(p) ModemControl(p,1,RTS); +#define RS485_RECEIVE_ENABLE(p) ModemControl(p,0,RTS); + +/* COM port number - COM1 = 0 */ +static int RS485_Port = COM2; +/* baud rate */ +static long RS485_Baud = 9600; +/* io base address */ +static long RS485_Base = 0; +/* hardware IRQ number */ +static long RS485_IRQ_Number = 0; + +#if PRINT_ENABLED_RS485 +static FineTime RS485_Debug_Transmit_Timer; +#endif + +#if PRINT_ENABLED_RS485 +void RS485_Print_Frame(int port, FineTime timer, uint8_t * buffer, /* frame to send (up to 501 bytes of data) */ + uint16_t nbytes) +{ + uint16_t i; /* byte counter */ + unsigned long duration; /* measures the time from last output to this one */ + unsigned long seconds; + unsigned long milliseconds; + + duration = ElapsedMilliSecs(timer); + seconds = duration / 1000U; + milliseconds = duration - (seconds * 1000U); + fprintf(stderr, "%0lu.%03lu: COM%d:", seconds, milliseconds, port + 1); + for (i = 0; i < nbytes; i++) { + unsigned value; + value = buffer[i]; + fprintf(stderr, " %02X", value); + } + fprintf(stderr, "\n"); + fflush(stderr); +} +#endif + +static void RS485_Standard_Port_Settings(long port, long *pIRQ, + long *pBase) +{ + switch (port) { + case COM1: + *pBase = (long) 0x3F8; + *pIRQ = 4L; + break; + case COM2: + *pBase = (long) 0x2F8; + *pIRQ = 3L; + break; + case COM3: + *pBase = (long) 0x3E8; + *pIRQ = 4L; + break; + case COM4: + *pBase = (long) 0x2E8; + *pIRQ = 3L; + break; + default: + break; + } +} + +static int TestCOMPort(int Base) +{ /* base address of UART */ + int i; + + for (i = 0; i < 256; i++) { + RTOut(Base + 7, (BYTE) i); /* write scratch register */ + RTOut(Base + 1, RTIn(Base + 1)); /* read/write IER */ + if (RTIn(Base + 7) != i) /* check scratch register */ + return FALSE; + } + return TRUE; +} + +static RS485_Open_Port(int port, /* COM port number - COM1 = 0 */ + long baud, /* baud rate */ + unsigned base, /* io base address */ + int irq) +{ /* hardware IRQ number */ + if (!TestCOMPort(base)) + return; + + /* setup the COM IO */ + SetIOBase(port, base); + SetIRQ(port, irq); + + if (irq < 8) + RTKIRQTopPriority(irq, 9); + + InitPort(port, baud, PARITY_NONE, 1, 8); + + if (HasFIFO(port)) + EnableFIFO(port, 8); + EnableCOMInterrupt(port, 1024 * 4); + + /* enable the 485 via the DTR pin */ + RS485_IO_ENABLE(port); + RS485_RECEIVE_ENABLE(port); +#if PRINT_ENABLED_RS485 + fprintf(stderr, "RS485: COM%d Enabled\r\n", port + 1); +#endif + + return; +} + +void RS485_Initialize(void) +{ +#if PRINT_ENABLED_RS485 + MarkTime(&RS485_Debug_Transmit_Timer); +#endif + RS485_Standard_Port_Settings(RS485_Port, &RS485_IRQ_Number, + &RS485_Base); + RS485_Open_Port(RS485_Port, RS485_Baud, RS485_Base, RS485_IRQ_Number); +} + +void RS485_Send_Frame(volatile struct mstp_port_struct_t *mstp_port, /* port specific data */ + uint8_t * buffer, /* frame to send (up to 501 bytes of data) */ + uint16_t nbytes) +{ /* number of bytes of data (up to 501) */ + bool status = true; /* return value */ + + RS485_TRANSMIT_ENABLE(RS485_Port); + SendBlock(RS485_Port, (char *) buffer, nbytes); + /* need to wait at least 9600 baud * 512 bytes = 54mS */ + (void) WaitSendBufferEmpty(RS485_Port, MilliSecsToTicks(200)); + while (!(LineStatus(RS485_Port) & TX_SHIFT_EMPTY)) + RTKScheduler(); + RS485_RECEIVE_ENABLE(RS485_Port); + /* SilenceTimer is cleared by the Receive State Machine when + activity is detected and by the SendFrame procedure as each + octet is transmitted. */ + mstp_port->SilenceTimer = 0; +#if PRINT_ENABLED_RS485 + RS485_Print_Frame(RS485_Port, RS485_Debug_Transmit_Timer, buffer, /* frame to send (up to 501 bytes of data) */ + nbytes); + MarkTime(&RS485_Debug_Transmit_Timer); +#endif + + return; +} + +void RS485_Check_UART_Data(volatile struct mstp_port_struct_t *mstp_port) +{ /* port specific data */ + COMData com_data = 0; /* byte from COM driver */ + unsigned timeout = 1; /* milliseconds to wait for a character */ + static Duration ticks = 0; /* duration to wait for data */ + + if (mstp_port->ReceiveError) { + /* wait for state machine to clear this */ + RTKScheduler(); + } + /* wait for state machine to read from the DataRegister */ + else if (!mstp_port->DataAvailable) { + if (!ticks) { + ticks = MilliSecsToTicks(timeout); + if (!ticks) + ticks = 1; + } + /* check for data */ + if (RTKGetTimed(ReceiveBuffer[RS485_Port], &com_data, ticks)) { + /* if error, */ + if (com_data & (COM_OVERRUN << 8)) + mstp_port->ReceiveError = true; + else if (com_data & (COM_FRAME << 8)) + mstp_port->ReceiveError = true; + else { + mstp_port->DataRegister = com_data & 0x00FF; + mstp_port->DataAvailable = true; + } + } + } else + RTKScheduler(); +} diff --git a/bacnet-stack/ports/rtos32/rs485.h b/bacnet-stack/ports/rtos32/rs485.h index e69de29b..1f0c18db 100644 --- a/bacnet-stack/ports/rtos32/rs485.h +++ b/bacnet-stack/ports/rtos32/rs485.h @@ -0,0 +1,57 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307 + USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#ifndef RS485_H +#define RS485_H + +#include +#include "mstp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + void RS485_Initialize(void); + + void RS485_Send_Frame(volatile struct mstp_port_struct_t *mstp_port, /* port specific data */ + uint8_t * buffer, /* frame to send (up to 501 bytes of data) */ + uint16_t nbytes); /* number of bytes of data (up to 501) */ + + void RS485_Check_UART_Data(volatile struct mstp_port_struct_t *mstp_port); /* port specific data */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/ports/rtos32/stdbool.h b/bacnet-stack/ports/rtos32/stdbool.h index e69de29b..2b7511a6 100644 --- a/bacnet-stack/ports/rtos32/stdbool.h +++ b/bacnet-stack/ports/rtos32/stdbool.h @@ -0,0 +1,28 @@ +#ifndef STDBOOL_H +#define STDBOOL_H + +/* C99 Boolean types for compilers without C99 support */ + +#ifndef __cplusplus +typedef int _Bool; +#ifndef bool +#define bool _Bool +#endif +#ifndef true +#define true 1 +#endif +#ifndef false +#define false 0 +#endif +#define __bool_true_false_are_defined 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#endif diff --git a/bacnet-stack/ports/rtos32/stdint.h b/bacnet-stack/ports/rtos32/stdint.h index e69de29b..9c003d0a 100644 --- a/bacnet-stack/ports/rtos32/stdint.h +++ b/bacnet-stack/ports/rtos32/stdint.h @@ -0,0 +1,19 @@ +/* Defines the standard integer types that are used in code */ +/* for the x86 processor and Borland Compiler */ + +#ifndef STDINT_H +#define STDINT_H + +#include + +typedef unsigned char uint8_t; /* 1 byte 0 to 255 */ +typedef signed char int8_t; /* 1 byte -127 to 127 */ +typedef unsigned short uint16_t; /* 2 bytes 0 to 65535 */ +typedef signed short int16_t; /* 2 bytes -32767 to 32767 */ +/*typedef unsigned short long uint24_t; // 3 bytes 0 to 16777215 */ +typedef unsigned long uint32_t; /* 4 bytes 0 to 4294967295 */ +typedef signed long int32_t; /* 4 bytes -2147483647 to 2147483647 */ +/* typedef signed long long int64_t; */ +/* typedef unsigned long long uint64_t; */ + +#endif /* STDINT_H */ diff --git a/bacnet-stack/ports/win32/bip-init.c b/bacnet-stack/ports/win32/bip-init.c index e69de29b..0bd71bb6 100644 --- a/bacnet-stack/ports/win32/bip-init.c +++ b/bacnet-stack/ports/win32/bip-init.c @@ -0,0 +1,358 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#define WIN32_LEAN_AND_MEAN +#define STRICT 1 + +#include +#include +#include /* for standard integer types uint8_t etc. */ +#include /* for the standard bool type. */ +#include "bacdcode.h" +#include "bip.h" +#include "net.h" + +/* To fill a need, we invent the gethostaddr() function. */ +static long gethostaddr(void) +{ + struct hostent *host_ent; + char host_name[255]; + + if (gethostname(host_name, sizeof(host_name)) != 0) + return -1; + + if ((host_ent = gethostbyname(host_name)) == NULL) + return -1; +#ifdef BIP_DEBUG + printf("host: %s at %u.%u.%u.%u\n", host_name, + ((uint8_t*)host_ent->h_addr)[0], + ((uint8_t*)host_ent->h_addr)[1], + ((uint8_t*)host_ent->h_addr)[2], + ((uint8_t*)host_ent->h_addr)[3]); +#endif + /* note: network byte order */ + return *(long *) host_ent->h_addr; +} + +static void set_broadcast_address(uint32_t net_address) +{ + long broadcast_address = 0; + long mask = 0; + + /* Note: sometimes INADDR_BROADCAST does not let me get + any unicast messages. Not sure why... */ +#if USE_INADDR + (void) net_address; + bip_set_broadcast_addr(INADDR_BROADCAST); +#else + if (IN_CLASSA(ntohl(net_address))) + broadcast_address = + (ntohl(net_address) & ~IN_CLASSA_HOST) | IN_CLASSA_HOST; + else if (IN_CLASSB(ntohl(net_address))) + broadcast_address = + (ntohl(net_address) & ~IN_CLASSB_HOST) | IN_CLASSB_HOST; + else if (IN_CLASSC(ntohl(net_address))) + broadcast_address = + (ntohl(net_address) & ~IN_CLASSC_HOST) | IN_CLASSC_HOST; + else if (IN_CLASSD(ntohl(net_address))) + broadcast_address = + (ntohl(net_address) & ~IN_CLASSD_HOST) | IN_CLASSD_HOST; + else + broadcast_address = INADDR_BROADCAST; + bip_set_broadcast_addr(htonl(broadcast_address)); +#endif +} + +static void cleanup(void) +{ + WSACleanup(); +} + + +/* on Windows, ifname is the dotted ip address of the interface */ +void bip_set_interface(char *ifname) +{ + struct in_addr address; + + /* setup local address */ + if (bip_get_addr() == 0) { + bip_set_addr(inet_addr(ifname)); + } +#ifdef BIP_DEBUG + address.s_addr = htonl(bip_get_addr()); + fprintf(stderr, "Interface: %s\n", ifname); +#endif + /* setup local broadcast address */ + if (bip_get_broadcast_addr() == 0) { + address.s_addr = htonl(bip_get_addr()); + set_broadcast_address(address.s_addr); + } +} + +static char *winsock_error_code_text(int code) +{ + switch (code) { + case WSAEACCES: + return "Permission denied."; + case WSAEINTR: + return "Interrupted system call."; + case WSAEBADF: + return "Bad file number."; + case WSAEFAULT: + return "Bad address."; + case WSAEINVAL: + return "Invalid argument."; + case WSAEMFILE: + return "Too many open files."; + case WSAEWOULDBLOCK: + return "Operation would block."; + case WSAEINPROGRESS: + return "Operation now in progress. This error is returned if any Windows Sockets API function is called while a blocking function is in progress."; + case WSAENOTSOCK: + return "Socket operation on nonsocket."; + case WSAEDESTADDRREQ: + return "Destination address required."; + case WSAEMSGSIZE: + return "Message too long."; + case WSAEPROTOTYPE: + return "Protocol wrong type for socket."; + case WSAENOPROTOOPT: + return "Protocol not available."; + case WSAEPROTONOSUPPORT: + return "Protocol not supported."; + case WSAESOCKTNOSUPPORT: + return "Socket type not supported."; + case WSAEOPNOTSUPP: + return "Operation not supported on socket."; + case WSAEPFNOSUPPORT: + return "Protocol family not supported."; + case WSAEAFNOSUPPORT: + return "Address family not supported by protocol family."; + case WSAEADDRINUSE: + return "Address already in use."; + case WSAEADDRNOTAVAIL: + return "Cannot assign requested address."; + case WSAENETDOWN: + return "Network is down. This error may be reported at any time if the Windows Sockets implementation detects an underlying failure."; + case WSAENETUNREACH: + return "Network is unreachable."; + case WSAENETRESET: + return "Network dropped connection on reset."; + case WSAECONNABORTED: + return "Software caused connection abort."; + case WSAECONNRESET: + return "Connection reset by peer."; + case WSAENOBUFS: + return "No buffer space available."; + case WSAEISCONN: + return "Socket is already connected."; + case WSAENOTCONN: + return "Socket is not connected."; + case WSAESHUTDOWN: + return "Cannot send after socket shutdown."; + case WSAETOOMANYREFS: + return "Too many references: cannot splice."; + case WSAETIMEDOUT: + return "Connection timed out."; + case WSAECONNREFUSED: + return "Connection refused."; + case WSAELOOP: + return "Too many levels of symbolic links."; + case WSAENAMETOOLONG: + return "File name too long."; + case WSAEHOSTDOWN: + return "Host is down."; + case WSAEHOSTUNREACH: + return "No route to host."; + case WSASYSNOTREADY: + return "Returned by WSAStartup(), " + "indicating that the network subsystem is unusable."; + case WSAVERNOTSUPPORTED: + return "Returned by WSAStartup(), " + "indicating that the Windows Sockets DLL cannot support " + "this application."; + case WSANOTINITIALISED: + return "Winsock not initialized. " + "This message is returned by any function except WSAStartup(), " + "indicating that a successful WSAStartup() has not yet " + "been performed."; + case WSAEDISCON: + return "Disconnect."; + case WSAHOST_NOT_FOUND: + return "Host not found. " + "This message indicates that the key " + "(name, address, and so on) was not found."; + case WSATRY_AGAIN: + return "Nonauthoritative host not found. " + "This error may suggest that the name service itself " + "is not functioning."; + case WSANO_RECOVERY: + return "Nonrecoverable error. " + "This error may suggest that the name service itself " + "is not functioning."; + case WSANO_DATA: + return "Valid name, no data record of requested type. " + "This error indicates that the key " + "(name, address, and so on) was not found."; + default: return "unknown"; + } +} + +bool bip_init(char *ifname) +{ + int rv = 0; /* return from socket lib calls */ + struct sockaddr_in sin = { -1 }; + int value = 1; + int sock_fd = -1; + int Result; + int Code; + WSADATA wd; + struct in_addr address; + struct in_addr broadcast_address; + + Result = WSAStartup((1 << 8) | 1, &wd); + /*Result = WSAStartup(MAKEWORD(2,2), &wd); */ + if (Result != 0) { + Code = WSAGetLastError(); + printf("TCP/IP stack initialization failed\n" + " error code: %i %s\n", + Code, winsock_error_code_text(Code)); + exit(1); + } + atexit(cleanup); + + if (ifname) + bip_set_interface(ifname); + /* has address been set? */ + address.s_addr = htonl(bip_get_addr()); + if (address.s_addr == 0) { + address.s_addr = gethostaddr(); + if (address.s_addr == (unsigned) -1) { + Code = WSAGetLastError(); + printf("Get host address failed\n" + " error code: %i %s\n", + Code, winsock_error_code_text(Code)); + exit(1); + } + bip_set_addr(address.s_addr); + } +#ifdef BIP_DEBUG + fprintf(stderr, "IP Address: %s\n", inet_ntoa(address)); +#endif + /* has broadcast address been set? */ + if (bip_get_broadcast_addr() == 0) { + set_broadcast_address(address.s_addr); + } +#ifdef BIP_DEBUG + broadcast_address.s_addr = htonl(bip_get_broadcast_addr()); + fprintf(stderr, "IP Broadcast Address: %s\n", + inet_ntoa(broadcast_address)); + fprintf(stderr, "UDP Port: 0x%04X [%hu]\n", + bip_get_port(), + bip_get_port()); +#endif + /* assumes that the driver has already been initialized */ + sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + bip_set_socket(sock_fd); + if (sock_fd < 0) { + fprintf(stderr, "bip: failed to allocate a socket.\n"); + return false; + } + /* Allow us to use the same socket for sending and receiving */ + /* This makes sure that the src port is correct when sending */ + rv = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, + (char *) &value, sizeof(value)); + if (rv < 0) { + fprintf(stderr, "bip: failed to set REUSEADDR socket option.\n"); + close(sock_fd); + bip_set_socket(-1); + return false; + } + /* allow us to send a broadcast */ + rv = setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, + (char *) &value, sizeof(value)); + if (rv < 0) { + fprintf(stderr, "bip: failed to set BROADCAST socket option.\n"); + close(sock_fd); + bip_set_socket(-1); + return false; + } +#if 0 + /* probably only for Apple... */ + /* rebind a port that is already in use. + Note: all users of the port must specify this flag */ + rv = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEPORT, + (char *) &value, sizeof(value)); + if (rv < 0) { + fprintf(stderr, "bip: failed to set REUSEPORT socket option.\n"); + close(sock_fd); + bip_set_socket(-1); + return false; + } +#endif + /* bind the socket to the local port number and IP address */ + sin.sin_family = AF_INET; +#if USE_INADDR + /* by setting sin.sin_addr.s_addr to INADDR_ANY, + I am telling the IP stack to automatically fill + in the IP address of the machine the process + is running on. + + Some server computers have multiple IP addresses. + A socket bound to one of these will not accept + connections to another address. Frequently you prefer + to allow any one of the computer's IP addresses + to be used for connections. Use INADDR_ANY (0L) to + allow clients to connect using any one of the host's + IP addresses. */ + sin.sin_addr.s_addr = htonl(INADDR_ANY); +#else + /* or we could use the specific adapter address + note: already in network byte order */ + sin.sin_addr.s_addr = address.s_addr; +#endif + sin.sin_port = htons(bip_get_port()); + memset(&(sin.sin_zero), '\0', sizeof(sin.sin_zero)); + rv = bind(sock_fd, + (const struct sockaddr *) &sin, sizeof(struct sockaddr)); + if (rv < 0) { + fprintf(stderr, "bip: failed to bind to %s port %hd\n", + inet_ntoa(sin.sin_addr), bip_get_port()); + close(sock_fd); + bip_set_socket(-1); + return false; + } + + return true; +} diff --git a/bacnet-stack/ports/win32/ethernet.c b/bacnet-stack/ports/win32/ethernet.c index e69de29b..972b30b3 100644 --- a/bacnet-stack/ports/win32/ethernet.c +++ b/bacnet-stack/ports/win32/ethernet.c @@ -0,0 +1,455 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg, modified by Kevin Liao + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#include /* for standard integer types uint8_t etc. */ +#include /* for the standard bool type. */ +#include +#include +#include + +#include "bacdef.h" +#include "ethernet.h" +#include "bacdcode.h" + + +/* Uses WinPCap to access raw ethernet */ +/* Notes: */ +/* To make ethernet.c work under win32, you have to: */ +/* 1. install winpcap 3.1 development pack; */ +/* 2. install Microsoft Platform SDK Feb 2003. */ +/* 3. remove or modify functions used for log such as */ +/* "LogError()", "LogInfo()", which were implemented */ +/* as a wrapper of Log4cpp. */ +/* -- Kevin Liao */ + +/* includes for accessing ethernet by using winpcap */ +#include "pcap.h" +#include "packet32.h" +#include "ntddndis.h" +#include "remote-ext.h" + + +/* commonly used comparison address for ethernet */ +uint8_t Ethernet_Broadcast[MAX_MAC_LEN] = + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; +/* commonly used empty address for ethernet quick compare */ +uint8_t Ethernet_Empty_MAC[MAX_MAC_LEN] = { 0, 0, 0, 0, 0, 0 }; + +/* my local device data - MAC address */ +uint8_t Ethernet_MAC_Address[MAX_MAC_LEN] = { 0 }; + +/* couple of var for using winpcap */ +static char pcap_errbuf[PCAP_ERRBUF_SIZE + 1]; +static pcap_t *pcap_eth802_fp = NULL; /* 802.2 file handle, from winpcap */ +static unsigned eth_timeout = 100; + + +/* couple of external func for runtime error logging, you can simply */ +/* replace them with standard "printf(...)" */ +/* Logging extern functions: Info level */ +extern void LogInfo(const char *msg); +/* Logging extern functions: Error level*/ +extern void LogError(const char *msg); +/* Logging extern functions: Debug level*/ +extern void LogDebug(const char *msg); + + +bool ethernet_valid(void) +{ + return (pcap_eth802_fp != NULL); +} + +void ethernet_cleanup(void) +{ + if (pcap_eth802_fp) { + pcap_close(pcap_eth802_fp); + pcap_eth802_fp = NULL; + } + LogInfo("ethernet.c: ethernet_cleanup() ok.\n"); +} + +void ethernet_set_timeout(unsigned timeout) +{ + eth_timeout = timeout; +} + +/*---------------------------------------------------------------------- + Portable function to set a socket into nonblocking mode. + Calling this on a socket causes all future read() and write() calls on + that socket to do only as much as they can immediately, and return + without waiting. + If no data can be read or written, they return -1 and set errno + to EAGAIN (or EWOULDBLOCK). + Thanks to Bjorn Reese for this code. +----------------------------------------------------------------------*/ +/** + * We don't need to use this function since WinPCap has provided one + * named "pcap_setnonblock()". + * Kevin, 2006.08.15 + */ +/* +int setNonblocking(int fd) +{ + int flags; + + if (-1 == (flags = fcntl(fd, F_GETFL, 0))) + flags = 0; + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); +} +*/ + +bool ethernet_init(char *if_name) +{ + PPACKET_OID_DATA pOidData; + LPADAPTER lpAdapter; + pcap_if_t *pcap_all_if; + pcap_if_t *dev; + BOOLEAN result; + CHAR str[sizeof(PACKET_OID_DATA) + 128]; + int i; + char msgBuf[200]; + + if (ethernet_valid()) + ethernet_cleanup(); + + /** + * Find the interface user specified + */ + /* Retrieve the device list */ + if (pcap_findalldevs(&pcap_all_if, pcap_errbuf) == -1) { + sprintf(msgBuf, + "ethernet.c: error in pcap_findalldevs: %s\n", pcap_errbuf); + LogError(msgBuf); + return false; + } + /* Scan the list printing every entry */ + for (dev = pcap_all_if; dev; dev = dev->next) { + if (strcmp(if_name, dev->name) == 0) + break; + } + pcap_freealldevs(pcap_all_if); /* we don't need it anymore */ + if (dev == NULL) { + sprintf(msgBuf, + "ethernet.c: specified interface not found: %s\n", if_name); + LogError(msgBuf); + return false; + } + + /** + * Get local MAC address + */ + ZeroMemory(str, sizeof(PACKET_OID_DATA) + 128); + lpAdapter = PacketOpenAdapter(if_name); + if (lpAdapter == NULL) { + ethernet_cleanup(); + sprintf(msgBuf, + "ethernet.c: error in PacketOpenAdapter(\"%s\")\n", if_name); + LogError(msgBuf); + return false; + } + pOidData = (PPACKET_OID_DATA) str; + pOidData->Oid = OID_802_3_CURRENT_ADDRESS; + pOidData->Length = 6; + result = PacketRequest(lpAdapter, FALSE, pOidData); + if (!result) { + PacketCloseAdapter(lpAdapter); + ethernet_cleanup(); + LogError("ethernet.c: error in PacketRequest()\n"); + return false; + } + for (i = 0; i < 6; ++i) + Ethernet_MAC_Address[i] = pOidData->Data[i]; + PacketCloseAdapter(lpAdapter); + + /** + * Open interface for subsequent sending and receiving + */ + /* Open the output device */ + pcap_eth802_fp = pcap_open(if_name, /* name of the device */ + MAX_MPDU, /* portion of the packet to capture */ + PCAP_OPENFLAG_PROMISCUOUS, /* promiscuous mode */ + eth_timeout, /* read timeout */ + NULL, /* authentication on the remote machine */ + pcap_errbuf /* error buffer */ + ); + if (pcap_eth802_fp == NULL) { + PacketCloseAdapter(lpAdapter); + ethernet_cleanup(); + sprintf(msgBuf, + "ethernet.c: unable to open the adapter. %s is not supported by WinPcap\n", + if_name); + LogError(msgBuf); + return false; + } + + LogInfo("ethernet.c: ethernet_init() ok.\n"); + + atexit(ethernet_cleanup); + + return ethernet_valid(); +} + +/* function to send a packet out the 802.2 socket */ +/* returns bytes sent success, negative on failure */ +int ethernet_send(BACNET_ADDRESS * dest, /* destination address */ + BACNET_ADDRESS * src, /* source address */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len /* number of bytes of data */ + ) +{ + int bytes = 0; + uint8_t mtu[MAX_MPDU] = { 0 }; + int mtu_len = 0; + int i = 0; + + /* don't waste time if the socket is not valid */ + if (!ethernet_valid()) { + LogError + ("ethernet.c: invalid 802.2 ethernet interface descriptor!\n"); + return -1; + } + /* load destination ethernet MAC address */ + if (dest->mac_len == 6) { + for (i = 0; i < 6; i++) { + mtu[mtu_len] = dest->mac[i]; + mtu_len++; + } + } else { + LogError("ethernet.c: invalid destination MAC address!\n"); + return -2; + } + + /* load source ethernet MAC address */ + if (src->mac_len == 6) { + for (i = 0; i < 6; i++) { + mtu[mtu_len] = src->mac[i]; + mtu_len++; + } + } else { + LogError("ethernet.c: invalid source MAC address!\n"); + return -3; + } + if ((14 + 3 + pdu_len) > MAX_MPDU) { + LogError("ethernet.c: PDU is too big to send!\n"); + return -4; + } + /* packet length */ + mtu_len += + encode_unsigned16(&mtu[12], 3 /*DSAP,SSAP,LLC */ + pdu_len); + /* Logical PDU portion */ + mtu[mtu_len++] = 0x82; /* DSAP for BACnet */ + mtu[mtu_len++] = 0x82; /* SSAP for BACnet */ + mtu[mtu_len++] = 0x03; /* Control byte in header */ + memcpy(&mtu[mtu_len], pdu, pdu_len); + mtu_len += pdu_len; + + /* Send the packet */ + if (pcap_sendpacket(pcap_eth802_fp, mtu, mtu_len) != 0) { + /* did it get sent? */ + char msgBuf[200]; + sprintf(msgBuf, + "ethernet.c: error sending packet: %s\n", + pcap_geterr(pcap_eth802_fp)); + LogError(msgBuf); + return -5; + } + + return mtu_len; +} + +/* function to send a packet out the 802.2 socket */ +/* returns number of bytes sent on success, negative on failure */ +int ethernet_send_pdu(BACNET_ADDRESS * dest, /* destination address */ + uint8_t * pdu, /* any data to be sent - may be null */ + unsigned pdu_len /* number of bytes of data */ + ) +{ + int i = 0; /* counter */ + BACNET_ADDRESS src = { 0 }; /* source address */ + + for (i = 0; i < 6; i++) { + src.mac[i] = Ethernet_MAC_Address[i]; + src.mac_len++; + } + /* function to send a packet out the 802.2 socket */ + /* returns 1 on success, 0 on failure */ + return ethernet_send(dest, /* destination address */ + &src, /* source address */ + pdu, /* any data to be sent - may be null */ + pdu_len /* number of bytes of data */ + ); +} + +/* receives an 802.2 framed packet */ +/* returns the number of octets in the PDU, or zero on failure */ +uint16_t ethernet_receive(BACNET_ADDRESS * src, /* source address */ + uint8_t * pdu, /* PDU data */ + uint16_t max_pdu, /* amount of space available in the PDU */ + unsigned timeout /* number of milliseconds to wait for a packet. we ommit it due to winpcap API. */ + ) +{ + struct pcap_pkthdr *header; + int res; + u_char *pkt_data; + uint16_t pdu_len = 0; /* return value */ + + /* Make sure the socket is open */ + if (!ethernet_valid()) { + LogError + ("ethernet.c: invalid 802.2 ethernet interface descriptor!\n"); + return 0; + } + + /* Capture a packet */ + res = pcap_next_ex(pcap_eth802_fp, &header, &pkt_data); + if (res < 0) { + char msgBuf[200]; + sprintf(msgBuf, + "ethernet.c: error in receiving packet: %s\n", + pcap_geterr(pcap_eth802_fp)); + return 0; + } else if (res == 0) + return 0; + + if (header->len == 0 || header->caplen == 0) + return 0; + + /* the signature of an 802.2 BACnet packet */ + if ((pkt_data[14] != 0x82) && (pkt_data[15] != 0x82)) { + /*eth_log_error("ethernet.c: Non-BACnet packet\n"); */ + return 0; + } + /* copy the source address */ + src->mac_len = 6; + memmove(src->mac, &pkt_data[6], 6); + + /* check destination address for when */ + /* the Ethernet card is in promiscious mode */ + if ((memcmp(&pkt_data[0], Ethernet_MAC_Address, 6) != 0) + && (memcmp(&pkt_data[0], Ethernet_Broadcast, 6) != 0)) { + /*eth_log_error( "ethernet.c: This packet isn't for us\n"); */ + return 0; + } + + (void) decode_unsigned16(&pkt_data[12], &pdu_len); + pdu_len -= 3 /* DSAP, SSAP, LLC Control */ ; + /* copy the buffer into the PDU */ + if (pdu_len < max_pdu) + memmove(&pdu[0], &pkt_data[17], pdu_len); + /* ignore packets that are too large */ + else + pdu_len = 0; + + return pdu_len; +} + +void ethernet_set_my_address(BACNET_ADDRESS * my_address) +{ + int i = 0; + + for (i = 0; i < 6; i++) { + Ethernet_MAC_Address[i] = my_address->mac[i]; + } + + return; +} + +void ethernet_get_my_address(BACNET_ADDRESS * my_address) +{ + int i = 0; + + my_address->mac_len = 0; + for (i = 0; i < 6; i++) { + my_address->mac[i] = Ethernet_MAC_Address[i]; + my_address->mac_len++; + } + my_address->net = 0; /* local only, no routing */ + my_address->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + my_address->adr[i] = 0; + } + + return; +} + +void ethernet_get_broadcast_address(BACNET_ADDRESS * dest) +{ /* destination address */ + int i = 0; /* counter */ + + if (dest) { + for (i = 0; i < 6; i++) { + dest->mac[i] = Ethernet_Broadcast[i]; + } + dest->mac_len = 6; + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; /* denotes broadcast address */ + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = 0; + } + } + + return; +} + +void ethernet_debug_address(const char *info, BACNET_ADDRESS * dest) +{ + int i = 0; /* counter */ + char msgBuf[200]; + + if (info) { + sprintf(msgBuf, "%s", info); + LogError(msgBuf); + } + /* if */ + if (dest) { + sprintf(msgBuf, + "Address:\n MAC Length=%d\n MAC Address=", dest->mac_len); + LogInfo(msgBuf); + for (i = 0; i < MAX_MAC_LEN; i++) { + sprintf(msgBuf, "%02X ", (unsigned) dest->mac[i]); + LogInfo(msgBuf); + } /* for */ + LogInfo("\n"); + sprintf(msgBuf, + " Net=%hu\n Len=%d\n Adr=", dest->net, dest->len); + LogInfo(msgBuf); + for (i = 0; i < MAX_MAC_LEN; i++) { + sprintf(msgBuf, "%02X ", (unsigned) dest->adr[i]); + LogInfo(msgBuf); + } /* for */ + LogInfo("\n"); + } + /* if ( dest ) */ + return; +} diff --git a/bacnet-stack/ports/win32/main.c b/bacnet-stack/ports/win32/main.c index e69de29b..7d0d76f8 100644 --- a/bacnet-stack/ports/win32/main.c +++ b/bacnet-stack/ports/win32/main.c @@ -0,0 +1,257 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ + +/* This is one way to use the embedded BACnet stack under Win32 */ +/* compiled with Borland C++ 5.02 or Visual C++ 6.0 */ +#include +#include +#include +#include +#include /* for kbhit and getch */ +#include "iam.h" +#include "address.h" +#include "config.h" +#include "bacdef.h" +#include "npdu.h" +#include "apdu.h" +#include "device.h" +#include "handlers.h" +#include "client.h" +#include "datalink.h" +#include "txbuf.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/* send a whois to see who is on the network */ +static bool Who_Is_Request = true; +bool I_Am_Request = true; + +static void Read_Properties(void) +{ + uint32_t device_id = 0; + bool status = false; + unsigned max_apdu = 0; + BACNET_ADDRESS src; + bool next_device = false; + static unsigned index = 0; + static unsigned property = 0; + /* list of required (and some optional) properties in the + Device Object + note: you could just loop through + all the properties in all the objects. */ + const int object_props[] = { + PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, + PROP_OBJECT_TYPE, + PROP_SYSTEM_STATUS, + PROP_VENDOR_NAME, + PROP_VENDOR_IDENTIFIER, + PROP_MODEL_NAME, + PROP_FIRMWARE_REVISION, + PROP_APPLICATION_SOFTWARE_VERSION, + PROP_PROTOCOL_VERSION, + PROP_PROTOCOL_CONFORMANCE_CLASS, + PROP_PROTOCOL_SERVICES_SUPPORTED, + PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, + PROP_MAX_APDU_LENGTH_ACCEPTED, + PROP_SEGMENTATION_SUPPORTED, + PROP_LOCAL_TIME, + PROP_LOCAL_DATE, + PROP_UTC_OFFSET, + PROP_DAYLIGHT_SAVINGS_STATUS, + PROP_APDU_SEGMENT_TIMEOUT, + PROP_APDU_TIMEOUT, + PROP_NUMBER_OF_APDU_RETRIES, + PROP_TIME_SYNCHRONIZATION_RECIPIENTS, + PROP_MAX_MASTER, + PROP_MAX_INFO_FRAMES, + PROP_DEVICE_ADDRESS_BINDING, + /* note: PROP_OBJECT_LIST is missing cause + we need to get it with an index method since + the list could be very large */ + /* some proprietary properties */ + 514, 515, + /* end of list */ + -1 + }; + + if (address_count()) { + if (address_get_by_index(index, &device_id, &max_apdu, &src)) { + if (object_props[property] < 0) + next_device = true; + else { + status = Send_Read_Property_Request(device_id, /* destination device */ + OBJECT_DEVICE, + device_id, object_props[property], BACNET_ARRAY_ALL); + if (status) + property++; + } + } else + next_device = true; + if (next_device) { + next_device = false; + index++; + if (index >= MAX_ADDRESS_CACHE) + index = 0; + property = 0; + } + } + + return; +} + +static void LocalIAmHandler(uint8_t * service_request, + uint16_t service_len, BACNET_ADDRESS * src) +{ + int len = 0; + uint32_t device_id = 0; + unsigned max_apdu = 0; + int segmentation = 0; + uint16_t vendor_id = 0; + + (void) src; + (void) service_len; + len = iam_decode_service_request(service_request, + &device_id, &max_apdu, &segmentation, &vendor_id); + fprintf(stderr, "Received I-Am Request"); + if (len != -1) { + fprintf(stderr, " from %u!\n", device_id); + address_add(device_id, max_apdu, src); + } else + fprintf(stderr, "!\n"); + + return; +} + +static void Init_Service_Handlers(void) +{ + /* we need to handle who-is to support dynamic device binding */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, + handler_who_is); + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, + LocalIAmHandler); + + /* 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); + apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, + handler_write_property); + /* handle the data coming back from confirmed requests */ + apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_READ_PROPERTY, + handler_read_property_ack); +} + +static void print_address(char *name, BACNET_ADDRESS * dest) +{ /* destination address */ + int i = 0; /* counter */ + + if (dest) { + printf("%s: ", name); + for (i = 0; i < dest->mac_len; i++) { + printf("%02X", dest->mac[i]); + } + printf("\n"); + } +} + +static void print_address_cache(void) +{ + int i, j; + BACNET_ADDRESS address; + uint32_t device_id = 0; + unsigned max_apdu = 0; + + fprintf(stderr, "Device\tMAC\tMaxAPDU\tNet\n"); + for (i = 0; i < MAX_ADDRESS_CACHE; i++) { + if (address_get_by_index(i, &device_id, &max_apdu, &address)) { + fprintf(stderr, "%u\t", device_id); + for (j = 0; j < address.mac_len; j++) { + fprintf(stderr, "%02X", address.mac[j]); + } + fprintf(stderr, "\t"); + fprintf(stderr, "%hu\t", max_apdu); + fprintf(stderr, "%hu\n", address.net); + } + } +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + uint16_t pdu_len = 0; + unsigned timeout = 100; /* milliseconds */ + BACNET_ADDRESS my_address, broadcast_address; + + (void) argc; + (void) argv; + Device_Set_Object_Instance_Number(124); + Init_Service_Handlers(); + /* init the data link layer */ + if (!datalink_init(NULL)) + return 1; + datalink_get_broadcast_address(&broadcast_address); + print_address("Broadcast", &broadcast_address); + datalink_get_my_address(&my_address); + print_address("Address", &my_address); + + printf("BACnet stack running...\n"); + /* loop forever */ + for (;;) { + /* input */ + + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + if (I_Am_Request) { + I_Am_Request = false; + iam_send(&Handler_Transmit_Buffer[0]); + } else if (Who_Is_Request) { + Who_Is_Request = false; + Send_WhoIs(-1, -1); + } else { + Read_Properties(); + } + + /* output */ + + /* blink LEDs, Turn on or off outputs, etc */ + + /* wait for ESC from keyboard before quitting */ + if (kbhit() && (getch() == 0x1B)) + break; + } + + print_address_cache(); + + return 0; +} diff --git a/bacnet-stack/ports/win32/net.h b/bacnet-stack/ports/win32/net.h index e69de29b..a717cf7b 100644 --- a/bacnet-stack/ports/win32/net.h +++ b/bacnet-stack/ports/win32/net.h @@ -0,0 +1,38 @@ +/************************************************************************** +* +* 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. +* +*********************************************************************/ + +#ifndef NET_H +#define NET_H + +#define WIN32_LEAN_AND_MEAN +#define STRICT 1 + +#include + +#define close closesocket + +typedef int socklen_t; + +#endif diff --git a/bacnet-stack/ports/win32/stdbool.h b/bacnet-stack/ports/win32/stdbool.h index e69de29b..2b7511a6 100644 --- a/bacnet-stack/ports/win32/stdbool.h +++ b/bacnet-stack/ports/win32/stdbool.h @@ -0,0 +1,28 @@ +#ifndef STDBOOL_H +#define STDBOOL_H + +/* C99 Boolean types for compilers without C99 support */ + +#ifndef __cplusplus +typedef int _Bool; +#ifndef bool +#define bool _Bool +#endif +#ifndef true +#define true 1 +#endif +#ifndef false +#define false 0 +#endif +#define __bool_true_false_are_defined 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#endif diff --git a/bacnet-stack/ports/win32/stdint.h b/bacnet-stack/ports/win32/stdint.h index e69de29b..9c003d0a 100644 --- a/bacnet-stack/ports/win32/stdint.h +++ b/bacnet-stack/ports/win32/stdint.h @@ -0,0 +1,19 @@ +/* Defines the standard integer types that are used in code */ +/* for the x86 processor and Borland Compiler */ + +#ifndef STDINT_H +#define STDINT_H + +#include + +typedef unsigned char uint8_t; /* 1 byte 0 to 255 */ +typedef signed char int8_t; /* 1 byte -127 to 127 */ +typedef unsigned short uint16_t; /* 2 bytes 0 to 65535 */ +typedef signed short int16_t; /* 2 bytes -32767 to 32767 */ +/*typedef unsigned short long uint24_t; // 3 bytes 0 to 16777215 */ +typedef unsigned long uint32_t; /* 4 bytes 0 to 4294967295 */ +typedef signed long int32_t; /* 4 bytes -2147483647 to 2147483647 */ +/* typedef signed long long int64_t; */ +/* typedef unsigned long long uint64_t; */ + +#endif /* STDINT_H */ diff --git a/bacnet-stack/rd.c b/bacnet-stack/rd.c index e69de29b..f814471d 100644 --- a/bacnet-stack/rd.c +++ b/bacnet-stack/rd.c @@ -0,0 +1,182 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacdcode.h" +#include "bacdef.h" +#include "rd.h" + +/* encode service */ +int rd_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, + BACNET_REINITIALIZED_STATE state, BACNET_CHARACTER_STRING * password) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST; + apdu[1] = encode_max_segs_max_apdu(0, MAX_APDU); + apdu[2] = invoke_id; + apdu[3] = SERVICE_CONFIRMED_REINITIALIZE_DEVICE; + apdu_len = 4; + len = encode_context_enumerated(&apdu[apdu_len], 0, state); + apdu_len += len; + /* optional password */ + if (password) { + /* FIXME: must be at least 1 character, limited to 20 characters */ + len = encode_context_character_string(&apdu[apdu_len], 1, + password); + apdu_len += len; + } + } + + return apdu_len; +} + +/* decode the service request only */ +int rd_decode_service_request(uint8_t * apdu, + unsigned apdu_len, + BACNET_REINITIALIZED_STATE * state, BACNET_CHARACTER_STRING * password) +{ + unsigned len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + int value = 0; + + /* check for value pointers */ + if (apdu_len) { + /* Tag 0: reinitializedStateOfDevice */ + if (!decode_is_context_tag(&apdu[len], 0)) + return -1; + len += decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += decode_enumerated(&apdu[len], len_value_type, &value); + if (state) + *state = value; + /* Tag 1: password - optional */ + if (len < apdu_len) { + if (!decode_is_context_tag(&apdu[len], 1)) + return -1; + len += decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + len += + decode_character_string(&apdu[len], len_value_type, + password); + } + } + + return (int) len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +int rd_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, + BACNET_REINITIALIZED_STATE * state, BACNET_CHARACTER_STRING * password) +{ + int len = 0; + unsigned offset = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) + return -1; + /* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */ + *invoke_id = apdu[2]; /* invoke id - filled in by net layer */ + if (apdu[3] != SERVICE_CONFIRMED_REINITIALIZE_DEVICE) + return -1; + offset = 4; + + if (apdu_len > offset) { + len = rd_decode_service_request(&apdu[offset], + apdu_len - offset, state, password); + } + + return len; +} + +void test_ReinitializeDevice(Test * pTest) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + uint8_t invoke_id = 128; + uint8_t test_invoke_id = 0; + BACNET_REINITIALIZED_STATE state; + BACNET_REINITIALIZED_STATE test_state; + BACNET_CHARACTER_STRING password; + BACNET_CHARACTER_STRING test_password; + + state = BACNET_REINIT_WARMSTART; + characterstring_init_ansi(&password, "John 3:16"); + len = rd_encode_apdu(&apdu[0], invoke_id, state, &password); + ct_test(pTest, len != 0); + apdu_len = len; + + len = rd_decode_apdu(&apdu[0], + apdu_len, &test_invoke_id, &test_state, &test_password); + ct_test(pTest, len != -1); + ct_test(pTest, test_invoke_id == invoke_id); + ct_test(pTest, test_state == state); + ct_test(pTest, characterstring_same(&test_password, &password)); + + return; +} + +#ifdef TEST_REINITIALIZE_DEVICE +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet ReinitializeDevice", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, test_ReinitializeDevice); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_REINITIALIZE_DEVICE */ +#endif /* TEST */ diff --git a/bacnet-stack/rd.h b/bacnet-stack/rd.h index e69de29b..ad16a64c 100644 --- a/bacnet-stack/rd.h +++ b/bacnet-stack/rd.h @@ -0,0 +1,70 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef REINITIALIZE_DEVICE_H +#define REINITIALIZE_DEVICE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* encode service */ + int rd_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, + BACNET_REINITIALIZED_STATE state, + BACNET_CHARACTER_STRING * password); + +/* decode the service request only */ + int rd_decode_service_request(uint8_t * apdu, + unsigned apdu_len, + BACNET_REINITIALIZED_STATE * state, + BACNET_CHARACTER_STRING * password); + +#ifdef TEST +#include "ctest.h" + int rd_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, + BACNET_REINITIALIZED_STATE * state, + BACNET_CHARACTER_STRING * password); + + void test_ReinitializeDevice(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/reject.c b/bacnet-stack/reject.c index e69de29b..8edc441d 100644 --- a/bacnet-stack/reject.c +++ b/bacnet-stack/reject.c @@ -0,0 +1,168 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacdcode.h" +#include "bacdef.h" + +/* encode service */ +int reject_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, uint8_t reject_reason) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu[0] = PDU_TYPE_REJECT; + apdu[1] = invoke_id; + apdu[2] = reject_reason; + apdu_len = 3; + } + + return apdu_len; +} + +/* decode the service request only */ +int reject_decode_service_request(uint8_t * apdu, + unsigned apdu_len, uint8_t * invoke_id, uint8_t * reject_reason) +{ + int len = 0; + + if (apdu_len) { + if (invoke_id) + *invoke_id = apdu[0]; + if (reject_reason) + *reject_reason = apdu[1]; + } + + return len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +/* decode the whole APDU - mainly used for unit testing */ +int reject_decode_apdu(uint8_t * apdu, + unsigned apdu_len, uint8_t * invoke_id, uint8_t * reject_reason) +{ + int len = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu_len) { + if (apdu[0] != PDU_TYPE_REJECT) + return -1; + if (apdu_len > 1) { + len = reject_decode_service_request(&apdu[1], + apdu_len - 1, invoke_id, reject_reason); + } + } + + return len; +} + +void testReject(Test * pTest) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + uint8_t invoke_id = 0; + uint8_t reject_reason = 0; + uint8_t test_invoke_id = 0; + uint8_t test_reject_reason = 0; + + len = reject_encode_apdu(&apdu[0], invoke_id, reject_reason); + ct_test(pTest, len != 0); + apdu_len = len; + + len = reject_decode_apdu(&apdu[0], + apdu_len, &test_invoke_id, &test_reject_reason); + ct_test(pTest, len != -1); + ct_test(pTest, test_invoke_id == invoke_id); + ct_test(pTest, test_reject_reason == reject_reason); + + /* change type to get negative response */ + apdu[0] = PDU_TYPE_ABORT; + len = reject_decode_apdu(&apdu[0], + apdu_len, &test_invoke_id, &test_reject_reason); + ct_test(pTest, len == -1); + + /* test NULL APDU */ + len = reject_decode_apdu(NULL, + apdu_len, &test_invoke_id, &test_reject_reason); + ct_test(pTest, len == -1); + + /* force a zero length */ + len = reject_decode_apdu(&apdu[0], + 0, &test_invoke_id, &test_reject_reason); + ct_test(pTest, len == 0); + + + /* check them all... */ + for (invoke_id = 0; invoke_id < 255; invoke_id++) { + for (reject_reason = 0; reject_reason < 255; reject_reason++) { + len = reject_encode_apdu(&apdu[0], invoke_id, reject_reason); + apdu_len = len; + ct_test(pTest, len != 0); + len = reject_decode_apdu(&apdu[0], + apdu_len, &test_invoke_id, &test_reject_reason); + ct_test(pTest, len != -1); + ct_test(pTest, test_invoke_id == invoke_id); + ct_test(pTest, test_reject_reason == reject_reason); + } + } +} + +#ifdef TEST_REJECT +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Reject", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testReject); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_REJECT */ +#endif /* TEST */ diff --git a/bacnet-stack/reject.h b/bacnet-stack/reject.h index e69de29b..83d11d1d 100644 --- a/bacnet-stack/reject.h +++ b/bacnet-stack/reject.h @@ -0,0 +1,60 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef REJECT_H +#define REJECT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + int reject_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, uint8_t reject_reason); + + int reject_decode_service_request(uint8_t * apdu, + unsigned apdu_len, uint8_t * invoke_id, uint8_t * reject_reason); + +#ifdef TEST + int reject_decode_apdu(uint8_t * apdu, + unsigned apdu_len, uint8_t * invoke_id, uint8_t * reject_reason); + + void testReject(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/ringbuf.c b/bacnet-stack/ringbuf.c index e69de29b..5ebe12af 100644 --- a/bacnet-stack/ringbuf.c +++ b/bacnet-stack/ringbuf.c @@ -0,0 +1,273 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 by Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307 + USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +/* Functional Description: Generic ring buffer library for deeply + embedded system. See the unit tests for usage examples. */ + +#include +#include +#include "ringbuf.h" + +/**************************************************************************** +* DESCRIPTION: Returns the empty/full status of the ring buffer +* RETURN: true if the ring buffer is empty, false if it is not. +* ALGORITHM: none +* NOTES: none +*****************************************************************************/ +bool Ringbuf_Empty(RING_BUFFER const *b) +{ + return (b->count == 0); +} + +/**************************************************************************** +* DESCRIPTION: Looks at the data from the head of the list without removing it +* RETURN: none +* ALGORITHM: none +* NOTES: none +*****************************************************************************/ +char *Ringbuf_Get_Front(RING_BUFFER const *b) +{ + return (b->count ? &(b->data[b->head * b->element_size]) : NULL); +} + +/**************************************************************************** +* DESCRIPTION: Gets the data from the front of the list, and removes it +* RETURN: none +* ALGORITHM: none +* NOTES: none +*****************************************************************************/ +char *Ringbuf_Pop_Front(RING_BUFFER * b) +{ + char *data = NULL; /* return value */ + + if (b->count) { + data = &(b->data[b->head * b->element_size]); + b->head++; + if (b->head >= b->element_count) + b->head = 0; + b->count--; + } + + return data; +} + +/**************************************************************************** +* DESCRIPTION: Adds an element of data to the ring buffer +* RETURN: true on succesful add, false if not added +* ALGORITHM: none +* NOTES: none +*****************************************************************************/ +bool Ringbuf_Put(RING_BUFFER * b, /* ring buffer structure */ + char *data_element) +{ /* one element to add to the ring */ + bool status = false; /* return value */ + unsigned offset = 0; /* offset into array of data */ + char *ring_data = NULL; /* used to help point ring data */ + unsigned i; /* loop counter */ + + if (b && data_element) { + /* limit the amount of data that we accept */ + if (b->count < b->element_count) { + offset = b->head + b->count; + if (offset >= b->element_count) + offset -= b->element_count; + ring_data = b->data + offset * b->element_size; + for (i = 0; i < b->element_size; i++) { + ring_data[i] = data_element[i]; + } + b->count++; + status = true; + } + } + + return status; +} + +/**************************************************************************** +* DESCRIPTION: Configures the ring buffer +* RETURN: none +* ALGORITHM: none +* NOTES: none +*****************************************************************************/ +void Ringbuf_Init(RING_BUFFER * b, /* ring buffer structure */ + char *data, /* data block or array of data */ + unsigned element_size, /* size of one element in the data block */ + unsigned element_count) +{ /* number of elements in the data block */ + b->head = 0; + b->count = 0; + b->data = data; + b->element_size = element_size; + b->element_count = element_count; + + return; +} + +#ifdef TEST +#include +#include + +#include "ctest.h" + +/* test the FIFO */ +#define RING_BUFFER_DATA_SIZE 5 +#define RING_BUFFER_COUNT 16 +void testRingBuf(Test * pTest) +{ + RING_BUFFER test_buffer; + char data_store[RING_BUFFER_DATA_SIZE * RING_BUFFER_COUNT]; + char data[RING_BUFFER_DATA_SIZE]; + char *test_data; + unsigned index; + unsigned data_index; + unsigned count; + unsigned dummy; + bool status; + + Ringbuf_Init(&test_buffer, data_store, RING_BUFFER_DATA_SIZE, + RING_BUFFER_COUNT); + ct_test(pTest, Ringbuf_Empty(&test_buffer)); + + for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE; data_index++) { + data[data_index] = data_index; + } + status = Ringbuf_Put(&test_buffer, data); + ct_test(pTest, status == true); + ct_test(pTest, !Ringbuf_Empty(&test_buffer)); + + test_data = Ringbuf_Get_Front(&test_buffer); + for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE; data_index++) { + ct_test(pTest, test_data[data_index] == data[data_index]); + } + ct_test(pTest, !Ringbuf_Empty(&test_buffer)); + + test_data = Ringbuf_Pop_Front(&test_buffer); + for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE; data_index++) { + ct_test(pTest, test_data[data_index] == data[data_index]); + } + ct_test(pTest, Ringbuf_Empty(&test_buffer)); + + /* fill to max */ + for (index = 0; index < RING_BUFFER_COUNT; index++) { + for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE; + data_index++) { + data[data_index] = index; + } + status = Ringbuf_Put(&test_buffer, data); + ct_test(pTest, status == true); + ct_test(pTest, !Ringbuf_Empty(&test_buffer)); + } + /* verify actions on full buffer */ + for (index = 0; index < RING_BUFFER_COUNT; index++) { + for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE; + data_index++) { + data[data_index] = index; + } + status = Ringbuf_Put(&test_buffer, data); + ct_test(pTest, status == false); + ct_test(pTest, !Ringbuf_Empty(&test_buffer)); + } + + /* check buffer full */ + for (index = 0; index < RING_BUFFER_COUNT; index++) { + test_data = Ringbuf_Get_Front(&test_buffer); + for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE; + data_index++) { + ct_test(pTest, test_data[data_index] == index); + } + + test_data = Ringbuf_Pop_Front(&test_buffer); + for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE; + data_index++) { + ct_test(pTest, test_data[data_index] == index); + } + } + ct_test(pTest, Ringbuf_Empty(&test_buffer)); + + /* test the ring around the buffer */ + for (index = 0; index < RING_BUFFER_COUNT; index++) { + for (count = 1; count < 4; count++) { + dummy = index * count; + for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE; + data_index++) { + data[data_index] = dummy; + } + status = Ringbuf_Put(&test_buffer, data); + ct_test(pTest, status == true); + } + + for (count = 1; count < 4; count++) { + dummy = index * count; + test_data = Ringbuf_Get_Front(&test_buffer); + for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE; + data_index++) { + ct_test(pTest, test_data[data_index] == dummy); + } + + test_data = Ringbuf_Pop_Front(&test_buffer); + for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE; + data_index++) { + ct_test(pTest, test_data[data_index] == dummy); + } + } + } + ct_test(pTest, Ringbuf_Empty(&test_buffer)); + + + return; +} + +#ifdef TEST_RINGBUF +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("ringbuf", NULL); + + /* individual tests */ + rc = ct_addTestFunction(pTest, testRingBuf); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + + ct_destroy(pTest); + + return 0; +} +#endif +#endif diff --git a/bacnet-stack/ringbuf.h b/bacnet-stack/ringbuf.h index e69de29b..6a6e4f72 100644 --- a/bacnet-stack/ringbuf.h +++ b/bacnet-stack/ringbuf.h @@ -0,0 +1,71 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2004 by Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307 + USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +/* Functional Description: Generic ring buffer library for deeply + embedded system. See the unit tests for usage examples. */ + +#ifndef RINGBUF_H +#define RINGBUF_H + +#include +#include + +struct ring_buffer_t { + char *data; /* block of memory or array of data */ + unsigned element_size; /* how many bytes for each chunk */ + unsigned element_count; /* number of chunks of data */ + unsigned head; /* first chunk of data */ + unsigned count; /* number of chunks in use */ +}; +typedef struct ring_buffer_t RING_BUFFER; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + bool Ringbuf_Empty(RING_BUFFER const *b); + char *Ringbuf_Get_Front(RING_BUFFER const *b); + char *Ringbuf_Pop_Front(RING_BUFFER * b); + bool Ringbuf_Put(RING_BUFFER * b, /* ring buffer structure */ + char *data_element); /* one element to add to the ring */ + void Ringbuf_Init(RING_BUFFER * b, /* ring buffer structure */ + char *data, /* data block or array of data */ + unsigned element_size, /* size of one element in the data block */ + unsigned element_count); /* number of elements in the data block */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/rp.c b/bacnet-stack/rp.c index e69de29b..d26613d3 100644 --- a/bacnet-stack/rp.c +++ b/bacnet-stack/rp.c @@ -0,0 +1,341 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacdcode.h" +#include "bacdef.h" +#include "rp.h" + +/* encode service */ +int rp_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_READ_PROPERTY_DATA * data) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST; + apdu[1] = encode_max_segs_max_apdu(0, MAX_APDU); + apdu[2] = invoke_id; + apdu[3] = SERVICE_CONFIRMED_READ_PROPERTY; /* service choice */ + apdu_len = 4; + len = encode_context_object_id(&apdu[apdu_len], 0, + data->object_type, data->object_instance); + apdu_len += len; + len = encode_context_enumerated(&apdu[apdu_len], 1, + data->object_property); + apdu_len += len; + /* optional array index */ + if (data->array_index != BACNET_ARRAY_ALL) { + len = encode_context_unsigned(&apdu[apdu_len], 2, + data->array_index); + apdu_len += len; + } + } + + return apdu_len; +} + +/* decode the service request only */ +int rp_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_READ_PROPERTY_DATA * data) +{ + unsigned len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + int type = 0; /* for decoding */ + int property = 0; /* for decoding */ + uint32_t array_value = 0; /* for decoding */ + + /* check for value pointers */ + if (apdu_len && data) { + /* Tag 0: Object ID */ + if (!decode_is_context_tag(&apdu[len++], 0)) + return -1; + len += decode_object_id(&apdu[len], &type, &data->object_instance); + data->object_type = type; + /* Tag 1: Property ID */ + len += decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + if (tag_number != 1) + return -1; + len += decode_enumerated(&apdu[len], len_value_type, &property); + data->object_property = property; + /* Tag 2: Optional Array Index */ + if (len < apdu_len) { + len += decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value_type); + if (tag_number == 2) { + len += decode_unsigned(&apdu[len], len_value_type, + &array_value); + data->array_index = array_value; + } else + data->array_index = BACNET_ARRAY_ALL; + } else + data->array_index = BACNET_ARRAY_ALL; + } + + return (int) len; +} + +int rp_ack_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_READ_PROPERTY_DATA * data) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu[0] = PDU_TYPE_COMPLEX_ACK; /* complex ACK service */ + apdu[1] = invoke_id; /* original invoke id from request */ + apdu[2] = SERVICE_CONFIRMED_READ_PROPERTY; /* service choice */ + apdu_len = 3; + /* service ack follows */ + apdu_len += encode_context_object_id(&apdu[apdu_len], 0, + data->object_type, data->object_instance); + apdu_len += encode_context_enumerated(&apdu[apdu_len], 1, + data->object_property); + /* context 2 array index is optional */ + if (data->array_index != BACNET_ARRAY_ALL) { + apdu_len += encode_context_unsigned(&apdu[apdu_len], 2, + data->array_index); + } + /* propertyValue */ + apdu_len += encode_opening_tag(&apdu[apdu_len], 3); + for (len = 0; len < data->application_data_len; len++) { + apdu[apdu_len++] = data->application_data[len]; + } + apdu_len += encode_closing_tag(&apdu[apdu_len], 3); + } + + return apdu_len; +} + +int rp_ack_decode_service_request(uint8_t * apdu, int apdu_len, /* total length of the apdu */ + BACNET_READ_PROPERTY_DATA * data) +{ + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + int tag_len = 0; /* length of tag decode */ + int len = 0; /* total length of decodes */ + int object = 0, property = 0; /* for decoding */ + uint32_t array_value = 0; /* for decoding */ + + /* FIXME: check apdu_len against the len during decode */ + /* Tag 0: Object ID */ + if (!decode_is_context_tag(&apdu[0], 0)) + return -1; + len = 1; + len += decode_object_id(&apdu[len], &object, &data->object_instance); + data->object_type = object; + /* Tag 1: Property ID */ + len += decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + if (tag_number != 1) + return -1; + len += decode_enumerated(&apdu[len], len_value_type, &property); + data->object_property = property; + /* Tag 2: Optional Array Index */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + if (tag_number == 2) { + len += tag_len; + len += decode_unsigned(&apdu[len], len_value_type, &array_value); + data->array_index = array_value; + } else + data->array_index = BACNET_ARRAY_ALL; + /* Tag 3: opening context tag */ + if (decode_is_opening_tag_number(&apdu[len], 3)) { + /* a tag number of 3 is not extended so only one octet */ + len++; + /* don't decode the application tag number or its data here */ + data->application_data = &apdu[len]; + data->application_data_len = apdu_len - len - 1 /*closing tag */ ; + } else + return -1; + + return len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +int rp_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, BACNET_READ_PROPERTY_DATA * data) +{ + int len = 0; + unsigned offset = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) + return -1; + /* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */ + *invoke_id = apdu[2]; /* invoke id - filled in by net layer */ + if (apdu[3] != SERVICE_CONFIRMED_READ_PROPERTY) + return -1; + offset = 4; + + if (apdu_len > offset) { + len = rp_decode_service_request(&apdu[offset], + apdu_len - offset, data); + } + + return len; +} + +int rp_ack_decode_apdu(uint8_t * apdu, int apdu_len, /* total length of the apdu */ + uint8_t * invoke_id, BACNET_READ_PROPERTY_DATA * data) +{ + int len = 0; + int offset = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_COMPLEX_ACK) + return -1; + *invoke_id = apdu[1]; + if (apdu[2] != SERVICE_CONFIRMED_READ_PROPERTY) + return -1; + offset = 3; + if (apdu_len > offset) { + len = rp_ack_decode_service_request(&apdu[offset], + apdu_len - offset, data); + } + + return len; +} + +void testReadPropertyAck(Test * pTest) +{ + uint8_t apdu[480] = { 0 }; + uint8_t apdu2[480] = { 0 }; + int len = 0; + int apdu_len = 0; + uint8_t invoke_id = 1; + uint8_t test_invoke_id = 0; + BACNET_READ_PROPERTY_DATA data; + BACNET_READ_PROPERTY_DATA test_data; + BACNET_OBJECT_TYPE object_type = OBJECT_DEVICE; + uint32_t object_instance = 0; + int object = 0; + + data.object_type = OBJECT_DEVICE; + data.object_instance = 1; + data.object_property = PROP_OBJECT_IDENTIFIER; + data.array_index = BACNET_ARRAY_ALL; + + data.application_data_len = encode_bacnet_object_id(&apdu2[0], + data.object_type, data.object_instance); + data.application_data = &apdu2[0]; + + len = rp_ack_encode_apdu(&apdu[0], invoke_id, &data); + ct_test(pTest, len != 0); + ct_test(pTest, len != -1); + apdu_len = len; + len = rp_ack_decode_apdu(&apdu[0], apdu_len, /* total length of the apdu */ + &test_invoke_id, &test_data); + ct_test(pTest, len != -1); + ct_test(pTest, test_invoke_id == invoke_id); + + ct_test(pTest, test_data.object_type == data.object_type); + ct_test(pTest, test_data.object_instance == data.object_instance); + ct_test(pTest, test_data.object_property == data.object_property); + ct_test(pTest, test_data.array_index == data.array_index); + ct_test(pTest, + test_data.application_data_len == data.application_data_len); + + /* since object property == object_id, decode the application data using + the appropriate decode function */ + len = decode_object_id(test_data.application_data, + &object, &object_instance); + object_type = object; + ct_test(pTest, object_type == data.object_type); + ct_test(pTest, object_instance == data.object_instance); +} + +void testReadProperty(Test * pTest) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + uint8_t invoke_id = 128; + uint8_t test_invoke_id = 0; + BACNET_READ_PROPERTY_DATA data; + BACNET_READ_PROPERTY_DATA test_data; + + data.object_type = OBJECT_DEVICE; + data.object_instance = 1; + data.object_property = PROP_OBJECT_IDENTIFIER; + data.array_index = BACNET_ARRAY_ALL; + len = rp_encode_apdu(&apdu[0], invoke_id, &data); + ct_test(pTest, len != 0); + apdu_len = len; + + len = rp_decode_apdu(&apdu[0], apdu_len, &test_invoke_id, &test_data); + ct_test(pTest, len != -1); + ct_test(pTest, test_data.object_type == data.object_type); + ct_test(pTest, test_data.object_instance == data.object_instance); + ct_test(pTest, test_data.object_property == data.object_property); + ct_test(pTest, test_data.array_index == data.array_index); + + return; +} + +#ifdef TEST_READ_PROPERTY +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet ReadProperty", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testReadProperty); + assert(rc); + rc = ct_addTestFunction(pTest, testReadPropertyAck); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_READ_PROPERTY */ +#endif /* TEST */ diff --git a/bacnet-stack/rp.h b/bacnet-stack/rp.h index e69de29b..4332ab18 100644 --- a/bacnet-stack/rp.h +++ b/bacnet-stack/rp.h @@ -0,0 +1,84 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef READPROPERTY_H +#define READPROPERTY_H + +#include +#include + +typedef struct BACnet_Read_Property_Data { + BACNET_OBJECT_TYPE object_type; + uint32_t object_instance; + BACNET_PROPERTY_ID object_property; + int32_t array_index; + uint8_t *application_data; + int application_data_len; +} BACNET_READ_PROPERTY_DATA; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* encode service */ + int rp_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_READ_PROPERTY_DATA * data); + +/* decode the service request only */ + int rp_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_READ_PROPERTY_DATA * data); + + int rp_ack_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_READ_PROPERTY_DATA * data); + + int rp_ack_decode_service_request(uint8_t * apdu, int apdu_len, /* total length of the apdu */ + BACNET_READ_PROPERTY_DATA * data); + + +#ifdef TEST +#include "ctest.h" + int rp_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, BACNET_READ_PROPERTY_DATA * data); + + int rp_ack_decode_apdu(uint8_t * apdu, int apdu_len, /* total length of the apdu */ + uint8_t * invoke_id, BACNET_READ_PROPERTY_DATA * data); + + void test_ReadProperty(Test * pTest); + void test_ReadPropertyAck(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/rpm.c b/bacnet-stack/rpm.c index e69de29b..08b49ac9 100644 --- a/bacnet-stack/rpm.c +++ b/bacnet-stack/rpm.c @@ -0,0 +1,744 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacerror.h" +#include "bacdcode.h" +#include "bacdef.h" +#include "bacapp.h" +#include "rpm.h" + +/* encode the initial portion of the service */ +int rpm_encode_apdu_init(uint8_t * apdu, uint8_t invoke_id) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST; + apdu[1] = encode_max_segs_max_apdu(0, MAX_APDU); + apdu[2] = invoke_id; + apdu[3] = SERVICE_CONFIRMED_READ_PROPERTY_MULTIPLE; /* service choice */ + apdu_len = 4; + } + + return apdu_len; +} + +int rpm_encode_apdu_object_begin(uint8_t * apdu, + BACNET_OBJECT_TYPE object_type, uint32_t object_instance) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu_len = encode_context_object_id(&apdu[0], 0, + object_type, object_instance); + /* Tag 1: sequence of ReadAccessSpecification */ + apdu_len += encode_opening_tag(&apdu[apdu_len], 1); + } + + return apdu_len; +} + +int rpm_encode_apdu_object_property(uint8_t * apdu, + BACNET_PROPERTY_ID object_property, int32_t array_index) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu_len = encode_context_enumerated(&apdu[0], 0, object_property); + /* optional array index */ + if (array_index != BACNET_ARRAY_ALL) + apdu_len += encode_context_unsigned(&apdu[apdu_len], 1, + array_index); + } + + return apdu_len; +} + +int rpm_encode_apdu_object_end(uint8_t * apdu) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu_len = encode_closing_tag(&apdu[0], 1); + } + + return apdu_len; +} + +/* decode the object portion of the service request only */ +int rpm_decode_object_id(uint8_t * apdu, + unsigned apdu_len, + BACNET_OBJECT_TYPE * object_type, uint32_t * object_instance) +{ + unsigned len = 0; + int type = 0; /* for decoding */ + + /* check for value pointers */ + if (apdu && apdu_len && object_type && object_instance) { + /* Tag 0: Object ID */ + if (!decode_is_context_tag(&apdu[len++], 0)) + return -1; + len += decode_object_id(&apdu[len], &type, object_instance); + if (object_type) + *object_type = type; + /* Tag 1: sequence of ReadAccessSpecification */ + if (!decode_is_opening_tag_number(&apdu[len], 1)) + return -1; + len++; /* opening tag is only one octet */ + } + + return (int) len; +} + +int rpm_decode_object_end(uint8_t * apdu, unsigned apdu_len) +{ + int len = 0; /* total length of the apdu, return value */ + + if (apdu && apdu_len) { + if (decode_is_closing_tag_number(apdu, 1)) + len = 1; + } + + return len; +} + +/* decode the object property portion of the service request only */ +int rpm_decode_object_property(uint8_t * apdu, + unsigned apdu_len, + BACNET_PROPERTY_ID * object_property, int32_t * array_index) +{ + unsigned len = 0; + unsigned option_len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + int property = 0; /* for decoding */ + uint32_t array_value = 0; /* for decoding */ + + /* check for valid pointers */ + if (apdu && apdu_len && object_property && array_index) { + /* Tag 0: propertyIdentifier */ + len += decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + if (tag_number != 0) + return -1; + len += decode_enumerated(&apdu[len], len_value_type, &property); + if (object_property) + *object_property = property; + /* Tag 1: Optional propertyArrayIndex */ + if (len < apdu_len) { + option_len = + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value_type); + if (tag_number == 2) { + len += option_len; + len += decode_unsigned(&apdu[len], len_value_type, + &array_value); + *array_index = array_value; + } else + *array_index = BACNET_ARRAY_ALL; + } else + *array_index = BACNET_ARRAY_ALL; + } + + return (int) len; +} + +int rpm_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, + uint8_t ** service_request, unsigned *service_request_len) +{ + unsigned offset = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) + return -1; + /* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */ + *invoke_id = apdu[2]; /* invoke id - filled in by net layer */ + if (apdu[3] != SERVICE_CONFIRMED_READ_PROPERTY_MULTIPLE) + return -1; + offset = 4; + + if (apdu_len > offset) { + if (service_request) + *service_request = &apdu[offset]; + if (service_request_len) + *service_request_len = apdu_len - offset; + } + + return offset; +} + +int rpm_ack_encode_apdu_init(uint8_t * apdu, uint8_t invoke_id) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu[0] = PDU_TYPE_COMPLEX_ACK; /* complex ACK service */ + apdu[1] = invoke_id; /* original invoke id from request */ + apdu[2] = SERVICE_CONFIRMED_READ_PROPERTY_MULTIPLE; /* service choice */ + apdu_len = 3; + } + + return apdu_len; +} + +int rpm_ack_encode_apdu_object_begin(uint8_t * apdu, + BACNET_OBJECT_TYPE object_type, uint32_t object_instance) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + /* Tag 0: objectIdentifier */ + apdu_len = encode_context_object_id(&apdu[0], 0, + object_type, object_instance); + /* Tag 1: listOfResults */ + apdu_len += encode_opening_tag(&apdu[apdu_len], 1); + } + + return apdu_len; +} + +int rpm_ack_encode_apdu_object_property(uint8_t * apdu, + BACNET_PROPERTY_ID object_property, int32_t array_index) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + /* Tag 2: propertyIdentifier */ + apdu_len = encode_context_enumerated(&apdu[0], 2, object_property); + /* Tag 3: optional propertyArrayIndex */ + if (array_index != BACNET_ARRAY_ALL) + apdu_len += encode_context_unsigned(&apdu[apdu_len], 3, + array_index); + } + + return apdu_len; +} + +int rpm_ack_encode_apdu_object_property_value(uint8_t * apdu, + uint8_t * application_data, unsigned application_data_len) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + unsigned len = 0; + + if (apdu) { + /* Tag 4: propertyValue */ + apdu_len += encode_opening_tag(&apdu[apdu_len], 4); + for (len = 0; len < application_data_len; len++) { + apdu[apdu_len++] = application_data[len]; + } + apdu_len += encode_closing_tag(&apdu[apdu_len], 4); + } + + return apdu_len; +} + +int rpm_ack_encode_apdu_object_property_error(uint8_t * apdu, + BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + /* Tag 5: propertyAccessError */ + apdu_len += encode_opening_tag(&apdu[apdu_len], 5); + apdu_len += encode_tagged_enumerated(&apdu[apdu_len], error_class); + apdu_len += encode_tagged_enumerated(&apdu[apdu_len], error_code); + apdu_len += encode_closing_tag(&apdu[apdu_len], 5); + } + + return apdu_len; +} + +int rpm_ack_encode_apdu_object_end(uint8_t * apdu) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu_len = encode_closing_tag(&apdu[0], 1); + } + + return apdu_len; +} + +/* decode the object portion of the service request only */ +int rpm_ack_decode_object_id(uint8_t * apdu, + unsigned apdu_len, + BACNET_OBJECT_TYPE * object_type, uint32_t * object_instance) +{ + unsigned len = 0; + int type = 0; /* for decoding */ + + /* check for value pointers */ + if (apdu && apdu_len && object_type && object_instance) { + /* Tag 0: objectIdentifier */ + if (!decode_is_context_tag(&apdu[len++], 0)) + return -1; + len += decode_object_id(&apdu[len], &type, object_instance); + if (object_type) + *object_type = type; + /* Tag 1: listOfResults */ + if (!decode_is_opening_tag_number(&apdu[len], 1)) + return -1; + len++; /* opening tag is only one octet */ + } + + return (int) len; +} + +/* is this the end of the list of this objects properties values? */ +int rpm_ack_decode_object_end(uint8_t * apdu, unsigned apdu_len) +{ + int len = 0; /* total length of the apdu, return value */ + + if (apdu && apdu_len) { + if (decode_is_closing_tag_number(apdu, 1)) + len = 1; + } + + return len; +} + +int rpm_ack_decode_object_property(uint8_t * apdu, + unsigned apdu_len, + BACNET_PROPERTY_ID * object_property, int32_t * array_index) +{ + unsigned len = 0; + unsigned tag_len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + int property = 0; /* for decoding */ + uint32_t array_value = 0; /* for decoding */ + + /* check for valid pointers */ + if (apdu && apdu_len && object_property && array_index) { + /* Tag 2: propertyIdentifier */ + len += decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + if (tag_number != 2) + return -1; + len += decode_enumerated(&apdu[len], len_value_type, &property); + if (object_property) + *object_property = property; + /* Tag 3: Optional propertyArrayIndex */ + tag_len = decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value_type); + if (tag_number == 3) { + len += tag_len; + len += decode_unsigned(&apdu[len], len_value_type, + &array_value); + *array_index = array_value; + } else + *array_index = BACNET_ARRAY_ALL; + } + + return (int) len; +} + +int rpm_ack_decode_apdu(uint8_t * apdu, int apdu_len, /* total length of the apdu */ + uint8_t * invoke_id, + uint8_t ** service_request, unsigned *service_request_len) +{ + int offset = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_COMPLEX_ACK) + return -1; + *invoke_id = apdu[1]; + if (apdu[2] != SERVICE_CONFIRMED_READ_PROPERTY_MULTIPLE) + return -1; + offset = 3; + if (apdu_len > offset) { + if (service_request) + *service_request = &apdu[offset]; + if (service_request_len) + *service_request_len = apdu_len - offset; + } + + return offset; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testReadPropertyMultiple(Test * pTest) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int test_len = 0; + int apdu_len = 0; + uint8_t invoke_id = 12; + uint8_t test_invoke_id = 0; + uint8_t *service_request = NULL; + unsigned service_request_len = 0; + BACNET_OBJECT_TYPE object_type = OBJECT_DEVICE; + uint32_t object_instance = 0; + BACNET_PROPERTY_ID object_property = PROP_OBJECT_IDENTIFIER; + int32_t array_index = 0; + + /* build the RPM - try to make it easy for the Application Layer development */ + /* IDEA: similar construction, but pass apdu, apdu_len pointer, size of apdu to + let the called function handle the out of space problem that these get into + by returning a boolean of success/failure. + It almost needs to use the keylist library or something similar. + Also check case of storing a backoff point (i.e. save enough room for object_end) */ + apdu_len = rpm_encode_apdu_init(&apdu[0], invoke_id); + /* each object has a beginning and an end */ + apdu_len += rpm_encode_apdu_object_begin(&apdu[apdu_len], + OBJECT_DEVICE, 123); + /* then stuff as many properties into it as APDU length will allow */ + apdu_len += rpm_encode_apdu_object_property(&apdu[apdu_len], + PROP_OBJECT_IDENTIFIER, BACNET_ARRAY_ALL); + apdu_len += rpm_encode_apdu_object_property(&apdu[apdu_len], + PROP_OBJECT_NAME, BACNET_ARRAY_ALL); + apdu_len += rpm_encode_apdu_object_end(&apdu[apdu_len]); + /* each object has a beginning and an end */ + apdu_len += rpm_encode_apdu_object_begin(&apdu[apdu_len], + OBJECT_ANALOG_INPUT, 33); + apdu_len += rpm_encode_apdu_object_property(&apdu[apdu_len], + PROP_OBJECT_IDENTIFIER, BACNET_ARRAY_ALL); + apdu_len += rpm_encode_apdu_object_property(&apdu[apdu_len], + PROP_ALL, BACNET_ARRAY_ALL); + apdu_len += rpm_encode_apdu_object_end(&apdu[apdu_len]); + + ct_test(pTest, apdu_len != 0); + + test_len = rpm_decode_apdu(&apdu[0], apdu_len, &test_invoke_id, &service_request, /* will point to the service request in the apdu */ + &service_request_len); + ct_test(pTest, test_len != -1); + ct_test(pTest, test_invoke_id == invoke_id); + ct_test(pTest, service_request != NULL); + ct_test(pTest, service_request_len > 0); + + test_len = rpm_decode_object_id(service_request, + service_request_len, &object_type, &object_instance); + ct_test(pTest, test_len != -1); + ct_test(pTest, object_type == OBJECT_DEVICE); + ct_test(pTest, object_instance == 123); + len = test_len; + /* decode the object property portion of the service request */ + test_len = rpm_decode_object_property(&service_request[len], + service_request_len - len, &object_property, &array_index); + ct_test(pTest, test_len != -1); + ct_test(pTest, object_property == PROP_OBJECT_IDENTIFIER); + ct_test(pTest, array_index == BACNET_ARRAY_ALL); + len += test_len; + test_len = rpm_decode_object_property(&service_request[len], + service_request_len - len, &object_property, &array_index); + ct_test(pTest, test_len != -1); + ct_test(pTest, object_property == PROP_OBJECT_NAME); + ct_test(pTest, array_index == BACNET_ARRAY_ALL); + len += test_len; + /* try again - we should fail */ + test_len = rpm_decode_object_property(&service_request[len], + service_request_len - len, &object_property, &array_index); + ct_test(pTest, test_len == -1); + /* is it the end of this object? */ + test_len = rpm_decode_object_end(&service_request[len], + service_request_len - len); + ct_test(pTest, test_len == 1); + len += test_len; + /* try to decode an object id */ + test_len = rpm_decode_object_id(&service_request[len], + service_request_len - len, &object_type, &object_instance); + ct_test(pTest, test_len != -1); + ct_test(pTest, object_type == OBJECT_ANALOG_INPUT); + ct_test(pTest, object_instance == 33); + len += test_len; + /* decode the object property portion of the service request only */ + test_len = rpm_decode_object_property(&service_request[len], + service_request_len - len, &object_property, &array_index); + ct_test(pTest, test_len != -1); + ct_test(pTest, object_property == PROP_OBJECT_IDENTIFIER); + ct_test(pTest, array_index == BACNET_ARRAY_ALL); + len += test_len; + test_len = rpm_decode_object_property(&service_request[len], + service_request_len - len, &object_property, &array_index); + ct_test(pTest, test_len != -1); + ct_test(pTest, object_property == PROP_ALL); + ct_test(pTest, array_index == BACNET_ARRAY_ALL); + len += test_len; + test_len = rpm_decode_object_property(&service_request[len], + service_request_len - len, &object_property, &array_index); + ct_test(pTest, test_len == -1); + /* got an error -1, is it the end of this object? */ + test_len = rpm_decode_object_end(&service_request[len], + service_request_len - len); + ct_test(pTest, test_len == 1); + len += test_len; + ct_test(pTest, len == service_request_len); +} + +void testReadPropertyMultipleAck(Test * pTest) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int test_len = 0; + int apdu_len = 0; + uint8_t invoke_id = 12; + uint8_t test_invoke_id = 0; + uint8_t *service_request = NULL; + unsigned service_request_len = 0; + BACNET_OBJECT_TYPE object_type = OBJECT_DEVICE; + uint32_t object_instance = 0; + BACNET_PROPERTY_ID object_property = PROP_OBJECT_IDENTIFIER; + int32_t array_index = 0; + BACNET_APPLICATION_DATA_VALUE application_data[4] = { {0} }; + BACNET_APPLICATION_DATA_VALUE test_application_data = { 0 }; + uint8_t application_data_buffer[MAX_APDU] = { 0 }; + int application_data_buffer_len = 0; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + /* build the RPM - try to make it easy for the + Application Layer development */ + /* IDEA: similar construction, but pass apdu, apdu_len pointer, + size of apdu to let the called function handle the out of + space problem that these get into by returning a boolean + of success/failure. + It almost needs to use the keylist library or something similar. + Also check case of storing a backoff point + (i.e. save enough room for object_end) */ + apdu_len = rpm_ack_encode_apdu_init(&apdu[0], invoke_id); + + /* object beginning */ + apdu_len += rpm_ack_encode_apdu_object_begin(&apdu[apdu_len], + OBJECT_DEVICE, 123); + /* reply property */ + apdu_len += rpm_ack_encode_apdu_object_property(&apdu[apdu_len], + PROP_OBJECT_IDENTIFIER, BACNET_ARRAY_ALL); + /* reply value */ + application_data[0].tag = BACNET_APPLICATION_TAG_OBJECT_ID; + application_data[0].type.Object_Id.type = OBJECT_DEVICE; + application_data[0].type.Object_Id.instance = 123; + application_data_buffer_len = + bacapp_encode_application_data(&application_data_buffer[0], + &application_data[0]); + apdu_len += + rpm_ack_encode_apdu_object_property_value(&apdu[apdu_len], + &application_data_buffer[0], application_data_buffer_len); + /* reply property */ + apdu_len += rpm_ack_encode_apdu_object_property(&apdu[apdu_len], + PROP_OBJECT_TYPE, BACNET_ARRAY_ALL); + /* reply value */ + application_data[1].tag = BACNET_APPLICATION_TAG_ENUMERATED; + application_data[1].type.Enumerated = OBJECT_DEVICE; + application_data_buffer_len = + bacapp_encode_application_data(&application_data_buffer[0], + &application_data[1]); + apdu_len += + rpm_ack_encode_apdu_object_property_value(&apdu[apdu_len], + &application_data_buffer[0], application_data_buffer_len); + /* object end */ + apdu_len += rpm_ack_encode_apdu_object_end(&apdu[apdu_len]); + + /* object beginning */ + apdu_len += rpm_ack_encode_apdu_object_begin(&apdu[apdu_len], + OBJECT_ANALOG_INPUT, 33); + /* reply property */ + apdu_len += rpm_ack_encode_apdu_object_property(&apdu[apdu_len], + PROP_PRESENT_VALUE, BACNET_ARRAY_ALL); + /* reply value */ + application_data[2].tag = BACNET_APPLICATION_TAG_REAL; + application_data[2].type.Real = 0.0; + application_data_buffer_len = + bacapp_encode_application_data(&application_data_buffer[0], + &application_data[2]); + apdu_len += + rpm_ack_encode_apdu_object_property_value(&apdu[apdu_len], + &application_data_buffer[0], application_data_buffer_len); + /* reply property */ + apdu_len += rpm_ack_encode_apdu_object_property(&apdu[apdu_len], + PROP_DEADBAND, BACNET_ARRAY_ALL); + /* reply error */ + apdu_len += rpm_ack_encode_apdu_object_property_error(&apdu[apdu_len], + ERROR_CLASS_PROPERTY, ERROR_CODE_UNKNOWN_PROPERTY); + /* object end */ + apdu_len += rpm_ack_encode_apdu_object_end(&apdu[apdu_len]); + ct_test(pTest, apdu_len != 0); + + /****** decode the packet ******/ + test_len = rpm_ack_decode_apdu(&apdu[0], apdu_len, &test_invoke_id, &service_request, /* will point to the service request in the apdu */ + &service_request_len); + ct_test(pTest, test_len != -1); + ct_test(pTest, test_invoke_id == invoke_id); + ct_test(pTest, service_request != NULL); + ct_test(pTest, service_request_len > 0); + /* the first part should be the first object id */ + test_len = rpm_ack_decode_object_id(service_request, + service_request_len, &object_type, &object_instance); + ct_test(pTest, test_len != -1); + ct_test(pTest, object_type == OBJECT_DEVICE); + ct_test(pTest, object_instance == 123); + len = test_len; + /* extract the property */ + test_len = rpm_ack_decode_object_property(&service_request[len], + service_request_len - len, &object_property, &array_index); + ct_test(pTest, object_property == PROP_OBJECT_IDENTIFIER); + ct_test(pTest, array_index == BACNET_ARRAY_ALL); + len += test_len; + /* what is the result? An error or a value? */ + ct_test(pTest, decode_is_opening_tag_number(&service_request[len], 4)); + len++; + /* decode the object property portion of the service request */ + /* note: if this was an array, there could have been + more than one element to decode */ + test_len = bacapp_decode_application_data(&service_request[len], + service_request_len - len, &test_application_data); + ct_test(pTest, test_len > 0); + ct_test(pTest, bacapp_same_value(&application_data[0], + &test_application_data)); + len += test_len; + ct_test(pTest, decode_is_closing_tag_number(&service_request[len], 4)); + len++; + /* see if there is another property */ + test_len = rpm_ack_decode_object_property(&service_request[len], + service_request_len - len, &object_property, &array_index); + ct_test(pTest, test_len != -1); + ct_test(pTest, object_property == PROP_OBJECT_TYPE); + ct_test(pTest, array_index == BACNET_ARRAY_ALL); + len += test_len; + /* what is the result value? */ + ct_test(pTest, decode_is_opening_tag_number(&service_request[len], 4)); + len++; + /* decode the object property portion of the service request */ + test_len = bacapp_decode_application_data(&service_request[len], + service_request_len - len, &test_application_data); + ct_test(pTest, test_len > 0); + ct_test(pTest, bacapp_same_value(&application_data[1], + &test_application_data)); + len += test_len; + ct_test(pTest, decode_is_closing_tag_number(&service_request[len], 4)); + len++; + /* see if there is another property */ + /* this time we should fail */ + test_len = rpm_ack_decode_object_property(&service_request[len], + service_request_len - len, &object_property, &array_index); + ct_test(pTest, test_len == -1); + /* see if it is the end of this object */ + test_len = rpm_ack_decode_object_end(&service_request[len], + service_request_len - len); + ct_test(pTest, test_len == 1); + len += test_len; + /* try to decode another object id */ + test_len = rpm_ack_decode_object_id(&service_request[len], + service_request_len - len, &object_type, &object_instance); + ct_test(pTest, test_len != -1); + ct_test(pTest, object_type == OBJECT_ANALOG_INPUT); + ct_test(pTest, object_instance == 33); + len += test_len; + /* decode the object property portion of the service request only */ + test_len = rpm_ack_decode_object_property(&service_request[len], + service_request_len - len, &object_property, &array_index); + ct_test(pTest, test_len != -1); + ct_test(pTest, object_property == PROP_PRESENT_VALUE); + ct_test(pTest, array_index == BACNET_ARRAY_ALL); + len += test_len; + /* what is the result value? */ + ct_test(pTest, decode_is_opening_tag_number(&service_request[len], 4)); + len++; + /* decode the object property portion of the service request */ + test_len = bacapp_decode_application_data(&service_request[len], + service_request_len - len, &test_application_data); + ct_test(pTest, test_len > 0); + ct_test(pTest, bacapp_same_value(&application_data[2], + &test_application_data)); + len += test_len; + ct_test(pTest, decode_is_closing_tag_number(&service_request[len], 4)); + len++; + /* see if there is another property */ + test_len = rpm_ack_decode_object_property(&service_request[len], + service_request_len - len, &object_property, &array_index); + ct_test(pTest, test_len != -1); + ct_test(pTest, object_property == PROP_DEADBAND); + ct_test(pTest, array_index == BACNET_ARRAY_ALL); + len += test_len; + /* what is the result value? */ + ct_test(pTest, decode_is_opening_tag_number(&service_request[len], 5)); + len++; + /* it was an error reply */ + test_len = bacerror_decode_error_class_and_code(&service_request[len], + service_request_len - len, &error_class, &error_code); + ct_test(pTest, test_len != 0); + ct_test(pTest, error_class == ERROR_CLASS_PROPERTY); + ct_test(pTest, error_code == ERROR_CODE_UNKNOWN_PROPERTY); + len += test_len; + ct_test(pTest, decode_is_closing_tag_number(&service_request[len], 5)); + len++; + /* is there another property? */ + test_len = rpm_ack_decode_object_property(&service_request[len], + service_request_len - len, &object_property, &array_index); + ct_test(pTest, test_len == -1); + /* got an error -1, is it the end of this object? */ + test_len = rpm_ack_decode_object_end(&service_request[len], + service_request_len - len); + ct_test(pTest, test_len == 1); + len += test_len; + /* check for another object */ + test_len = rpm_ack_decode_object_id(&service_request[len], + service_request_len - len, &object_type, &object_instance); + ct_test(pTest, test_len == 0); + ct_test(pTest, len == service_request_len); +} + +#ifdef TEST_READ_PROPERTY_MULTIPLE +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet ReadPropertyMultiple", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testReadPropertyMultiple); + assert(rc); + rc = ct_addTestFunction(pTest, testReadPropertyMultipleAck); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_READ_PROPERTY_MULTIPLE */ + +#endif /* TEST */ diff --git a/bacnet-stack/rpm.h b/bacnet-stack/rpm.h index e69de29b..e868657d 100644 --- a/bacnet-stack/rpm.h +++ b/bacnet-stack/rpm.h @@ -0,0 +1,119 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef RPM_H +#define RPM_H + +#include +#include +#include "bacenum.h" +#include "bacdef.h" +#include "bacapp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* encode functions */ +/* Start with the Init function, and then add an object, + then add its properties, and then end the object. + Continue to add objects and properties as needed + until the APDU is full.*/ + +/* RPM */ + int rpm_encode_apdu_init(uint8_t * apdu, uint8_t invoke_id); + + int rpm_encode_apdu_object_begin(uint8_t * apdu, + BACNET_OBJECT_TYPE object_type, uint32_t object_instance); + + int rpm_encode_apdu_object_property(uint8_t * apdu, + BACNET_PROPERTY_ID object_property, int32_t array_index); + + int rpm_encode_apdu_object_end(uint8_t * apdu); + + int rpm_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, + uint8_t ** service_request, unsigned *service_request_len); + +/* decode the object portion of the service request only */ + int rpm_decode_object_id(uint8_t * apdu, + unsigned apdu_len, + BACNET_OBJECT_TYPE * object_type, uint32_t * object_instance); + +/* is this the end of this object property list? */ + int rpm_decode_object_end(uint8_t * apdu, unsigned apdu_len); + +/* decode the object property portion of the service request only */ + int rpm_decode_object_property(uint8_t * apdu, + unsigned apdu_len, + BACNET_PROPERTY_ID * object_property, int32_t * array_index); + +/* RPM Ack */ + int rpm_ack_encode_apdu_object_begin(uint8_t * apdu, + BACNET_OBJECT_TYPE object_type, uint32_t object_instance); + + int rpm_ack_encode_apdu_object_property_value(uint8_t * apdu, + uint8_t * application_data, unsigned application_data_len); + + int rpm_ack_encode_apdu_object_property_error(uint8_t * apdu, + BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code); + + int rpm_ack_encode_apdu_object_end(uint8_t * apdu); + + int rpm_ack_decode_object_id(uint8_t * apdu, + unsigned apdu_len, + BACNET_OBJECT_TYPE * object_type, uint32_t * object_instance); +/* is this the end of the list of this objects properties values? */ + int rpm_ack_decode_object_end(uint8_t * apdu, unsigned apdu_len); + int rpm_ack_decode_object_property(uint8_t * apdu, + unsigned apdu_len, + BACNET_PROPERTY_ID * object_property, int32_t * array_index); +/* decode the object property value portion of the service request only */ + int rpm_ack_decode_object_property_value(uint8_t * apdu, + unsigned apdu_len, + uint8_t ** application_data, unsigned *application_data_len); + int rpm_ack_decode_apdu(uint8_t * apdu, int apdu_len, /* total length of the apdu */ + uint8_t * invoke_id, + uint8_t ** service_request, unsigned *service_request_len); + +#ifdef TEST +#include "ctest.h" + void testReadPropertyMultiple(Test * pTest); + void testReadPropertyMultipleAck(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/sbuf.c b/bacnet-stack/sbuf.c index e69de29b..cbb5d954 100644 --- a/bacnet-stack/sbuf.c +++ b/bacnet-stack/sbuf.c @@ -0,0 +1,209 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 by Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307 + USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +/* Functional Description: Static buffer library for deeply + embedded system. See the unit tests for usage examples. */ +#include +#include +#include +#include "sbuf.h" + +void sbuf_init(STATIC_BUFFER * b, /* static buffer structure */ + char *data, /* actual size, in bytes, of the data block or array of data */ + unsigned size) +{ /* number of bytes used */ + if (b) { + b->data = data; + b->size = size; + b->count = 0; + } + + return; +} + +/* returns true if count==0, false if count > 0 */ +bool sbuf_empty(STATIC_BUFFER const *b) +{ + return (b ? (b->count == 0) : false); +} + +char *sbuf_data(STATIC_BUFFER const *b) +{ + return (b ? b->data : NULL); +} + +unsigned sbuf_size(STATIC_BUFFER * b) +{ + return (b ? b->size : 0); +} + +unsigned sbuf_count(STATIC_BUFFER * b) +{ + return (b ? b->count : 0); +} + +/* returns true if successful, false if not enough room to append data */ +bool sbuf_put(STATIC_BUFFER * b, /* static buffer structure */ + unsigned offset, /* where to start */ + char *data, /* number of bytes used */ + unsigned data_size) +{ /* how many to add */ + bool status = false; /* return value */ + + if (b && b->data) { + if (((offset + data_size) < b->size)) { + b->count = offset + data_size; + while (data_size) { + b->data[offset] = *data; + offset++; + data++; + data_size--; + } + status = true; + } + } + + return status; +} + +/* returns true if successful, false if not enough room to append data */ +bool sbuf_append(STATIC_BUFFER * b, /* static buffer structure */ + char *data, /* number of bytes used */ + unsigned data_size) +{ /* how many to add */ + unsigned count = 0; + + if (b) + count = b->count; + + return sbuf_put(b, count, data, data_size); +} + +/* returns true if successful, false if not enough room to append data */ +bool sbuf_truncate(STATIC_BUFFER * b, /* static buffer structure */ + unsigned count) +{ /* total number of bytes in use */ + bool status = false; /* return value */ + + if (b) { + if (count < b->size) { + b->count = count; + status = true; + } + } + + return status; +} + +#ifdef TEST +#include +#include + +#include "ctest.h" + +void testStaticBuffer(Test * pTest) +{ + STATIC_BUFFER sbuffer; + char *data1 = "Joshua"; + char *data2 = "Anna"; + char *data3 = "Christopher"; + char *data4 = "Mary"; + char data_buffer[480] = ""; + char test_data_buffer[480] = ""; + char *data; + unsigned count; + + sbuf_init(&sbuffer, NULL, 0); + ct_test(pTest, sbuf_empty(&sbuffer) == true); + ct_test(pTest, sbuf_data(&sbuffer) == NULL); + ct_test(pTest, sbuf_size(&sbuffer) == 0); + ct_test(pTest, sbuf_count(&sbuffer) == 0); + ct_test(pTest, sbuf_append(&sbuffer, data1, strlen(data1)) == false); + + sbuf_init(&sbuffer, data_buffer, sizeof(data_buffer)); + ct_test(pTest, sbuf_empty(&sbuffer) == true); + ct_test(pTest, sbuf_data(&sbuffer) == data_buffer); + ct_test(pTest, sbuf_size(&sbuffer) == sizeof(data_buffer)); + ct_test(pTest, sbuf_count(&sbuffer) == 0); + + ct_test(pTest, sbuf_append(&sbuffer, data1, strlen(data1)) == true); + ct_test(pTest, sbuf_append(&sbuffer, data2, strlen(data2)) == true); + ct_test(pTest, sbuf_append(&sbuffer, data3, strlen(data3)) == true); + ct_test(pTest, sbuf_append(&sbuffer, data4, strlen(data4)) == true); + strcat(test_data_buffer, data1); + strcat(test_data_buffer, data2); + strcat(test_data_buffer, data3); + strcat(test_data_buffer, data4); + ct_test(pTest, sbuf_count(&sbuffer) == strlen(test_data_buffer)); + + data = sbuf_data(&sbuffer); + count = sbuf_count(&sbuffer); + ct_test(pTest, memcmp(data, test_data_buffer, count) == 0); + ct_test(pTest, count == strlen(test_data_buffer)); + + ct_test(pTest, sbuf_truncate(&sbuffer, 0) == true); + ct_test(pTest, sbuf_count(&sbuffer) == 0); + ct_test(pTest, sbuf_size(&sbuffer) == sizeof(data_buffer)); + ct_test(pTest, sbuf_append(&sbuffer, data4, strlen(data4)) == true); + data = sbuf_data(&sbuffer); + count = sbuf_count(&sbuffer); + ct_test(pTest, memcmp(data, data4, count) == 0); + ct_test(pTest, count == strlen(data4)); + + return; +} + +#ifdef TEST_STATIC_BUFFER +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("static buffer", NULL); + + /* individual tests */ + rc = ct_addTestFunction(pTest, testStaticBuffer); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_STATIC_BUFFER */ +#endif /* TEST */ diff --git a/bacnet-stack/sbuf.h b/bacnet-stack/sbuf.h index e69de29b..ffcad78a 100644 --- a/bacnet-stack/sbuf.h +++ b/bacnet-stack/sbuf.h @@ -0,0 +1,80 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 by Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307 + USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +/* Functional Description: Static buffer library for deeply + embedded system. See the unit tests for usage examples. */ + +#ifndef SBUF_H +#define SBUF_H + +#include +#include + +struct static_buffer_t { + char *data; /* block of memory or array of data */ + unsigned size; /* actual size, in bytes, of the block of data */ + unsigned count; /* number of bytes in use */ +}; +typedef struct static_buffer_t STATIC_BUFFER; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + void sbuf_init(STATIC_BUFFER * b, /* static buffer structure */ + char *data, /* actual size, in bytes, of the data block or array of data */ + unsigned size); /* number of bytes used */ +/* returns true if size==0, false if size > 0 */ + bool sbuf_empty(STATIC_BUFFER const *b); + char *sbuf_data(STATIC_BUFFER const *b); + unsigned sbuf_size(STATIC_BUFFER * b); + unsigned sbuf_count(STATIC_BUFFER * b); +/* returns true if successful, false if not enough room to append data */ + bool sbuf_put(STATIC_BUFFER * b, /* static buffer structure */ + unsigned offset, /* where to start */ + char *data, /* number of bytes used */ + unsigned data_size); /* how many to add */ +/* returns true if successful, false if not enough room to append data */ + bool sbuf_append(STATIC_BUFFER * b, /* static buffer structure */ + char *data, /* number of bytes used */ + unsigned data_size); /* how many to add */ +/* returns true if successful, false if not enough room to append data */ + bool sbuf_truncate(STATIC_BUFFER * b, /* static buffer structure */ + unsigned count); /* total number of bytes in use */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/test/ctest.c b/bacnet-stack/test/ctest.c index e69de29b..94739b79 100644 --- a/bacnet-stack/test/ctest.c +++ b/bacnet-stack/test/ctest.c @@ -0,0 +1,169 @@ +/* ctest.c: Implements the CTest Framework */ + +#include "ctest.h" +#include +#include +#include +#include + +/* Number of tests to hold incrementally */ +enum { CHUNK = 10 }; + +Test *ct_create(const char *name, void (*init) (Test *)) +{ + int backOutLevel = 0; + Test *pTest = malloc(sizeof(Test)); + if (pTest) { + pTest->nPass = pTest->nFail = pTest->nTests = 0; + pTest->pStream = stdout; + + /* Allocate array of fptrs: */ + assert(CHUNK); + pTest->pTestFuns = calloc(CHUNK, sizeof(TestFunc)); + if (pTest->pTestFuns) { + pTest->maxTests = CHUNK; + /* Allocate test name: */ + assert(name); + pTest->name = malloc(strlen(name) + 1); + if (pTest->name) + strcpy(pTest->name, name); + else + ++backOutLevel; + } else + ++backOutLevel; + } + + /* Back-out allocations if memory failed: */ + if (backOutLevel) { + switch (backOutLevel) { + case 2: + free(pTest->pTestFuns); + pTest->pTestFuns = NULL; + case 1: + free(pTest); + pTest = NULL; + } + } else if (init) { + assert(pTest); + init(pTest); + } + return pTest; +} + +void ct_destroy(Test * pTest) +{ + assert(pTest); + assert(pTest->pTestFuns); + free(pTest->pTestFuns); + pTest->pTestFuns = NULL; + assert(pTest->name); + free(pTest->name); + pTest->name = NULL; + free(pTest); +} + +bool ct_addTestFunction(Test * pTest, TestFunc tfun) +{ + assert(pTest); + assert(pTest->pTestFuns); + if (pTest->nTests == pTest->maxTests) { + size_t newSize = pTest->nTests + CHUNK; + TestFunc *new_pTestFuns = realloc(pTest->pTestFuns, + newSize * sizeof(TestFunc)); + if (!new_pTestFuns) + return false; + pTest->pTestFuns = new_pTestFuns; + pTest->maxTests += CHUNK; + } + assert(pTest->nTests < pTest->maxTests); + pTest->pTestFuns[pTest->nTests++] = tfun; + return true; +} + +void ct_setStream(Test * pTest, FILE * pStream) +{ + pTest->pStream = pStream; +} + +FILE *ct_getStream(Test * pTest) +{ + return pTest->pStream; +} + +long ct_report(Test * pTest) +{ + assert(pTest); + if (pTest->pStream) { + fprintf(pTest->pStream, + "Test \"%s\":\n\tPassed: %ld\n\tFailed: %ld\n", + pTest->name, pTest->nPass, pTest->nFail); + } + return pTest->nFail; +} + + +void ct_succeed(Test * pTest) +{ + assert(pTest); + ++pTest->nPass; +} + +void ct_do_test(Test * pTest, const char *str, + bool cond, const char *file, long line) +{ + assert(pTest); + if (!cond) + ct_do_fail(pTest, str, file, line); + else + ct_succeed(pTest); +} + +void ct_do_fail(Test * pTest, const char *str, const char *file, long line) +{ + assert(pTest); + ++pTest->nFail; + if (pTest->pStream) { + fprintf(pTest->pStream, + "%s failure: (%s), %s (line %ld)\n", + pTest->name, str, file, line); + } +} + +long ct_getNumPassed(Test * pTest) +{ + assert(pTest); + return pTest->nPass; +} + +long ct_getNumFailed(Test * pTest) +{ + assert(pTest); + return pTest->nFail; +} + +long ct_run(Test * pTest) +{ + size_t testNum; + assert(pTest); + for (testNum = 0; testNum < pTest->nTests; ++testNum) + pTest->pTestFuns[testNum] (pTest); + return pTest->nFail; +} + +void ct_reset(Test * pTest) +{ + assert(pTest); + pTest->nFail = pTest->nPass = 0; +} + +const char *ct_getName(Test * pTest) +{ + assert(pTest); + return (pTest->name); +} + +long ct_getNumTests(Test * pTest) +{ + assert(pTest); + return pTest->nTests; +} diff --git a/bacnet-stack/test/ctest.h b/bacnet-stack/test/ctest.h index e69de29b..9be0bbe6 100644 --- a/bacnet-stack/test/ctest.h +++ b/bacnet-stack/test/ctest.h @@ -0,0 +1,59 @@ +/* ctest.h + * + * Defines a test framework for C projects. + */ +#ifndef CTEST_H +#define CTEST_H + +#include +#include + +#define ct_test(test, cond) \ + ct_do_test(test, #cond, cond, __FILE__, __LINE__) +#define ct_fail(test, str) \ + ct_do_fail(test, str, __FILE__, __LINE__) + +typedef struct _Test Test; + +typedef void (*TestFunc) (Test *); + +struct _Test { + char *name; + FILE *pStream; + size_t nTests; + size_t maxTests; + TestFunc *pTestFuns; + long nPass; + long nFail; +}; + +#ifdef __cplusplus +extern "C" { +#endif + + Test *ct_create(const char *name, void (*init) (Test *)); + void ct_destroy(Test * pTest); + + const char *ct_getName(Test * pTest); + long ct_getNumPassed(Test * pTest); + long ct_getNumFailed(Test * pTest); + long ct_getNumTests(Test * pTest); + FILE *ct_getStream(Test * pTest); + void ct_setStream(Test * pTest, FILE * stream); + + bool ct_addTestFunction(Test * pTest, TestFunc tfun); + void ct_succeed(Test * pTest); + long ct_run(Test * pTest); + long ct_report(Test * pTest); + void ct_reset(Test * pTest); + +/* Not intended for end-users: */ + void ct_do_test(Test * pTest, const char *str, + bool cond, const char *file, long line); + void ct_do_fail(Test * pTest, const char *str, + const char *file, long line); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/bacnet-stack/timesync.c b/bacnet-stack/timesync.c index e69de29b..77839d1d 100644 --- a/bacnet-stack/timesync.c +++ b/bacnet-stack/timesync.c @@ -0,0 +1,214 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacdcode.h" +#include "bacdef.h" +#include "bacapp.h" +#include "timesync.h" + +/* encode service */ +int timesync_encode_apdu_service(uint8_t * apdu, + BACNET_UNCONFIRMED_SERVICE service, + BACNET_DATE * my_date, BACNET_TIME * my_time) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu && my_date && my_time) { + apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST; + apdu[1] = service; + apdu_len = 2; + len = encode_tagged_date(&apdu[apdu_len], my_date); + apdu_len += len; + len = encode_tagged_time(&apdu[apdu_len], my_time); + apdu_len += len; + } + + return apdu_len; +} + +int timesync_utc_encode_apdu(uint8_t * apdu, + BACNET_DATE * my_date, BACNET_TIME * my_time) +{ + return timesync_encode_apdu_service(apdu, + SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION, my_date, my_time); +} + +int timesync_encode_apdu(uint8_t * apdu, + BACNET_DATE * my_date, BACNET_TIME * my_time) +{ + return timesync_encode_apdu_service(apdu, + SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION, my_date, my_time); +} + +/* decode the service request only */ +int timesync_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_DATE * my_date, BACNET_TIME * my_time) +{ + int len = 0; + uint8_t tag_number = 0; + uint32_t len_value = 0; + + if (apdu_len && my_date && my_time) { + /* date */ + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + if (tag_number == BACNET_APPLICATION_TAG_DATE) { + len += decode_date(&apdu[len], my_date); + } else + return -1; + /* time */ + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + if (tag_number == BACNET_APPLICATION_TAG_TIME) { + len += decode_bacnet_time(&apdu[len], my_time); + } else + return -1; + } + + return len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +int timesync_decode_apdu_service(uint8_t * apdu, + BACNET_UNCONFIRMED_SERVICE service, + unsigned apdu_len, BACNET_DATE * my_date, BACNET_TIME * my_time) +{ + int len = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST) + return -1; + if (apdu[1] != service) + return -1; + /* optional limits - must be used as a pair */ + if (apdu_len > 2) { + len = timesync_decode_service_request(&apdu[2], apdu_len - 2, + my_date, my_time); + } + + return len; +} + +int timesync_utc_decode_apdu(uint8_t * apdu, + unsigned apdu_len, BACNET_DATE * my_date, BACNET_TIME * my_time) +{ + return timesync_decode_apdu_service(apdu, + SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION, + apdu_len, my_date, my_time); +} + +int timesync_decode_apdu(uint8_t * apdu, + unsigned apdu_len, BACNET_DATE * my_date, BACNET_TIME * my_time) +{ + return timesync_decode_apdu_service(apdu, + SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION, + apdu_len, my_date, my_time); +} + +void testTimeSyncData(Test * pTest, + BACNET_DATE * my_date, BACNET_TIME * my_time) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + BACNET_DATE test_date; + BACNET_TIME test_time; + + len = timesync_encode_apdu(&apdu[0], my_date, my_time); + ct_test(pTest, len != 0); + apdu_len = len; + len = timesync_decode_apdu(&apdu[0], apdu_len, &test_date, &test_time); + ct_test(pTest, len != -1); + ct_test(pTest, bacapp_same_time(my_time, &test_time)); + ct_test(pTest, bacapp_same_date(my_date, &test_date)); + + len = timesync_utc_encode_apdu(&apdu[0], my_date, my_time); + ct_test(pTest, len != 0); + apdu_len = len; + len = + timesync_utc_decode_apdu(&apdu[0], apdu_len, &test_date, + &test_time); + ct_test(pTest, len != -1); + ct_test(pTest, bacapp_same_time(my_time, &test_time)); + ct_test(pTest, bacapp_same_date(my_date, &test_date)); +} + +void testTimeSync(Test * pTest) +{ + BACNET_DATE bdate; + BACNET_TIME btime; + + bdate.year = 2006; /* AD */ + bdate.month = 4; /* 1=Jan */ + bdate.day = 11; /* 1..31 */ + bdate.wday = 1; /* 1=Monday */ + + btime.hour = 7; + btime.min = 0; + btime.sec = 3; + btime.hundredths = 1; + + testTimeSyncData(pTest, &bdate, &btime); +} + +#ifdef TEST_TIMESYNC +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Time-Sync", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testTimeSync); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_WHOIS */ +#endif /* TEST */ diff --git a/bacnet-stack/timesync.h b/bacnet-stack/timesync.h index e69de29b..f54a3c26 100644 --- a/bacnet-stack/timesync.h +++ b/bacnet-stack/timesync.h @@ -0,0 +1,66 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef TIMESYNC_H +#define TIMESYNC_H + +#include +#include +#include "bacdef.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* encode service */ + int timesync_utc_encode_apdu(uint8_t * apdu, + BACNET_DATE * my_date, BACNET_TIME * my_time); + int timesync_encode_apdu(uint8_t * apdu, + BACNET_DATE * my_date, BACNET_TIME * my_time); +/* decode the service request only */ + int timesync_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_DATE * my_date, BACNET_TIME * my_time); + int timesync_utc_decode_apdu(uint8_t * apdu, + unsigned apdu_len, BACNET_DATE * my_date, BACNET_TIME * my_time); + int timesync_decode_apdu(uint8_t * apdu, + unsigned apdu_len, BACNET_DATE * my_date, BACNET_TIME * my_time); + +#ifdef TEST +#include "ctest.h" + void testTimeSync(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/tsm.c b/bacnet-stack/tsm.c index e69de29b..a6e1e571 100644 --- a/bacnet-stack/tsm.c +++ b/bacnet-stack/tsm.c @@ -0,0 +1,354 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + Corrections by Ferran Arumi, 2007, Barcelona, Spain + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include +#include +#include /* memmove() */ +#include "bits.h" +#include "apdu.h" +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "tsm.h" +#include "config.h" +#include "device.h" +#include "datalink.h" +#include "handlers.h" +#include "address.h" + +/* Transaction State Machine */ +/* Really only needed for segmented messages */ +/* and a little for sending confirmed messages */ +/* If we are only a server and only initiate broadcasts, */ +/* then we don't need a TSM layer. */ + +/* FIXME: not coded for segmentation */ + +/* declare space for the TSM transactions, and set it up in the init. */ +/* table rules: an Invoke ID = 0 is an unused spot in the table */ +static BACNET_TSM_DATA TSM_List[MAX_TSM_TRANSACTIONS]; + +/* returns MAX_TSM_TRANSACTIONS if not found */ +static uint8_t tsm_find_invokeID_index(uint8_t invokeID) +{ + unsigned i = 0; /* counter */ + uint8_t index = MAX_TSM_TRANSACTIONS; /* return value */ + + for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) { + if (TSM_List[i].InvokeID == invokeID) { + index = i; + break; + } + } + + return index; +} + +static uint8_t tsm_find_first_free_index(void) +{ + unsigned i = 0; /* counter */ + uint8_t index = MAX_TSM_TRANSACTIONS; /* return value */ + + for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) { + if (TSM_List[i].InvokeID == 0) { + index = i; + break; + } + } + + return index; +} + +bool tsm_transaction_available(void) +{ + bool status = false; /* return value */ + unsigned i = 0; /* counter */ + + for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) { + if (TSM_List[i].InvokeID == 0) { + /* one is available! */ + status = true; + break; + } + } + + return status; +} + +uint8_t tsm_transaction_idle_count(void) +{ + uint8_t count = 0; /* return value */ + unsigned i = 0; /* counter */ + + for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) { + if ((TSM_List[i].InvokeID == 0) && + (TSM_List[i].state == TSM_STATE_IDLE)) { + /* one is available! */ + count++; + } + } + + return count; +} + +/* gets the next free invokeID, + and reserves a spot in the table + returns 0 if none are available */ +uint8_t tsm_next_free_invokeID(void) +{ + static uint8_t current_invokeID = 1; /* incremented... */ + uint8_t index = 0; + uint8_t invokeID = 0; + bool found = false; + + /* is there even space available? */ + if (tsm_transaction_available()) { + while (!found) { + index = tsm_find_invokeID_index(current_invokeID); + if (index == MAX_TSM_TRANSACTIONS) { + /* Not found, so this invokeID is not used */ + found = true; + /* set this id into the table */ + index = tsm_find_first_free_index(); + if (index != MAX_TSM_TRANSACTIONS) { + TSM_List[index].InvokeID = invokeID = current_invokeID; + TSM_List[index].state = TSM_STATE_IDLE; + TSM_List[index].RequestTimer = Device_APDU_Timeout(); + /* update for the next call or check */ + current_invokeID++; + /* skip zero - we treat that internally as invalid or no free */ + if (current_invokeID == 0) { + current_invokeID = 1; + } + } + } else { + /* found! This invokeID is already used */ + /* try next one */ + current_invokeID++; + /* skip zero - we treat that internally as invalid or no free */ + if (current_invokeID == 0) { + current_invokeID = 1; + } + } + } + } + + return invokeID; +} + +void tsm_set_confirmed_unsegmented_transaction(uint8_t invokeID, + BACNET_ADDRESS * dest, BACNET_NPDU_DATA * ndpu_data, + uint8_t * apdu, uint16_t apdu_len) +{ + uint16_t j = 0; + uint8_t index; + + if (invokeID) { + index = tsm_find_invokeID_index(invokeID); + if (index < MAX_TSM_TRANSACTIONS) { + /* assign the transaction */ + TSM_List[index].state = TSM_STATE_AWAIT_CONFIRMATION; + TSM_List[index].RetryCount = Device_Number_Of_APDU_Retries(); + /* start the timer */ + TSM_List[index].RequestTimer = Device_APDU_Timeout(); + /* copy the data */ + for (j = 0; j < apdu_len; j++) { + TSM_List[index].apdu[j] = apdu[j]; + } + TSM_List[index].apdu_len = apdu_len; + npdu_copy_data(&TSM_List[index].npdu_data, ndpu_data); + address_copy(&TSM_List[index].dest, dest); + } + } + + return; +} + +/* used to retrieve the transaction payload */ +/* if we wanted to find out what we sent (i.e. when we get an ack) */ +bool tsm_get_transaction_pdu(uint8_t invokeID, + BACNET_ADDRESS * dest, BACNET_NPDU_DATA * ndpu_data, + uint8_t * apdu, uint16_t * apdu_len) +{ + uint16_t j = 0; + uint8_t index; + bool found = false; + + if (invokeID) { + index = tsm_find_invokeID_index(invokeID); + /* how much checking is needed? state? dest match? just invokeID? */ + if (index < MAX_TSM_TRANSACTIONS) { + /* FIXME: we may want to free the transaction so it doesn't timeout */ + /* retrieve the transaction */ + /* FIXME: bounds check the pdu_len? */ + *apdu_len = TSM_List[index].apdu_len; + for (j = 0; j < *apdu_len; j++) { + apdu[j] = TSM_List[index].apdu[j]; + } + npdu_copy_data(ndpu_data, &TSM_List[index].npdu_data); + address_copy(dest, &TSM_List[index].dest); + found = true; + } + } + + return found; +} + +/* called once a millisecond or slower */ +void tsm_timer_milliseconds(uint16_t milliseconds) +{ + unsigned i = 0; /* counter */ + int bytes_sent = 0; + + for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) { + if (TSM_List[i].state == TSM_STATE_AWAIT_CONFIRMATION) { + if (TSM_List[i].RequestTimer > milliseconds) + TSM_List[i].RequestTimer -= milliseconds; + else + TSM_List[i].RequestTimer = 0; + /* timeout. retry? */ + if (TSM_List[i].RequestTimer == 0) { + TSM_List[i].RetryCount--; + TSM_List[i].RequestTimer = Device_APDU_Timeout(); + if (TSM_List[i].RetryCount) { + bytes_sent = datalink_send_pdu(&TSM_List[i].dest, + &TSM_List[i].npdu_data, + &TSM_List[i].apdu[0], TSM_List[i].apdu_len); + } else { + /* note: the invoke id has not been cleared yet + and this indicates a failed message: + IDLE and a valid invoke id */ + TSM_List[i].state = TSM_STATE_IDLE; + } + } + } + } +} + +/* frees the invokeID and sets its state to IDLE */ +void tsm_free_invoke_id(uint8_t invokeID) +{ + uint8_t index; + + index = tsm_find_invokeID_index(invokeID); + if (index < MAX_TSM_TRANSACTIONS) { + TSM_List[index].state = TSM_STATE_IDLE; + TSM_List[index].InvokeID = 0; + } +} + +/* check if the invoke ID has been made free */ +bool tsm_invoke_id_free(uint8_t invokeID) +{ + bool status = true; + uint8_t index; + + index = tsm_find_invokeID_index(invokeID); + if (index < MAX_TSM_TRANSACTIONS) + status = false; + + return status; +} + +/* see if the invoke ID has failed get a confirmation */ +bool tsm_invoke_id_failed(uint8_t invokeID) +{ + bool status = false; + uint8_t index; + + index = tsm_find_invokeID_index(invokeID); + if (index < MAX_TSM_TRANSACTIONS) { + /* a valid invoke ID and the state is IDLE is a + message that failed to confirm */ + if (TSM_List[index].state == TSM_STATE_IDLE) + status = true; + } + + return status; +} + + +#ifdef TEST +#include +#include +#include "ctest.h" + +/* flag to send an I-Am */ +bool I_Am_Request = true; + +/* dummy function stubs */ +int datalink_send_pdu(BACNET_ADDRESS * dest, + BACNET_NPDU_DATA * npdu_data, uint8_t * pdu, unsigned pdu_len) +{ + (void) dest; + (void) npdu_data; + (void) pdu; + (void) pdu_len; + + return 0; +} + +/* dummy function stubs */ +void datalink_get_broadcast_address(BACNET_ADDRESS * dest) +{ + (void) dest; +} + +void testTSM(Test * pTest) +{ + /* FIXME: add some unit testing... */ + return; +} + +#ifdef TEST_TSM +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet TSM", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testTSM); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_TSM */ +#endif /* TEST */ diff --git a/bacnet-stack/tsm.h b/bacnet-stack/tsm.h index e69de29b..c6a4afb7 100644 --- a/bacnet-stack/tsm.h +++ b/bacnet-stack/tsm.h @@ -0,0 +1,125 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef TSM_H +#define TSM_H + +#include +#include +#include +#include "bacdef.h" +#include "npdu.h" + +/* note: TSM functionality is optional - only needed if we are + doing client requests */ +#ifndef TSM_ENABLED + #define TSM_ENABLED 0 +#endif +#if (!TSM_ENABLED) +#define tsm_free_invoke_id(x) (void)x; +#else +typedef enum { + TSM_STATE_IDLE, + TSM_STATE_AWAIT_CONFIRMATION, + TSM_STATE_AWAIT_RESPONSE, + TSM_STATE_SEGMENTED_REQUEST, + TSM_STATE_SEGMENTED_CONFIRMATION +} BACNET_TSM_STATE; + +/* 5.4.1 Variables And Parameters */ +/* The following variables are defined for each instance of */ +/* Transaction State Machine: */ +typedef struct BACnet_TSM_Data { + /* used to count APDU retries */ + uint8_t RetryCount; + /* used to count segment retries */ + /*uint8_t SegmentRetryCount; */ + /* used to control APDU retries and the acceptance of server replies */ + /*bool SentAllSegments; */ + /* stores the sequence number of the last segment received in order */ + /*uint8_t LastSequenceNumber; */ + /* stores the sequence number of the first segment of */ + /* a sequence of segments that fill a window */ + /*uint8_t InitialSequenceNumber; */ + /* stores the current window size */ + /*uint8_t ActualWindowSize; */ + /* stores the window size proposed by the segment sender */ + /*uint8_t ProposedWindowSize; */ + /* used to perform timeout on PDU segments */ + /*uint8_t SegmentTimer; */ + /* used to perform timeout on Confirmed Requests */ + /* in milliseconds */ + uint16_t RequestTimer; + /* unique id */ + uint8_t InvokeID; + /* state that the TSM is in */ + BACNET_TSM_STATE state; + /* the address we sent it to */ + BACNET_ADDRESS dest; + /* the network layer info */ + BACNET_NPDU_DATA npdu_data; + /* copy of the APDU, should we need to send it again */ + uint8_t apdu[MAX_PDU]; + unsigned apdu_len; +} BACNET_TSM_DATA; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + bool tsm_transaction_available(void); + uint8_t tsm_transaction_idle_count(void); + void tsm_timer_milliseconds(uint16_t milliseconds); +/* free the invoke ID when the reply comes back */ + void tsm_free_invoke_id(uint8_t invokeID); +/* use these in tandem */ + uint8_t tsm_next_free_invokeID(void); +/* returns the same invoke ID that was given */ + void tsm_set_confirmed_unsegmented_transaction(uint8_t invokeID, + BACNET_ADDRESS * dest, BACNET_NPDU_DATA * ndpu_data, + uint8_t * apdu, uint16_t apdu_len); +/* returns true if transaction is found */ + bool tsm_get_transaction_pdu(uint8_t invokeID, + BACNET_ADDRESS * dest, BACNET_NPDU_DATA * ndpu_data, + uint8_t * apdu, uint16_t * apdu_len); + + bool tsm_invoke_id_free(uint8_t invokeID); + bool tsm_invoke_id_failed(uint8_t invokeID); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +/* define out any functions necessary for compile */ +#endif + +#endif diff --git a/bacnet-stack/whohas.c b/bacnet-stack/whohas.c index e69de29b..928b80a5 100644 --- a/bacnet-stack/whohas.c +++ b/bacnet-stack/whohas.c @@ -0,0 +1,248 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacdcode.h" +#include "bacdef.h" +#include "whohas.h" + +/* encode service - use -1 for limit for unlimited */ + +int whohas_encode_apdu(uint8_t * apdu, BACNET_WHO_HAS_DATA * data) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu && data) { + apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST; + apdu[1] = SERVICE_UNCONFIRMED_WHO_HAS; /* service choice */ + apdu_len = 2; + /* optional limits - must be used as a pair */ + if ((data->low_limit >= 0) + && (data->low_limit <= BACNET_MAX_INSTANCE) + && (data->high_limit >= 0) + && (data->high_limit <= BACNET_MAX_INSTANCE)) { + len = + encode_context_unsigned(&apdu[apdu_len], 0, + data->low_limit); + apdu_len += len; + len = encode_context_unsigned(&apdu[apdu_len], + 1, data->high_limit); + apdu_len += len; + } + if (data->object_name) { + len = encode_context_character_string(&apdu[apdu_len], + 3, &data->object.name); + apdu_len += len; + } else { + len = encode_context_object_id(&apdu[apdu_len], + 2, + data->object.identifier.type, + data->object.identifier.instance); + apdu_len += len; + } + } + + return apdu_len; +} + +/* decode the service request only */ +int whohas_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_WHO_HAS_DATA * data) +{ + int len = 0; + uint8_t tag_number = 0; + uint32_t len_value = 0; + uint32_t decoded_value = 0; /* for decoding */ + int decoded_type = 0; /* for decoding */ + + if (apdu_len && data) { + /* optional limits - must be used as a pair */ + if (decode_is_context_tag(&apdu[len], 0)) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += decode_unsigned(&apdu[len], len_value, &decoded_value); + if (decoded_value <= BACNET_MAX_INSTANCE) + data->low_limit = decoded_value; + if (!decode_is_context_tag(&apdu[len], 1)) + return -1; + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += decode_unsigned(&apdu[len], len_value, &decoded_value); + if (decoded_value <= BACNET_MAX_INSTANCE) + data->high_limit = decoded_value; + } else { + data->low_limit = -1; + data->high_limit = -1; + } + /* object id */ + if (decode_is_context_tag(&apdu[len], 2)) { + data->object_name = false; + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += + decode_object_id(&apdu[len], &decoded_type, + &data->object.identifier.instance); + data->object.identifier.type = decoded_type; + } + /* object name */ + else if (decode_is_context_tag(&apdu[len], 3)) { + data->object_name = true; + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + len += + decode_character_string(&apdu[len], len_value, + &data->object.name); + } + /* missing required parameters */ + else + return -1; + } + + return len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +int whohas_decode_apdu(uint8_t * apdu, + unsigned apdu_len, BACNET_WHO_HAS_DATA * data) +{ + int len = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST) + return -1; + if (apdu[1] != SERVICE_UNCONFIRMED_WHO_HAS) + return -1; + /* optional limits - must be used as a pair */ + if (apdu_len > 2) { + len = whohas_decode_service_request(&apdu[2], apdu_len - 2, data); + } + + return len; +} + +void testWhoHasData(Test * pTest, BACNET_WHO_HAS_DATA * data) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + BACNET_WHO_HAS_DATA test_data; + + len = whohas_encode_apdu(&apdu[0], data); + ct_test(pTest, len != 0); + apdu_len = len; + + len = whohas_decode_apdu(&apdu[0], apdu_len, &test_data); + ct_test(pTest, len != -1); + ct_test(pTest, test_data.low_limit == data->low_limit); + ct_test(pTest, test_data.high_limit == data->high_limit); + ct_test(pTest, test_data.object_name == data->object_name); + /* Object ID */ + if (data->object_name == false) { + ct_test(pTest, test_data.object.identifier.type == + data->object.identifier.type); + ct_test(pTest, test_data.object.identifier.instance == + data->object.identifier.instance); + } + /* Object Name */ + else { + ct_test(pTest, characterstring_same(&test_data.object.name, + &data->object.name)); + } +} + +void testWhoHas(Test * pTest) +{ + BACNET_WHO_HAS_DATA data; + + data.low_limit = -1; + data.high_limit = -1; + data.object_name = false; + data.object.identifier.type = OBJECT_ANALOG_INPUT; + data.object.identifier.instance = 1; + testWhoHasData(pTest, &data); + + for (data.low_limit = 0; + data.low_limit <= BACNET_MAX_INSTANCE; + data.low_limit += (BACNET_MAX_INSTANCE / 4)) { + for (data.high_limit = 0; + data.high_limit <= BACNET_MAX_INSTANCE; + data.high_limit += (BACNET_MAX_INSTANCE / 4)) { + data.object_name = false; + for (data.object.identifier.type = OBJECT_ANALOG_INPUT; + data.object.identifier.type <= MAX_BACNET_OBJECT_TYPE; + data.object.identifier.type++) { + for (data.object.identifier.instance = 1; + data.object.identifier.instance <= BACNET_MAX_INSTANCE; + data.object.identifier.instance <<= 1) { + testWhoHasData(pTest, &data); + } + } + data.object_name = true; + characterstring_init_ansi(&data.object.name, "patricia"); + testWhoHasData(pTest, &data); + } + } +} + +#ifdef TEST_WHOHAS +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Who-Has", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testWhoHas); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_WHOIS */ +#endif /* TEST */ diff --git a/bacnet-stack/whohas.h b/bacnet-stack/whohas.h index e69de29b..c51e2312 100644 --- a/bacnet-stack/whohas.h +++ b/bacnet-stack/whohas.h @@ -0,0 +1,72 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2006 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef WHOHAS_H +#define WHOHAS_H + +#include +#include +#include "bacstr.h" + +typedef struct BACnet_Who_Has_Data { + int32_t low_limit; /* deviceInstanceRange */ + int32_t high_limit; + bool object_name; /* true if a string */ + union { + BACNET_OBJECT_ID identifier; + BACNET_CHARACTER_STRING name; + } object; +} BACNET_WHO_HAS_DATA; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* encode service - use -1 for limit if you want unlimited */ + int whohas_encode_apdu(uint8_t * apdu, BACNET_WHO_HAS_DATA * data); + + int whohas_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_WHO_HAS_DATA * data); + + int whohas_decode_apdu(uint8_t * apdu, + unsigned apdu_len, BACNET_WHO_HAS_DATA * data); + +#ifdef TEST +#include "ctest.h" + void testWhoHas(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/whois.c b/bacnet-stack/whois.c index e69de29b..4e7b78d7 100644 --- a/bacnet-stack/whois.c +++ b/bacnet-stack/whois.c @@ -0,0 +1,182 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacdcode.h" +#include "bacdef.h" + +/* encode I-Am service - use -1 for limit if you want unlimited */ +int whois_encode_apdu(uint8_t * apdu, + int32_t low_limit, int32_t high_limit) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST; + apdu[1] = SERVICE_UNCONFIRMED_WHO_IS; /* service choice */ + apdu_len = 2; + /* optional limits - must be used as a pair */ + if ((low_limit >= 0) && (low_limit <= BACNET_MAX_INSTANCE) && + (high_limit >= 0) && (high_limit <= BACNET_MAX_INSTANCE)) { + len = encode_context_unsigned(&apdu[apdu_len], 0, low_limit); + apdu_len += len; + len = encode_context_unsigned(&apdu[apdu_len], 1, high_limit); + apdu_len += len; + } + } + + return apdu_len; +} + +/* decode the service request only */ +int whois_decode_service_request(uint8_t * apdu, + unsigned apdu_len, int32_t * pLow_limit, int32_t * pHigh_limit) +{ + int len = 0; + uint8_t tag_number = 0; + uint32_t len_value = 0; + uint32_t decoded_value = 0; + + /* optional limits - must be used as a pair */ + if (apdu_len) { + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + if (tag_number != 0) + return -1; + len += decode_unsigned(&apdu[len], len_value, &decoded_value); + if (decoded_value <= BACNET_MAX_INSTANCE) { + if (pLow_limit) + *pLow_limit = decoded_value; + } + len += + decode_tag_number_and_value(&apdu[len], &tag_number, + &len_value); + if (tag_number != 1) + return -1; + len += decode_unsigned(&apdu[len], len_value, &decoded_value); + if (decoded_value <= BACNET_MAX_INSTANCE) { + if (pHigh_limit) + *pHigh_limit = decoded_value; + } + } + + return len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +int whois_decode_apdu(uint8_t * apdu, + unsigned apdu_len, int32_t * pLow_limit, int32_t * pHigh_limit) +{ + int len = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST) + return -1; + if (apdu[1] != SERVICE_UNCONFIRMED_WHO_IS) + return -1; + /* optional limits - must be used as a pair */ + if (apdu_len > 2) { + len = whois_decode_service_request(&apdu[2], + apdu_len - 2, pLow_limit, pHigh_limit); + } + + return len; +} + +void testWhoIs(Test * pTest) +{ + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + int32_t low_limit = -1; + int32_t high_limit = -1; + int32_t test_low_limit = -1; + int32_t test_high_limit = -1; + + len = whois_encode_apdu(&apdu[0], low_limit, high_limit); + ct_test(pTest, len != 0); + apdu_len = len; + + len = whois_decode_apdu(&apdu[0], + apdu_len, &test_low_limit, &test_high_limit); + ct_test(pTest, len != -1); + ct_test(pTest, test_low_limit == low_limit); + ct_test(pTest, test_high_limit == high_limit); + + for (low_limit = 0; + low_limit <= BACNET_MAX_INSTANCE; + low_limit += (BACNET_MAX_INSTANCE / 4)) { + for (high_limit = 0; + high_limit <= BACNET_MAX_INSTANCE; + high_limit += (BACNET_MAX_INSTANCE / 4)) { + len = whois_encode_apdu(&apdu[0], low_limit, high_limit); + apdu_len = len; + ct_test(pTest, len != 0); + len = whois_decode_apdu(&apdu[0], + apdu_len, &test_low_limit, &test_high_limit); + ct_test(pTest, len != -1); + ct_test(pTest, test_low_limit == low_limit); + ct_test(pTest, test_high_limit == high_limit); + } + } +} + +#ifdef TEST_WHOIS +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Who-Is", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testWhoIs); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_WHOIS */ +#endif /* TEST */ diff --git a/bacnet-stack/whois.h b/bacnet-stack/whois.h index e69de29b..aa2cb853 100644 --- a/bacnet-stack/whois.h +++ b/bacnet-stack/whois.h @@ -0,0 +1,61 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef WHOIS_H +#define WHOIS_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* encode service - use -1 for limit if you want unlimited */ + int whois_encode_apdu(uint8_t * apdu, + int32_t low_limit, int32_t high_limit); + + int whois_decode_service_request(uint8_t * apdu, + unsigned apdu_len, int32_t * pLow_limit, int32_t * pHigh_limit); + +#ifdef TEST + int whois_decode_apdu(uint8_t * apdu, + unsigned apdu_len, int32_t * pLow_limit, int32_t * pHigh_limit); + + void testWhoIs(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/bacnet-stack/wp.c b/bacnet-stack/wp.c index e69de29b..7499f757 100644 --- a/bacnet-stack/wp.c +++ b/bacnet-stack/wp.c @@ -0,0 +1,372 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#include +#include "bacenum.h" +#include "bacdcode.h" +#include "bacdef.h" +#include "device.h" +#include "wp.h" + +/* encode service */ +int wp_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_WRITE_PROPERTY_DATA * data) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + int len = 0; /* total length of the apdu, return value */ + + if (apdu) { + apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST; + apdu[1] = + encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); + apdu[2] = invoke_id; + apdu[3] = SERVICE_CONFIRMED_WRITE_PROPERTY; /* service choice */ + apdu_len = 4; + len = encode_context_object_id(&apdu[apdu_len], 0, + data->object_type, data->object_instance); + apdu_len += len; + len = encode_context_enumerated(&apdu[apdu_len], 1, + data->object_property); + apdu_len += len; + /* optional array index; ALL is -1 which is assumed when missing */ + if (data->array_index != BACNET_ARRAY_ALL) { + len = encode_context_unsigned(&apdu[apdu_len], 2, + data->array_index); + apdu_len += len; + } + /* propertyValue */ + len = encode_opening_tag(&apdu[apdu_len], 3); + apdu_len += len; + for (len = 0; len < data->application_data_len; len++) { + apdu[apdu_len + len] = data->application_data[len]; + } + apdu_len += data->application_data_len; + len = encode_closing_tag(&apdu[apdu_len], 3); + apdu_len += len; + /* optional priority - 0 if not set, 1..16 if set */ + if (data->priority != BACNET_NO_PRIORITY) { + len = encode_context_unsigned(&apdu[apdu_len], 4, + data->priority); + apdu_len += len; + } + } + + return apdu_len; +} + +/* decode the service request only */ +/* FIXME: there could be various error messages returned + using unique values less than zero */ +int wp_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_WRITE_PROPERTY_DATA * data) +{ + int len = 0; + int tag_len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + int type = 0; /* for decoding */ + int property = 0; /* for decoding */ + uint32_t unsigned_value = 0; + int i = 0; /* loop counter */ + + /* check for value pointers */ + if (apdu_len && data) { + /* Tag 0: Object ID */ + if (!decode_is_context_tag(&apdu[len++], 0)) + return -1; + len += decode_object_id(&apdu[len], &type, &data->object_instance); + data->object_type = type; + /* Tag 1: Property ID */ + len += decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + if (tag_number != 1) + return -1; + len += decode_enumerated(&apdu[len], len_value_type, &property); + data->object_property = property; + /* Tag 2: Optional Array Index */ + /* note: decode without incrementing len so we can check for opening tag */ + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + if (tag_number == 2) { + len += tag_len; + len += decode_unsigned(&apdu[len], len_value_type, + &unsigned_value); + data->array_index = unsigned_value; + } else + data->array_index = BACNET_ARRAY_ALL; + /* Tag 3: opening context tag */ + if (!decode_is_opening_tag_number(&apdu[len], 3)) + return -1; + /* determine the length of the data blob */ + data->application_data_len = bacapp_data_len(&apdu[len], + apdu_len - len, property); + /* a tag number of 3 is not extended so only one octet */ + len++; + /* copy the data from the APDU */ + for (i = 0; i < data->application_data_len; i++) { + data->application_data[i] = apdu[len + i]; + } + /* add on the data length */ + len += data->application_data_len; + if (!decode_is_closing_tag_number(&apdu[len], 3)) + return -2; + /* a tag number of 3 is not extended so only one octet */ + len++; + /* Tag 4: optional Priority - assumed MAX if not explicitly set */ + data->priority = BACNET_MAX_PRIORITY; + if ((unsigned) len < apdu_len) { + tag_len = decode_tag_number_and_value(&apdu[len], + &tag_number, &len_value_type); + if (tag_number == 4) { + len += tag_len; + len = + decode_unsigned(&apdu[len], len_value_type, + &unsigned_value); + if ((unsigned_value >= BACNET_MIN_PRIORITY) + && (unsigned_value <= BACNET_MAX_PRIORITY)) { + data->priority = (uint8_t) unsigned_value; + } else + return -5; + } + } + } + + return len; +} + +#ifdef TEST +#include +#include +#include "ctest.h" + +int wp_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, BACNET_WRITE_PROPERTY_DATA * data) +{ + int len = 0; + unsigned offset = 0; + + if (!apdu) + return -1; + /* optional checking - most likely was already done prior to this call */ + if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) + return -1; + /* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */ + *invoke_id = apdu[2]; /* invoke id - filled in by net layer */ + if (apdu[3] != SERVICE_CONFIRMED_WRITE_PROPERTY) + return -1; + offset = 4; + + if (apdu_len > offset) { + len = wp_decode_service_request(&apdu[offset], + apdu_len - offset, data); + } + + return len; +} + +void testWritePropertyTag(Test * pTest, + BACNET_APPLICATION_DATA_VALUE * value) +{ + BACNET_WRITE_PROPERTY_DATA data = { 0 }; + BACNET_WRITE_PROPERTY_DATA test_data = { 0 }; + BACNET_APPLICATION_DATA_VALUE test_value; + uint8_t apdu[480] = { 0 }; + int len = 0; + int apdu_len = 0; + uint8_t invoke_id = 128; + uint8_t test_invoke_id = 0; + + data.application_data_len = + bacapp_encode_application_data(&data.application_data[0], value); + len = wp_encode_apdu(&apdu[0], invoke_id, &data); + ct_test(pTest, len != 0); + /* decode the data */ + apdu_len = len; + len = wp_decode_apdu(&apdu[0], apdu_len, &test_invoke_id, &test_data); + ct_test(pTest, len != -1); + ct_test(pTest, test_data.object_type == data.object_type); + ct_test(pTest, test_data.object_instance == data.object_instance); + ct_test(pTest, test_data.object_property == data.object_property); + ct_test(pTest, test_data.array_index == data.array_index); + /* decode the application value of the request */ + len = bacapp_decode_application_data(test_data.application_data, + test_data.application_data_len, &test_value); + ct_test(pTest, test_value.tag == value->tag); + switch (test_value.tag) { + case BACNET_APPLICATION_TAG_NULL: + break; + case BACNET_APPLICATION_TAG_BOOLEAN: + ct_test(pTest, test_value.type.Boolean == value->type.Boolean); + break; + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + ct_test(pTest, test_value.type.Unsigned_Int == + value->type.Unsigned_Int); + break; + case BACNET_APPLICATION_TAG_SIGNED_INT: + ct_test(pTest, test_value.type.Signed_Int == + value->type.Signed_Int); + break; + case BACNET_APPLICATION_TAG_REAL: + ct_test(pTest, test_value.type.Real == value->type.Real); + break; + case BACNET_APPLICATION_TAG_ENUMERATED: + ct_test(pTest, test_value.type.Enumerated == + value->type.Enumerated); + break; + case BACNET_APPLICATION_TAG_DATE: + ct_test(pTest, test_value.type.Date.year == value->type.Date.year); + ct_test(pTest, test_value.type.Date.month == + value->type.Date.month); + ct_test(pTest, test_value.type.Date.day == value->type.Date.day); + ct_test(pTest, test_value.type.Date.wday == value->type.Date.wday); + break; + case BACNET_APPLICATION_TAG_TIME: + ct_test(pTest, test_value.type.Time.hour == value->type.Time.hour); + ct_test(pTest, test_value.type.Time.min == value->type.Time.min); + ct_test(pTest, test_value.type.Time.sec == value->type.Time.sec); + ct_test(pTest, test_value.type.Time.hundredths == + value->type.Time.hundredths); + break; + case BACNET_APPLICATION_TAG_OBJECT_ID: + ct_test(pTest, test_value.type.Object_Id.type == + value->type.Object_Id.type); + ct_test(pTest, test_value.type.Object_Id.instance == + value->type.Object_Id.instance); + break; + default: + break; + } +} + +void testWriteProperty(Test * pTest) +{ + BACNET_APPLICATION_DATA_VALUE value; + + value.tag = BACNET_APPLICATION_TAG_NULL; + testWritePropertyTag(pTest, &value); + + value.tag = BACNET_APPLICATION_TAG_BOOLEAN; + value.type.Boolean = true; + testWritePropertyTag(pTest, &value); + value.type.Boolean = false; + testWritePropertyTag(pTest, &value); + + value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; + value.type.Unsigned_Int = 0; + testWritePropertyTag(pTest, &value); + value.type.Unsigned_Int = 0xFFFF; + testWritePropertyTag(pTest, &value); + value.type.Unsigned_Int = 0xFFFFFFFF; + testWritePropertyTag(pTest, &value); + + value.tag = BACNET_APPLICATION_TAG_SIGNED_INT; + value.type.Signed_Int = 0; + testWritePropertyTag(pTest, &value); + value.type.Signed_Int = -1; + testWritePropertyTag(pTest, &value); + value.type.Signed_Int = 32768; + testWritePropertyTag(pTest, &value); + value.type.Signed_Int = -32768; + testWritePropertyTag(pTest, &value); + + value.tag = BACNET_APPLICATION_TAG_REAL; + value.type.Real = 0.0; + testWritePropertyTag(pTest, &value); + value.type.Real = -1.0; + testWritePropertyTag(pTest, &value); + value.type.Real = 1.0; + testWritePropertyTag(pTest, &value); + value.type.Real = 3.14159; + testWritePropertyTag(pTest, &value); + value.type.Real = -3.14159; + testWritePropertyTag(pTest, &value); + + value.tag = BACNET_APPLICATION_TAG_ENUMERATED; + value.type.Enumerated = 0; + testWritePropertyTag(pTest, &value); + value.type.Enumerated = 0xFFFF; + testWritePropertyTag(pTest, &value); + value.type.Enumerated = 0xFFFFFFFF; + testWritePropertyTag(pTest, &value); + + value.tag = BACNET_APPLICATION_TAG_DATE; + value.type.Date.year = 2005; + value.type.Date.month = 5; + value.type.Date.day = 22; + value.type.Date.wday = 1; + testWritePropertyTag(pTest, &value); + + value.tag = BACNET_APPLICATION_TAG_TIME; + value.type.Time.hour = 23; + value.type.Time.min = 59; + value.type.Time.sec = 59; + value.type.Time.hundredths = 12; + testWritePropertyTag(pTest, &value); + + value.tag = BACNET_APPLICATION_TAG_OBJECT_ID; + value.type.Object_Id.type = OBJECT_ANALOG_INPUT; + value.type.Object_Id.instance = 0; + testWritePropertyTag(pTest, &value); + value.type.Object_Id.type = OBJECT_LIFE_SAFETY_ZONE; + value.type.Object_Id.instance = BACNET_MAX_INSTANCE; + testWritePropertyTag(pTest, &value); + + return; +} + +#ifdef TEST_WRITE_PROPERTY +uint16_t Device_Max_APDU_Length_Accepted(void) +{ + return MAX_APDU; +} + +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet WriteProperty", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testWriteProperty); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_WRITE_PROPERTY */ +#endif /* TEST */ diff --git a/bacnet-stack/wp.h b/bacnet-stack/wp.h index e69de29b..57614039 100644 --- a/bacnet-stack/wp.h +++ b/bacnet-stack/wp.h @@ -0,0 +1,81 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2005 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ +#ifndef WRITEPROPERTY_H +#define WRITEPROPERTY_H + +#include +#include +#include "bacdcode.h" +#include "bacapp.h" + +/* write property can have application tagged data, or context tagged data, + or even complex data types (i.e. opening and closing tag around data). + It could also have more than one value or element. */ + +typedef struct BACnet_Write_Property_Data { + BACNET_OBJECT_TYPE object_type; + uint32_t object_instance; + BACNET_PROPERTY_ID object_property; + int32_t array_index; /* use BACNET_ARRAY_ALL when not setting */ + uint8_t application_data[MAX_APDU]; + int application_data_len; + uint8_t priority; /* use BACNET_NO_PRIORITY if no priority */ +} BACNET_WRITE_PROPERTY_DATA; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* encode service */ + int wp_encode_apdu(uint8_t * apdu, + uint8_t invoke_id, BACNET_WRITE_PROPERTY_DATA * data); + +/* decode the service request only */ + int wp_decode_service_request(uint8_t * apdu, + unsigned apdu_len, BACNET_WRITE_PROPERTY_DATA * data); + +#ifdef TEST +#include "ctest.h" + int wp_decode_apdu(uint8_t * apdu, + unsigned apdu_len, + uint8_t * invoke_id, BACNET_WRITE_PROPERTY_DATA * data); + + void test_ReadProperty(Test * pTest); + void test_ReadPropertyAck(Test * pTest); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif