Edouard@2020: /************************************************************************** Edouard@2020: * Edouard@2020: * Copyright (C) 2006 Steve Karg Edouard@2020: * Copyright (C) 2017 Mario de Sousa Edouard@2020: * Edouard@2020: * Permission is hereby granted, free of charge, to any person obtaining Edouard@2020: * a copy of this software and associated documentation files (the Edouard@2020: * "Software"), to deal in the Software without restriction, including Edouard@2020: * without limitation the rights to use, copy, modify, merge, publish, Edouard@2020: * distribute, sublicense, and/or sell copies of the Software, and to Edouard@2020: * permit persons to whom the Software is furnished to do so, subject to Edouard@2020: * the following conditions: Edouard@2020: * Edouard@2020: * The above copyright notice and this permission notice shall be included Edouard@2020: * in all copies or substantial portions of the Software. Edouard@2020: * Edouard@2020: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, Edouard@2020: * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF Edouard@2020: * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. Edouard@2020: * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY Edouard@2020: * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, Edouard@2020: * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE Edouard@2020: * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Edouard@2020: * Edouard@2020: *********************************************************************/ Edouard@2020: #include Edouard@2020: #include Edouard@2020: #include Edouard@2020: #include Edouard@2020: #include Edouard@2020: #include Edouard@2020: #include // uint32_t, ..., PRIu32, ... Edouard@2020: Edouard@2020: #include "config_bacnet_for_beremiz_%(locstr)s.h" /* the custom configuration for beremiz pluginh */ Edouard@2020: #include "server_%(locstr)s.h" Edouard@2020: #include "address.h" Edouard@2020: #include "bacdef.h" Edouard@2020: #include "handlers.h" Edouard@2020: #include "client.h" Edouard@2020: #include "dlenv.h" Edouard@2020: #include "bacdcode.h" Edouard@2020: #include "npdu.h" Edouard@2020: #include "apdu.h" Edouard@2020: #include "iam.h" Edouard@2020: #include "tsm.h" Edouard@2020: #include "datalink.h" Edouard@2020: #include "dcc.h" Edouard@2020: #include "getevent.h" Edouard@2020: #include "net.h" Edouard@2020: #include "txbuf.h" Edouard@2020: #include "version.h" Edouard@2268: #include "timesync.h" Edouard@2020: Edouard@2020: msousa@2649: msousa@2649: msousa@2649: msousa@2649: Edouard@2020: /* A utility function used by most (all?) implementations of BACnet Objects */ Edouard@2020: /* Adds to Prop_List all entries in Prop_List_XX that are not Edouard@2020: * PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_PROPERTY_LIST Edouard@2020: * and returns the number of elements that were added Edouard@2020: */ Edouard@2020: int BACnet_Init_Properties_List( Edouard@2020: int *Prop_List, Edouard@2020: const int *Prop_List_XX) { Edouard@2020: Edouard@2020: unsigned int i = 0; Edouard@2020: unsigned int j = 0; Edouard@2020: Edouard@2020: for (j = 0; Prop_List_XX[j] >= 0; j++) Edouard@2020: // Add any propety, except for the following 4 which should not be included Edouard@2020: // in the Property_List property array. Edouard@2020: // (see ASHRAE 135-2016, for example section 12.4.34) Edouard@2020: if ((Prop_List_XX[j] != PROP_OBJECT_IDENTIFIER) && Edouard@2020: (Prop_List_XX[j] != PROP_OBJECT_NAME) && Edouard@2020: (Prop_List_XX[j] != PROP_OBJECT_TYPE) && Edouard@2020: (Prop_List_XX[j] != PROP_PROPERTY_LIST)) { Edouard@2020: Prop_List[i] = Prop_List_XX[j]; Edouard@2020: i++; Edouard@2020: } Edouard@2020: Prop_List[i] = -1; // marks the end of the list! Edouard@2020: return i; Edouard@2020: } Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: int BACnet_encode_character_string(uint8_t *apdu, const char *str) { Edouard@2020: BACNET_CHARACTER_STRING char_string; Edouard@2020: characterstring_init_ansi(&char_string, str); Edouard@2020: /* FIXME: this might go beyond MAX_APDU length! */ Edouard@2020: return encode_application_character_string(apdu, &char_string); Edouard@2020: } Edouard@2020: Edouard@2020: /* macro that always returns false. Edouard@2020: * To be used as the 'test_null' parameter to the BACnet_encode_array macro Edouard@2020: * in situations where we should never encode_null() values. Edouard@2020: */ Edouard@2020: #define retfalse(x) (false) Edouard@2020: Edouard@2020: #define BACnet_encode_array(array, array_len, test_null, encode_function) \ Edouard@2020: { \ Edouard@2020: uint8_t *apdu = NULL; \ Edouard@2020: apdu = rpdata->application_data; \ Edouard@2020: \ Edouard@2020: switch (rpdata->array_index) { \ Edouard@2020: case 0: /* Array element zero is the number of elements in the array */ \ Edouard@2020: apdu_len = encode_application_unsigned(&apdu[0], array_len); \ Edouard@2020: break; \ Edouard@2020: case BACNET_ARRAY_ALL: { \ Edouard@2020: /* if no index was specified, then try to encode the entire list */ \ Edouard@2020: unsigned i = 0; \ Edouard@2020: apdu_len = 0; \ Edouard@2020: for (i = 0; i < array_len; i++) { \ Edouard@2020: /* FIXME: this might go beyond MAX_APDU length! */ \ Edouard@2020: if (!test_null(array[i])) \ Edouard@2020: apdu_len += encode_function (&apdu[apdu_len], array[i]); \ Edouard@2020: else apdu_len += encode_application_null(&apdu[apdu_len]); \ Edouard@2020: /* return error if it does not fit in the APDU */ \ Edouard@2020: if (apdu_len >= MAX_APDU) { \ Edouard@2020: rpdata->error_class = ERROR_CLASS_SERVICES; \ Edouard@2020: rpdata->error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; \ Edouard@2020: apdu_len = BACNET_STATUS_ERROR; \ Edouard@2020: break; /* for(;;) */ \ Edouard@2020: } \ Edouard@2020: } \ Edouard@2020: break; \ Edouard@2020: } \ Edouard@2020: default: \ Edouard@2020: if (rpdata->array_index <= array_len) { \ Edouard@2020: if (!test_null(array[rpdata->array_index - 1])) \ Edouard@2020: apdu_len += encode_function(&apdu[0], array[rpdata->array_index - 1]); \ Edouard@2020: else apdu_len += encode_application_null(&apdu[0]); \ Edouard@2020: } else { \ Edouard@2020: rpdata->error_class = ERROR_CLASS_PROPERTY; \ Edouard@2020: rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; \ Edouard@2020: apdu_len = BACNET_STATUS_ERROR; \ Edouard@2020: } \ Edouard@2020: break; \ Edouard@2020: } /* switch() */ \ Edouard@2020: } Edouard@2020: Edouard@2020: Edouard@2020: /* include the device object */ Edouard@2020: #include "device_%(locstr)s.c" Edouard@2020: #include "ai_%(locstr)s.c" Edouard@2020: #include "ao_%(locstr)s.c" Edouard@2020: #include "av_%(locstr)s.c" Edouard@2020: #include "bi_%(locstr)s.c" Edouard@2020: #include "bo_%(locstr)s.c" Edouard@2020: #include "bv_%(locstr)s.c" Edouard@2020: #include "msi_%(locstr)s.c" Edouard@2020: #include "mso_%(locstr)s.c" Edouard@2020: #include "msv_%(locstr)s.c" Edouard@2020: Edouard@2020: Edouard@2020: /** Buffer used for receiving */ Edouard@2020: static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: /*******************************************************/ Edouard@2020: /* BACnet Service Handlers taylored to Beremiz plugin */ Edouard@2020: /*******************************************************/ Edouard@2020: Edouard@2020: static void BACNET_show_date_time( Edouard@2020: BACNET_DATE * bdate, Edouard@2020: BACNET_TIME * btime) Edouard@2020: { Edouard@2020: /* show the date received */ Edouard@2020: fprintf(stderr, "%%u", (unsigned) bdate->year); Edouard@2020: fprintf(stderr, "/%%u", (unsigned) bdate->month); Edouard@2020: fprintf(stderr, "/%%u", (unsigned) bdate->day); Edouard@2020: /* show the time received */ Edouard@2020: fprintf(stderr, " %%02u", (unsigned) btime->hour); Edouard@2020: fprintf(stderr, ":%%02u", (unsigned) btime->min); Edouard@2020: fprintf(stderr, ":%%02u", (unsigned) btime->sec); Edouard@2020: fprintf(stderr, ".%%02u", (unsigned) btime->hundredths); Edouard@2020: fprintf(stderr, "\r\n"); Edouard@2020: } Edouard@2020: Edouard@2020: static time_t __timegm(struct tm *new_time) { Edouard@2020: time_t sec = mktime(new_time); /* assume new_time is in local time */ Edouard@2020: Edouard@2020: /* sec will be an aproximation of the correct value. Edouard@2020: * We must now fix this with the current difference Edouard@2020: * between UTC and localtime. Edouard@2020: * Edouard@2020: * WARNING: The following algorithm to determine the current Edouard@2020: * difference between local time and UTC will not Edouard@2020: * work if each value (lcl and utc) falls on a different Edouard@2020: * side of a change to/from DST. Edouard@2020: * For example, assume a change to DST is made at 12h00 Edouard@2020: * of day X. The following algorithm does not work if: Edouard@2020: * - lcl falls before 12h00 of day X Edouard@2020: * - utc falls after 12h00 of day X Edouard@2020: */ Edouard@2020: struct tm lcl = *localtime(&sec); // lcl will be == new_time Edouard@2020: struct tm utc = *gmtime (&sec); Edouard@2020: Edouard@2020: if (lcl.tm_isdst == 1) utc.tm_isdst = 1; Edouard@2020: Edouard@2020: time_t sec_lcl = mktime(&lcl); Edouard@2020: time_t sec_utc = mktime(&utc); Edouard@2020: Edouard@2020: /* difference in seconds between localtime and utc */ Edouard@2020: time_t sec_dif = sec_lcl - sec_utc; Edouard@2020: return sec + sec_dif; Edouard@2020: } Edouard@2020: Edouard@2020: Edouard@2020: static void BACNET_set_date_time( Edouard@2020: BACNET_DATE * bdate, Edouard@2020: BACNET_TIME * btime, Edouard@2020: int utc /* set to > 0 if date & time in UTC */) Edouard@2020: { Edouard@2020: struct tm brokendown_time; Edouard@2020: time_t seconds = 0; Edouard@2020: struct timespec ts; Edouard@2020: Edouard@2020: brokendown_time.tm_sec = btime->sec; /* seconds 0..60 */ Edouard@2020: brokendown_time.tm_min = btime->min; /* minutes 0..59 */ Edouard@2020: brokendown_time.tm_hour = btime->hour; /* hours 0..23 */ Edouard@2020: brokendown_time.tm_mday = bdate->day; /* day of the month 1..31 */ Edouard@2020: brokendown_time.tm_mon = bdate->month-1; /* month 0..11 */ Edouard@2020: brokendown_time.tm_year = bdate->year-1900; /* years since 1900 */ Edouard@2020: // brokendown_time.tm_wday = ; /* day of the week */ Edouard@2020: // brokendown_time.tm_yday = ; /* day in the year */ Edouard@2020: brokendown_time.tm_isdst = -1; /* daylight saving time (-1 => unknown) */ Edouard@2020: Edouard@2020: // Tranform time into format -> 'seconds since epoch' Edouard@2020: /* WARNING: timegm() is a non standard GNU extension. Edouard@2020: * If you do not have it on your build system then consider Edouard@2020: * finding the source code for timegm() (it is LGPL) from GNU Edouard@2020: * C library and copying it here Edouard@2020: * (e.g. https://code.woboq.org/userspace/glibc/time/timegm.c.html) Edouard@2020: * Another alternative is to use the fundion __timegm() above, Edouard@2020: * which will mostly work but may have errors when the time being Edouard@2020: * converted is close to the time in the year when changing Edouard@2020: * to/from DST (daylight saving time) Edouard@2020: */ Edouard@2020: if (utc > 0) seconds = timegm(&brokendown_time); Edouard@2020: else seconds = mktime(&brokendown_time); Edouard@2020: Edouard@2020: ts.tv_sec = seconds; Edouard@2020: ts.tv_nsec = btime->hundredths*10*1000*1000; Edouard@2020: Edouard@2020: // fprintf(stderr, "clock_settime() s=%%ul, ns=%%u\n", ts.tv_sec, ts.tv_nsec); Edouard@2020: clock_settime(CLOCK_REALTIME, &ts); Edouard@2020: // clock_gettime(CLOCK_REALTIME, &ts); Edouard@2020: // fprintf(stderr, "clock_gettime() s=%%ul, ns=%%u\n", ts.tv_sec, ts.tv_nsec); Edouard@2020: } Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: void BACnet_handler_timesync( Edouard@2020: uint8_t * service_request, Edouard@2020: uint16_t service_len, Edouard@2020: BACNET_ADDRESS * src) Edouard@2020: { Edouard@2020: int len = 0; Edouard@2020: BACNET_DATE bdate; Edouard@2020: BACNET_TIME btime; Edouard@2020: Edouard@2020: (void) src; Edouard@2020: (void) service_len; Edouard@2020: len = Edouard@2020: timesync_decode_service_request(service_request, service_len, &bdate, &btime); Edouard@2020: if (len > 0) { Edouard@2020: fprintf(stderr, "BACnet plugin: Received TimeSyncronization Request -> "); Edouard@2020: BACNET_show_date_time(&bdate, &btime); Edouard@2020: /* set the time */ Edouard@2020: BACNET_set_date_time(&bdate, &btime, 0 /* time in local time */); Edouard@2020: } Edouard@2020: Edouard@2020: return; Edouard@2020: } Edouard@2020: Edouard@2020: void BACnet_handler_timesync_utc( Edouard@2020: uint8_t * service_request, Edouard@2020: uint16_t service_len, Edouard@2020: BACNET_ADDRESS * src) Edouard@2020: { Edouard@2020: int len = 0; Edouard@2020: BACNET_DATE bdate; Edouard@2020: BACNET_TIME btime; Edouard@2020: Edouard@2020: (void) src; Edouard@2020: (void) service_len; Edouard@2020: len = Edouard@2020: timesync_decode_service_request(service_request, service_len, &bdate, &btime); Edouard@2020: if (len > 0) { Edouard@2020: fprintf(stderr, "BACnet plugin: Received TimeSyncronizationUTC Request -> "); Edouard@2020: BACNET_show_date_time(&bdate, &btime); Edouard@2020: /* set the time */ Edouard@2020: BACNET_set_date_time(&bdate, &btime, 1 /* time in UTC */); Edouard@2020: } Edouard@2020: Edouard@2020: return; Edouard@2020: } Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: /**********************************************/ Edouard@2020: /** Initialize the handlers we will utilize. **/ Edouard@2020: /**********************************************/ Edouard@2020: /* Edouard@2020: * TLDR: The functions that will create the __Resp.__ messages. Edouard@2020: * Edouard@2020: * The service handlers are the functions that will respond to BACnet requests this device receives. Edouard@2020: * In essence, the service handlers will create and send the Resp. (Response) messages Edouard@2020: * of the Req. -> Ind. -> Resp. -> Conf. service sequence defined in OSI Edouard@2020: * (Request, Indication, Response, Confirmation) Edouard@2020: * Edouard@2020: */ Edouard@2020: static int Init_Service_Handlers( Edouard@2020: void) Edouard@2020: { Edouard@2020: /* set the handler for all the services we don't implement */ Edouard@2020: /* It is required to send the proper reject message... */ Edouard@2020: apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service); Edouard@2020: Edouard@2020: /* Set the handlers for any unconfirmed services that we support. */ Edouard@2020: apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, // DM-DDB-B - Dynamic Device Binding B (Resp.) Edouard@2020: handler_who_is); // (see ASHRAE 135-2016, section K5.1 and K5.2) Edouard@2020: // apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, // DM-DDB-A - Dynamic Device Binding A (Resp.) Edouard@2020: // handler_i_am_bind); // Responding to I_AM requests is for clients (A)! Edouard@2020: // // (see ASHRAE 135-2016, section K5.1 and K5.2) Edouard@2020: apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, // DM-DOB-B - Dynamic Object Binding B (Resp.) Edouard@2020: handler_who_has); // (see ASHRAE 135-2016, section K5.3 and K5.4) Edouard@2020: // apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_HAVE, // DM-DOB-A - Dynamic Object Binding A (Resp.) Edouard@2020: // handler_i_have); // Responding to I_HAVE requests is for clients (A)! Edouard@2020: // // (see ASHRAE 135-2016, section K5.3 and K5.4) Edouard@2020: apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION, // DM-UTC-B -UTCTimeSynchronization-B (Resp.) Edouard@2020: BACnet_handler_timesync_utc); // (see ASHRAE 135-2016, section K5.14) Edouard@2020: apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION, // DM-TS-B - TimeSynchronization-B (Resp.) Edouard@2020: BACnet_handler_timesync); // (see ASHRAE 135-2016, section K5.12) Edouard@2020: Edouard@2020: /* Set the handlers for any confirmed services that we support. */ Edouard@2020: apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY, // DS-RP-B - Read Property B (Resp.) Edouard@2020: handler_read_property); // (see ASHRAE 135-2016, section K1.2) Edouard@2020: // apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROP_MULTIPLE, // DS-RPM-B -Read Property Multiple-B (Resp.) Edouard@2020: // handler_read_property_multiple); // (see ASHRAE 135-2016, section K1.4) Edouard@2020: apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, // DS-WP-B - Write Property B (Resp.) Edouard@2020: handler_write_property); // (see ASHRAE 135-2016, section K1.8) Edouard@2020: // apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE,// DS-WPM-B -Write Property Multiple B (Resp.) Edouard@2020: // handler_write_property_multiple); // (see ASHRAE 135-2016, section K1.10) Edouard@2020: // apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_RANGE, Edouard@2020: // handler_read_range); Edouard@2020: // apdu_set_confirmed_handler(SERVICE_CONFIRMED_REINITIALIZE_DEVICE, Edouard@2020: // handler_reinitialize_device); Edouard@2020: Edouard@2020: // apdu_set_confirmed_handler(SERVICE_CONFIRMED_SUBSCRIBE_COV, Edouard@2020: // handler_cov_subscribe); Edouard@2020: // apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_COV_NOTIFICATION, Edouard@2020: // handler_ucov_notification); Edouard@2020: /* handle communication so we can shutup when asked */ Edouard@2020: apdu_set_confirmed_handler(SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, // DM-DCC-B - Device Communication Control B Edouard@2020: handler_device_communication_control); // (see ASHRAE 135-2016, section K5.6) Edouard@2020: // /* handle the data coming back from private requests */ Edouard@2020: // apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_PRIVATE_TRANSFER, Edouard@2020: // handler_unconfirmed_private_transfer); Edouard@2020: Edouard@2020: // success Edouard@2020: return 0; Edouard@2020: } Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: static int Init_Network_Interface( Edouard@2020: const char *interface, // for linux: /dev/eth0, /dev/eth1, /dev/wlan0, ... Edouard@2020: // for windows: 192.168.0.1 (IP addr. of interface) Edouard@2020: // NULL => use default! Edouard@2020: const char *port, // Port the server will listen on. (NULL => use default) Edouard@2020: const char *apdu_timeout, // (NULL => use default) Edouard@2020: const char *apdu_retries // (NULL => use default) Edouard@2020: ) Edouard@2020: { Edouard@2020: char datalink_layer[4]; Edouard@2020: Edouard@2020: strcpy(datalink_layer, "BIP"); // datalink_set() does not accpet const char *, so we copy it... Edouard@2020: // BIP_Debug = true; Edouard@2020: Edouard@2020: if (port != NULL) { Edouard@2020: bip_set_port(htons((uint16_t) strtol(port, NULL, 0))); Edouard@2020: } else { Edouard@2020: bip_set_port(htons(0xBAC0)); Edouard@2020: } Edouard@2020: Edouard@2020: if (apdu_timeout != NULL) { Edouard@2020: apdu_timeout_set((uint16_t) strtol(apdu_timeout, NULL, 0)); Edouard@2020: } Edouard@2020: Edouard@2020: if (apdu_retries != NULL) { Edouard@2020: apdu_retries_set((uint8_t) strtol(apdu_retries, NULL, 0)); Edouard@2020: } Edouard@2020: Edouard@2020: // datalink_init is a pointer that will actually call bip_init() Edouard@2020: // datalink_init pointer is set by the call datalink_set("BIP") Edouard@2020: /* NOTE: current implementation of BACnet stack uses the interface Edouard@2020: * only to determine the server's local IP address and broacast address. Edouard@2020: * The local IP addr is later used to discard (broadcast) messages Edouard@2020: * it receives that were sent by itself. Edouard@2020: * The broadcast IP addr is used for broadcast messages. Edouard@2020: * WARNING: The socket itself is created to listen on all Edouard@2020: * available interfaces (INADDR_ANY), so this setting may induce Edouard@2020: * the user in error as we will accept messages arriving on other Edouard@2020: * interfaces (if they exist) Edouard@2020: * (see bip_init() in ports/linux/bip-init.c) Edouard@2020: * (see bip_****() in src/bip.c) Edouard@2020: */ Edouard@2020: char *tmp = (char *)malloc(strlen(interface) + 1); Edouard@2020: if (tmp == NULL) return -1; Edouard@2020: strncpy(tmp, interface, strlen(interface) + 1); Edouard@2020: if (!datalink_init(tmp)) { Edouard@2020: return -1; Edouard@2020: } Edouard@2020: // #if (MAX_TSM_TRANSACTIONS) Edouard@2020: // pEnv = getenv("BACNET_INVOKE_ID"); Edouard@2020: // if (pEnv) { Edouard@2020: // tsm_invokeID_set((uint8_t) strtol(pEnv, NULL, 0)); Edouard@2020: // } Edouard@2020: // #endif Edouard@2020: dlenv_register_as_foreign_device(); Edouard@2020: Edouard@2020: // success Edouard@2020: return 0; Edouard@2020: } Edouard@2020: Edouard@2020: Edouard@2187: // This mutex blocks execution of __init_%(locstr)s() until initialization is done Edouard@2187: static int init_done = 0; Edouard@2187: static pthread_mutex_t init_done_lock = PTHREAD_MUTEX_INITIALIZER; Edouard@2187: static pthread_cond_t init_done_cond = PTHREAD_COND_INITIALIZER; Edouard@2020: Edouard@2020: /** Main function of server demo. **/ Edouard@2020: int bn_server_run(server_node_t *server_node) { Edouard@2020: int res = 0; Edouard@2020: BACNET_ADDRESS src = {0}; /* address where message came from */ Edouard@2020: uint16_t pdu_len = 0; Edouard@2020: unsigned timeout = 1000; /* milliseconds */ Edouard@2020: time_t last_seconds = 0; Edouard@2020: time_t current_seconds = 0; Edouard@2020: uint32_t elapsed_seconds = 0; Edouard@2020: uint32_t elapsed_milliseconds = 0; Edouard@2020: uint32_t address_binding_tmr = 0; Edouard@2020: uint32_t recipient_scan_tmr = 0; Edouard@2020: Edouard@2020: /* allow the device ID to be set */ Edouard@2020: Device_Set_Object_Instance_Number(server_node->device_id); Edouard@2020: /* load any static address bindings in our device bindings list */ Edouard@2020: address_init(); Edouard@2020: /* load any entries in the BDT table from backup file */ Edouard@2020: bvlc_bdt_restore_local(); Edouard@2020: /* Initiliaze the bacnet server 'device' */ Edouard@2020: Device_Init(server_node->device_name); msousa@2649: msousa@2649: /* Although the default values for the following properties are hardcoded into msousa@2649: * the respective variable definition+initialization in the C code, msousa@2649: * these values may be potentially changed after compilation but before msousa@2649: * code startup. This is done by the web interface(1), directly loading the .so file, msousa@2649: * and changing the values in the server_node_t variable. msousa@2649: * We must therefore honor those values when we start running the BACnet device server msousa@2649: * msousa@2649: * (1) Web interface is implemented in runtime/BACnet_config.py msousa@2649: * which works as an extension of the web server in runtime/NevowServer.py msousa@2649: * which in turn is initialised/run by the Beremiz_service.py deamon msousa@2649: */ msousa@2649: Device_Set_Location (server_node->device_location, strlen(server_node->device_location)); msousa@2649: Device_Set_Description (server_node->device_description, strlen(server_node->device_description)); msousa@2649: Device_Set_Application_Software_Version(server_node->device_appsoftware_ver, strlen(server_node->device_appsoftware_ver)); Edouard@2020: /* Set the password (max 31 chars) for Device Communication Control request. */ Edouard@2020: /* Default in the BACnet stack is hardcoded as "filister" */ Edouard@2020: /* (char *) cast is to remove the cast. The function is incorrectly declared/defined in the BACnet stack! */ Edouard@2020: /* BACnet stack needs to change demo/handler/h_dcc.c and include/handlers.h */ Edouard@2020: handler_dcc_password_set((char *)server_node->comm_control_passwd); msousa@2649: msousa@2649: msousa@2649: pthread_mutex_lock(&init_done_lock); msousa@2649: init_done = 1; msousa@2649: pthread_cond_signal(&init_done_cond); msousa@2649: pthread_mutex_unlock(&init_done_lock); msousa@2649: Edouard@2020: /* Set callbacks and configure network interface */ Edouard@2020: res = Init_Service_Handlers(); Edouard@2020: if (res < 0) exit(1); Edouard@2020: res = Init_Network_Interface( Edouard@2020: server_node->network_interface, // interface (NULL => use default (eth0)) Edouard@2020: server_node->port_number, // Port number (NULL => use default (0xBAC0)) Edouard@2020: NULL, // apdu_timeout (NULL => use default) Edouard@2020: NULL // apdu_retries (NULL => use default) Edouard@2020: ); Edouard@2020: if (res < 0) { Edouard@2020: fprintf(stderr, "BACnet plugin: error initializing bacnet server node %%s!\n", server_node->location); Edouard@2020: exit(1); // kill the server thread! Edouard@2020: } Edouard@2020: /* BACnet stack correcly configured. Give user some feedback! */ Edouard@2020: struct in_addr my_addr, broadcast_addr; Edouard@2020: my_addr. s_addr = bip_get_addr(); Edouard@2020: broadcast_addr.s_addr = bip_get_broadcast_addr(); Edouard@2020: printf("BACnet plugin:" Edouard@2020: " Local IP addr: %%s" Edouard@2020: ", Broadcast IP addr: %%s" Edouard@2020: ", Port number: 0x%%04X [%%hu]" Edouard@2020: ", BACnet Device ID: %%d" Edouard@2020: ", Max APDU: %%d" Edouard@2020: ", BACnet Stack Version %%s\n", Edouard@2020: inet_ntoa(my_addr), Edouard@2020: inet_ntoa(broadcast_addr), Edouard@2020: ntohs(bip_get_port()), ntohs(bip_get_port()), Edouard@2020: Device_Object_Instance_Number(), Edouard@2020: MAX_APDU, Edouard@2020: Device_Firmware_Revision() Edouard@2020: ); Edouard@2020: /* configure the timeout values */ Edouard@2020: last_seconds = time(NULL); Edouard@2020: /* broadcast an I-Am on startup */ Edouard@2020: Send_I_Am(&Handler_Transmit_Buffer[0]); Edouard@2020: /* loop forever */ Edouard@2020: for (;;) { Edouard@2020: /* input */ Edouard@2020: current_seconds = time(NULL); Edouard@2020: Edouard@2020: /* returns 0 bytes on timeout */ Edouard@2020: pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); Edouard@2020: Edouard@2020: /* process */ Edouard@2020: if (pdu_len) { Edouard@2020: npdu_handler(&src, &Rx_Buf[0], pdu_len); Edouard@2020: } Edouard@2020: /* at least one second has passed */ Edouard@2020: elapsed_seconds = (uint32_t) (current_seconds - last_seconds); Edouard@2020: if (elapsed_seconds) { Edouard@2020: last_seconds = current_seconds; Edouard@2020: dcc_timer_seconds(elapsed_seconds); msousa@2261: //bvlc_maintenance_timer(elapsed_seconds); // already called by dlenv_maintenance_timer() => do _not_ call here! Edouard@2020: dlenv_maintenance_timer(elapsed_seconds); Edouard@2020: elapsed_milliseconds = elapsed_seconds * 1000; Edouard@2020: tsm_timer_milliseconds(elapsed_milliseconds); Edouard@2020: } Edouard@2020: handler_cov_task(); Edouard@2020: /* scan cache address */ Edouard@2020: address_binding_tmr += elapsed_seconds; Edouard@2020: if (address_binding_tmr >= 60) { Edouard@2020: address_cache_timer(address_binding_tmr); Edouard@2020: address_binding_tmr = 0; Edouard@2020: } Edouard@2020: } Edouard@2020: Edouard@2020: /* should never occur!! */ Edouard@2020: return 0; Edouard@2020: } Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: #include Edouard@2020: Edouard@2020: static void *__bn_server_thread(void *_server_node) { Edouard@2020: server_node_t *server_node = _server_node; Edouard@2020: Edouard@2020: // Enable thread cancelation. Enabled is default, but set it anyway to be safe. Edouard@2020: pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); Edouard@2020: Edouard@2020: // bn_server_run() should never return! Edouard@2020: bn_server_run(server_node); Edouard@2020: fprintf(stderr, "BACnet plugin: bacnet server for node %%s died unexpectedly!\n", server_node->location); /* should never occur */ Edouard@2020: return NULL; Edouard@2020: } Edouard@2020: Edouard@2020: Edouard@2020: int __cleanup_%(locstr)s (); Edouard@2020: Edouard@2020: int __init_%(locstr)s (int argc, char **argv){ Edouard@2020: int index; Edouard@2020: Edouard@2187: init_done = 0; Edouard@2020: /* init each local server */ Edouard@2020: /* NOTE: All server_nodes[].init_state are initialised to 0 in the code Edouard@2020: * generated by the BACnet plugin Edouard@2020: */ Edouard@2020: /* create the BACnet server */ Edouard@2020: server_node.init_state = 1; // we have created the node Edouard@2020: Edouard@2020: /* launch a thread to handle this server node */ Edouard@2020: { Edouard@2020: int res = 0; Edouard@2020: pthread_attr_t attr; Edouard@2020: res |= pthread_attr_init(&attr); Edouard@2020: res |= pthread_create(&(server_node.thread_id), &attr, &__bn_server_thread, (void *)&(server_node)); Edouard@2020: if (res != 0) { Edouard@2020: fprintf(stderr, "BACnet plugin: Error starting bacnet server thread for node %%s\n", server_node.location); Edouard@2020: goto error_exit; Edouard@2020: } Edouard@2020: } Edouard@2187: Edouard@2187: pthread_mutex_lock(&init_done_lock); Edouard@2187: while (!init_done) { Edouard@2187: pthread_cond_wait(&init_done_cond, &init_done_lock); Edouard@2187: } Edouard@2187: pthread_mutex_unlock(&init_done_lock); Edouard@2187: Edouard@2020: server_node.init_state = 2; // we have created the node and thread Edouard@2020: Edouard@2020: return 0; Edouard@2020: Edouard@2020: error_exit: Edouard@2020: __cleanup_%(locstr)s (); Edouard@2020: return -1; Edouard@2020: } Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: void __publish_%(locstr)s (){ Edouard@2020: Analog_Value_Copy_Located_Var_to_Present_Value(); Edouard@2020: Analog_Input_Copy_Located_Var_to_Present_Value(); Edouard@2020: Analog_Output_Copy_Located_Var_to_Present_Value(); Edouard@2020: Binary_Value_Copy_Located_Var_to_Present_Value(); Edouard@2020: Binary_Input_Copy_Located_Var_to_Present_Value(); Edouard@2020: Binary_Output_Copy_Located_Var_to_Present_Value(); Edouard@2020: Multistate_Value_Copy_Located_Var_to_Present_Value(); Edouard@2020: Multistate_Input_Copy_Located_Var_to_Present_Value(); Edouard@2020: Multistate_Output_Copy_Located_Var_to_Present_Value(); Edouard@2020: } Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: void __retrieve_%(locstr)s (){ Edouard@2020: Analog_Value_Copy_Present_Value_to_Located_Var(); Edouard@2020: Analog_Input_Copy_Present_Value_to_Located_Var(); Edouard@2020: Analog_Output_Copy_Present_Value_to_Located_Var(); Edouard@2020: Binary_Value_Copy_Present_Value_to_Located_Var(); Edouard@2020: Binary_Input_Copy_Present_Value_to_Located_Var(); Edouard@2020: Binary_Output_Copy_Present_Value_to_Located_Var(); Edouard@2020: Multistate_Value_Copy_Present_Value_to_Located_Var(); Edouard@2020: Multistate_Input_Copy_Present_Value_to_Located_Var(); Edouard@2020: Multistate_Output_Copy_Present_Value_to_Located_Var(); Edouard@2020: } Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: Edouard@2020: int __cleanup_%(locstr)s (){ Edouard@2020: int index, close; Edouard@2020: int res = 0; Edouard@2020: Edouard@2020: /* kill thread and close connections of each modbus server node */ Edouard@2020: close = 0; Edouard@2020: if (server_node.init_state >= 2) { Edouard@2020: // thread was launched, so we try to cancel it! Edouard@2020: close = pthread_cancel(server_node.thread_id); Edouard@2020: close |= pthread_join (server_node.thread_id, NULL); Edouard@2020: if (close < 0) Edouard@2020: fprintf(stderr, "BACnet plugin: Error closing thread for bacnet server %%s\n", server_node.location); Edouard@2020: } Edouard@2020: res |= close; Edouard@2020: Edouard@2020: close = 0; Edouard@2020: if (server_node.init_state >= 1) { Edouard@2020: // bacnet server node was created, so we try to close it! Edouard@2020: // datalink_cleanup is a pointer that will actually call bip_cleanup() Edouard@2020: // datalink_cleanup pointer is set by the call datalink_set("BIP") Edouard@2020: datalink_cleanup(); Edouard@2020: } Edouard@2020: res |= close; Edouard@2020: server_node.init_state = 0; Edouard@2020: Edouard@2020: /* bacnet library close */ Edouard@2020: // Nothing to do ??? Edouard@2020: Edouard@2020: return res; Edouard@2020: } Edouard@2020: msousa@2649: msousa@2649: msousa@2649: msousa@2649: msousa@2649: /**********************************************/ msousa@2649: /** Functions for Beremiz web interface. **/ msousa@2649: /**********************************************/ msousa@2649: msousa@2649: /* msousa@2649: * Beremiz has a program to run on the PLC (Beremiz_service.py) msousa@2649: * to handle downloading of compiled programs, start/stop of PLC, etc. msousa@2649: * (see runtime/PLCObject.py for start/stop, loading, ...) msousa@2649: * msousa@2649: * This service also includes a web server to access PLC state (start/stop) msousa@2649: * and to change some basic confiuration parameters. msousa@2649: * (see runtime/NevowServer.py for the web server) msousa@2649: * msousa@2649: * The web server allows for extensions, where additional configuration msousa@2649: * parameters may be changed on the running/downloaded PLC. msousa@2649: * BACnet plugin also comes with an extension to the web server, through msousa@2649: * which the basic BACnet plugin configuration parameters may be changed msousa@2649: * (basically, only the parameters in server_node_t may be changed) msousa@2649: * msousa@2649: * The following functions are never called from other C code. They are msousa@2649: * called instead from the python code in runtime/BACnet_config.py, that msousa@2649: * implements the web server extension for configuring BACnet parameters. msousa@2649: */ msousa@2649: msousa@2649: msousa@2649: /* The location (in the Config. Node Tree of Beremiz IDE) msousa@2649: * of the BACnet plugin. msousa@2649: * msousa@2649: * This variable is also used by the BACnet web config code to determine msousa@2649: * whether the current loaded PLC includes the BACnet plugin msousa@2649: * (so it should make the BACnet parameter web interface visible to the user). msousa@2649: */ msousa@2649: const char * __bacnet_plugin_location = "%(locstr)s"; msousa@2649: msousa@2649: msousa@2649: /* NOTE: We could have the python code in runtime/BACnet_config.py msousa@2649: * directly access the server_node_t structure, however msousa@2649: * this would create a tight coupling between these two msousa@2649: * disjoint pieces of code. msousa@2649: * Any change to the server_node_t structure would require the msousa@2649: * python code to be changed accordingly. I have therefore opted msousa@2649: * to cretae get/set functions, one for each parameter. msousa@2649: * msousa@2649: * NOTE: since the BACnet plugin can only support/allow at most one instance msousa@2649: * of the BACnet plugin in Beremiz (2 or more are not allowed due to msousa@2649: * limitations of the underlying BACnet protocol stack being used), msousa@2649: * a single generic version of each of the following functions would work. msousa@2649: * However, simply for the sake of keeping things general, we create msousa@2649: * a diferent function for each plugin instance (which, as I said, msousa@2649: * will never occur for now). msousa@2649: * msousa@2649: * The functions being declared are therefoe named: msousa@2649: * __bacnet_0_get_ConfigParam_location msousa@2649: * __bacnet_0_get_ConfigParam_device_name msousa@2649: * etc... msousa@2649: * where the 0 will be replaced by the location of the BACnet plugin msousa@2649: * in the Beremiz configuration tree (will change depending on where msousa@2649: * the user inserted the BACnet plugin into their project) msousa@2649: */ msousa@2649: msousa@2649: /* macro works for all data types */ msousa@2649: #define __bacnet_get_ConfigParam(param_type,param_name) \ msousa@2649: param_type __bacnet_%(locstr)s_get_ConfigParam_##param_name(void) { \ msousa@2649: return server_node.param_name; \ msousa@2649: } msousa@2649: msousa@2649: /* macro only works for char * data types */ msousa@2649: /* Note that the storage space (max string size) reserved for each parameter msousa@2649: * (this storage space is reserved in device.h) msousa@2649: * is set to a minimum of msousa@2649: * %(BACnet_Param_String_Size)d msousa@2649: * which is set to the value of msousa@2649: * BACNET_PARAM_STRING_SIZE msousa@2649: * in bacnet.py msousa@2649: */ msousa@2649: #define __bacnet_set_ConfigParam(param_type,param_name) \ msousa@2649: void __bacnet_%(locstr)s_set_ConfigParam_##param_name(param_type val) { \ msousa@2649: strncpy(server_node.param_name, val, %(BACnet_Param_String_Size)d); \ msousa@2649: server_node.param_name[%(BACnet_Param_String_Size)d - 1] = '\0'; \ msousa@2649: } msousa@2649: msousa@2649: msousa@2649: #define __bacnet_ConfigParam_str(param_name) \ msousa@2649: __bacnet_get_ConfigParam(const char*,param_name) \ msousa@2649: __bacnet_set_ConfigParam(const char*,param_name) msousa@2649: msousa@2649: msousa@2649: __bacnet_ConfigParam_str(location) msousa@2649: __bacnet_ConfigParam_str(network_interface) msousa@2649: __bacnet_ConfigParam_str(port_number) msousa@2649: __bacnet_ConfigParam_str(device_name) msousa@2649: __bacnet_ConfigParam_str(device_location) msousa@2649: __bacnet_ConfigParam_str(device_description) msousa@2649: __bacnet_ConfigParam_str(device_appsoftware_ver) msousa@2649: __bacnet_ConfigParam_str(comm_control_passwd) msousa@2649: msousa@2649: __bacnet_get_ConfigParam(uint32_t,device_id) msousa@2649: void __bacnet_%(locstr)s_set_ConfigParam_device_id(uint32_t val) { msousa@2649: server_node.device_id = val; msousa@2649: } msousa@2649: