Edouard@2020: /**************************************************************************
Edouard@2020: *
Edouard@2020: * Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
Edouard@2020: * Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
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 <stddef.h>
Edouard@2020: #include <stdint.h>
Edouard@2020: #include <stdio.h>
Edouard@2020: #include <stdlib.h>
Edouard@2020: #include <signal.h>
Edouard@2020: #include <time.h>
Edouard@2020: #include <inttypes.h>  // 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 <pthread.h>
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: