bacnet/runtime/server.c
changeset 2020 6dddf3070806
child 2187 c6321473fac1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/runtime/server.c	Fri Jun 08 13:28:00 2018 +0200
@@ -0,0 +1,645 @@
+/**************************************************************************
+*
+* Copyright (C) 2006 Steve Karg <skarg@users.sourceforge.net>
+* Copyright (C) 2017 Mario de Sousa <msousa@fe.up.pt>
+*
+* 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 <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <time.h>
+#include <inttypes.h>  // uint32_t, ..., PRIu32, ...
+
+#include "config_bacnet_for_beremiz_%(locstr)s.h"     /* the custom configuration for beremiz pluginh  */
+#include "server_%(locstr)s.h"
+#include "address.h"
+#include "bacdef.h"
+#include "handlers.h"
+#include "client.h"
+#include "dlenv.h"
+#include "bacdcode.h"
+#include "npdu.h"
+#include "apdu.h"
+#include "iam.h"
+#include "tsm.h"
+#include "datalink.h"
+#include "dcc.h"
+#include "getevent.h"
+#include "net.h"
+#include "txbuf.h"
+#include "version.h"
+#include "timesync.h"
+
+
+/* A utility function used by most (all?) implementations of BACnet Objects */
+/* Adds to Prop_List all entries in Prop_List_XX that are not
+ * PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_PROPERTY_LIST
+ * and returns the number of elements that were added
+ */
+int BACnet_Init_Properties_List(
+          int *Prop_List, 
+    const int *Prop_List_XX) {
+  
+    unsigned int i = 0;
+    unsigned int j = 0;
+    
+    for (j = 0; Prop_List_XX[j] >= 0; j++)
+        // Add any propety, except for the following 4 which should not be included
+        // in the Property_List property array.
+        //  (see ASHRAE 135-2016, for example section 12.4.34)
+        if ((Prop_List_XX[j] != PROP_OBJECT_IDENTIFIER) &&
+            (Prop_List_XX[j] != PROP_OBJECT_NAME)       &&
+            (Prop_List_XX[j] != PROP_OBJECT_TYPE)       &&
+            (Prop_List_XX[j] != PROP_PROPERTY_LIST)) {
+            Prop_List[i] = Prop_List_XX[j];
+            i++;
+        }
+    Prop_List[i] = -1; // marks the end of the list!
+    return i;
+}
+
+
+
+
+
+
+int BACnet_encode_character_string(uint8_t *apdu, const char *str) {
+    BACNET_CHARACTER_STRING   char_string;
+    characterstring_init_ansi(&char_string, str);
+    /* FIXME: this might go beyond MAX_APDU length! */
+    return encode_application_character_string(apdu, &char_string);
+}
+
+/* macro that always returns false.
+ * To be used as the 'test_null' parameter to the BACnet_encode_array macro
+ * in situations where we should never encode_null() values.
+ */
+#define retfalse(x) (false)
+
+#define BACnet_encode_array(array, array_len, test_null, encode_function)                  \
+{                                                                                           \
+    uint8_t *apdu = NULL;                                                                   \
+    apdu = rpdata->application_data;                                                        \
+                                                                                            \
+    switch (rpdata->array_index) {                                                          \
+      case 0: /* Array element zero is the number of elements in the array */               \
+        apdu_len = encode_application_unsigned(&apdu[0], array_len);                        \
+        break;                                                                              \
+      case BACNET_ARRAY_ALL: {                                                              \
+        /* if no index was specified, then try to encode the entire list */                 \
+        unsigned i = 0;                                                                     \
+        apdu_len   = 0;                                                                     \
+        for (i = 0; i < array_len; i++) {                                                   \
+            /* FIXME: this might go beyond MAX_APDU length! */                              \
+            if (!test_null(array[i]))                                                       \
+                  apdu_len += encode_function        (&apdu[apdu_len], array[i]);           \
+            else  apdu_len += encode_application_null(&apdu[apdu_len]);                     \
+            /* return error if it does not fit in the APDU */                               \
+            if (apdu_len >= MAX_APDU) {                                                     \
+                rpdata->error_class = ERROR_CLASS_SERVICES;                                 \
+                rpdata->error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT;                        \
+                apdu_len = BACNET_STATUS_ERROR;                                             \
+                break; /* for(;;) */                                                        \
+            }                                                                               \
+        }                                                                                   \
+        break;                                                                              \
+      }                                                                                     \
+      default:                                                                              \
+        if (rpdata->array_index <= array_len) {                                             \
+            if (!test_null(array[rpdata->array_index - 1]))                                 \
+                  apdu_len += encode_function(&apdu[0], array[rpdata->array_index - 1]);    \
+            else  apdu_len += encode_application_null(&apdu[0]);                            \
+        } else {                                                                            \
+            rpdata->error_class = ERROR_CLASS_PROPERTY;                                     \
+            rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;                            \
+            apdu_len = BACNET_STATUS_ERROR;                                                 \
+        }                                                                                   \
+        break;                                                                              \
+    } /* switch() */                                                                        \
+}
+
+
+/* include the device object */
+#include "device_%(locstr)s.c"
+#include "ai_%(locstr)s.c"
+#include "ao_%(locstr)s.c"
+#include "av_%(locstr)s.c"
+#include "bi_%(locstr)s.c"
+#include "bo_%(locstr)s.c"
+#include "bv_%(locstr)s.c"
+#include "msi_%(locstr)s.c"
+#include "mso_%(locstr)s.c"
+#include "msv_%(locstr)s.c"
+
+
+/** Buffer used for receiving */
+static uint8_t Rx_Buf[MAX_MPDU] = { 0 };
+
+
+
+
+/*******************************************************/
+/* BACnet Service Handlers taylored to Beremiz plugin  */ 
+/*******************************************************/
+
+static void BACNET_show_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");
+}
+
+static time_t __timegm(struct tm *new_time) {
+    time_t     sec =  mktime(new_time);  /* assume new_time is in local time */
+    
+    /* sec will be an aproximation of the correct value. 
+     * We must now fix this with the current difference
+     * between UTC and localtime.
+     * 
+     * WARNING: The following algorithm to determine the current
+     *          difference between local time and UTC will not 
+     *          work if each value (lcl and utc) falls on a different
+     *          side of a change to/from DST.
+     *          For example, assume a change to DST is made at 12h00 
+     *          of day X. The following algorithm does not work if:
+     *            - lcl falls before 12h00 of day X
+     *            - utc falls after  12h00 of day X
+     */
+    struct  tm lcl = *localtime(&sec); // lcl will be == new_time
+    struct  tm utc = *gmtime   (&sec);
+    
+    if (lcl.tm_isdst == 1)  utc.tm_isdst = 1;
+    
+    time_t sec_lcl =  mktime(&lcl);
+    time_t sec_utc =  mktime(&utc);
+
+    /* difference in seconds between localtime and utc */
+    time_t sec_dif = sec_lcl - sec_utc;
+    return sec + sec_dif;
+}
+
+
+static void BACNET_set_date_time(
+    BACNET_DATE * bdate,
+    BACNET_TIME * btime,
+    int utc /* set to > 0 if date & time in UTC */)
+{
+    struct tm       brokendown_time;
+    time_t          seconds = 0;
+    struct timespec ts;
+
+    brokendown_time.tm_sec   = btime->sec;        /* seconds 0..60    */
+    brokendown_time.tm_min   = btime->min;        /* minutes 0..59    */
+    brokendown_time.tm_hour  = btime->hour;       /* hours   0..23    */
+    brokendown_time.tm_mday  = bdate->day;        /* day of the month 1..31 */
+    brokendown_time.tm_mon   = bdate->month-1;    /* month 0..11      */
+    brokendown_time.tm_year  = bdate->year-1900;  /* years since 1900 */
+//  brokendown_time.tm_wday  = ;                  /* day of the week  */
+//  brokendown_time.tm_yday  = ;                  /* day in the year  */
+    brokendown_time.tm_isdst = -1;                /* daylight saving time (-1 => unknown) */
+
+    // Tranform time into format -> 'seconds since epoch'
+    /* WARNING: timegm() is a non standard GNU extension.
+     *          If you do not have it on your build system then consider
+     *          finding the source code for timegm() (it is LGPL) from GNU
+     *          C library and copying it here 
+     *          (e.g. https://code.woboq.org/userspace/glibc/time/timegm.c.html)
+     *          Another alternative is to use the fundion __timegm() above,
+     *          which will mostly work but may have errors when the time being 
+     *          converted is close to the time in the year when changing 
+     *          to/from DST (daylight saving time)
+     */
+    if (utc > 0) seconds = timegm(&brokendown_time);    
+    else         seconds = mktime(&brokendown_time);
+
+    ts.tv_sec  = seconds;
+    ts.tv_nsec = btime->hundredths*10*1000*1000;
+
+//  fprintf(stderr, "clock_settime() s=%%ul,  ns=%%u\n", ts.tv_sec, ts.tv_nsec);
+    clock_settime(CLOCK_REALTIME, &ts); 
+//  clock_gettime(CLOCK_REALTIME, &ts); 
+//  fprintf(stderr, "clock_gettime() s=%%ul,  ns=%%u\n", ts.tv_sec, ts.tv_nsec);
+}
+
+
+
+void BACnet_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 (len > 0) {
+        fprintf(stderr, "BACnet plugin: Received TimeSyncronization Request -> ");
+        BACNET_show_date_time(&bdate, &btime);
+        /* set the time */
+        BACNET_set_date_time(&bdate, &btime, 0 /* time in local time */);
+    }
+
+    return;
+}
+
+void BACnet_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 (len > 0) {
+        fprintf(stderr, "BACnet plugin: Received TimeSyncronizationUTC Request -> ");
+        BACNET_show_date_time(&bdate, &btime);
+        /* set the time */
+        BACNET_set_date_time(&bdate, &btime, 1 /* time in UTC */);
+    }
+
+    return;
+}
+
+
+
+/**********************************************/
+/** Initialize the handlers we will utilize. **/
+/**********************************************/
+/*
+ * TLDR: The functions that will create the __Resp.__ messages.
+ * 
+ * The service handlers are the functions that will respond to BACnet requests this device receives.
+ * In essence, the service handlers will create and send the Resp. (Response) messages
+ * of the Req. -> Ind. -> Resp. -> Conf. service sequence defined in OSI
+ * (Request, Indication, Response, Confirmation)
+ * 
+ */
+static int Init_Service_Handlers(
+    void)
+{
+    /* 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 unconfirmed services that we support. */
+    apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS,  // DM-DDB-B - Dynamic Device Binding B (Resp.)
+                                   handler_who_is);           //            (see ASHRAE 135-2016, section K5.1 and K5.2)
+//  apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM,    // DM-DDB-A - Dynamic Device Binding A (Resp.)
+//                                 handler_i_am_bind);        //            Responding to I_AM requests is for clients (A)!
+//                                                            //            (see ASHRAE 135-2016, section K5.1 and K5.2)
+    apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, // DM-DOB-B - Dynamic Object Binding B (Resp.)
+                                   handler_who_has);          //            (see ASHRAE 135-2016, section K5.3 and K5.4)
+//  apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_HAVE,  // DM-DOB-A - Dynamic Object Binding A (Resp.)
+//                                 handler_i_have);           //            Responding to I_HAVE requests is for clients (A)!
+//                                                            //            (see ASHRAE 135-2016, section K5.3 and K5.4)
+    apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION, // DM-UTC-B -UTCTimeSynchronization-B (Resp.)
+                                 BACnet_handler_timesync_utc);                 //            (see ASHRAE 135-2016, section K5.14)
+    apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION,     // DM-TS-B - TimeSynchronization-B (Resp.)
+                                 BACnet_handler_timesync);                     //            (see ASHRAE 135-2016, section K5.12)
+
+    /* Set the handlers for any confirmed services that we support. */
+    apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY,      // DS-RP-B - Read Property B (Resp.)
+                                 handler_read_property);             //            (see ASHRAE 135-2016, section K1.2)
+//  apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROP_MULTIPLE, // DS-RPM-B -Read Property Multiple-B (Resp.)
+//                               handler_read_property_multiple);    //            (see ASHRAE 135-2016, section K1.4)
+    apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROPERTY,     // DS-WP-B - Write Property B (Resp.)
+                                 handler_write_property);            //            (see ASHRAE 135-2016, section K1.8)
+//  apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE,// DS-WPM-B -Write Property Multiple B (Resp.)
+//                               handler_write_property_multiple);   //            (see ASHRAE 135-2016, section K1.10)
+//  apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_RANGE,
+//                               handler_read_range);
+//  apdu_set_confirmed_handler(SERVICE_CONFIRMED_REINITIALIZE_DEVICE,
+//                               handler_reinitialize_device);
+    
+//  apdu_set_confirmed_handler(SERVICE_CONFIRMED_SUBSCRIBE_COV,
+//                               handler_cov_subscribe);
+//  apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_COV_NOTIFICATION,
+//                               handler_ucov_notification);
+    /* handle communication so we can shutup when asked */
+    apdu_set_confirmed_handler(SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, // DM-DCC-B - Device Communication Control B
+                                 handler_device_communication_control);        //            (see ASHRAE 135-2016, section K5.6)
+//  /* handle the data coming back from private requests */
+//  apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_PRIVATE_TRANSFER,
+//                               handler_unconfirmed_private_transfer);
+    
+    // success
+    return 0;
+}
+
+
+
+static int Init_Network_Interface(
+    const char *interface,    // for linux:   /dev/eth0, /dev/eth1, /dev/wlan0, ...
+                              // for windows: 192.168.0.1 (IP addr. of interface)
+                              // NULL => use default!
+    const char *port,         // Port the server will listen on. (NULL => use default)
+    const char *apdu_timeout, // (NULL => use default)
+    const char *apdu_retries  // (NULL => use default)
+   )
+{
+    char datalink_layer[4];
+
+    strcpy(datalink_layer, "BIP"); // datalink_set() does not accpet const char *, so we copy it...
+//  BIP_Debug = true;
+
+    if (port != NULL) {
+        bip_set_port(htons((uint16_t) strtol(port, NULL, 0)));
+    } else {
+            bip_set_port(htons(0xBAC0));
+    }
+    
+    if (apdu_timeout != NULL) {
+        apdu_timeout_set((uint16_t) strtol(apdu_timeout, NULL, 0));
+    }
+
+    if (apdu_retries != NULL) {
+        apdu_retries_set((uint8_t) strtol(apdu_retries, NULL, 0));
+    }
+
+    // datalink_init is a pointer that will actually call bip_init()
+    // datalink_init pointer is set by the call datalink_set("BIP")
+    /* NOTE: current implementation of BACnet stack uses the interface
+     *       only to determine the server's local IP address and broacast address.
+     *       The local IP addr is later used to discard (broadcast) messages
+     *       it receives that were sent by itself.
+     *       The broadcast IP addr is used for broadcast messages.
+     *       WARNING: The socket itself is created to listen on all 
+     *       available interfaces (INADDR_ANY), so this setting may induce
+     *       the user in error as we will accept messages arriving on other
+     *       interfaces (if they exist)
+     *       (see bip_init() in ports/linux/bip-init.c)
+     *       (see bip_****() in src/bip.c)
+     */
+    char *tmp = (char *)malloc(strlen(interface) + 1);
+    if (tmp == NULL) return -1;
+    strncpy(tmp, interface, strlen(interface) + 1);
+    if (!datalink_init(tmp)) {
+        return -1;
+    }
+// #if (MAX_TSM_TRANSACTIONS)
+//     pEnv = getenv("BACNET_INVOKE_ID");
+//     if (pEnv) {
+//         tsm_invokeID_set((uint8_t) strtol(pEnv, NULL, 0));
+//     }
+// #endif
+    dlenv_register_as_foreign_device();
+    
+    // success
+    return 0;
+}
+
+
+
+/** Main function of server demo. **/
+int bn_server_run(server_node_t *server_node) {
+    int res = 0;
+    BACNET_ADDRESS src = {0};  /* address where message came from */
+    uint16_t pdu_len = 0;
+    unsigned timeout = 1000;       /* milliseconds */
+    time_t last_seconds = 0;
+    time_t current_seconds = 0;
+    uint32_t elapsed_seconds = 0;
+    uint32_t elapsed_milliseconds = 0;
+    uint32_t address_binding_tmr = 0;
+    uint32_t recipient_scan_tmr = 0;
+
+    /* allow the device ID to be set */
+    Device_Set_Object_Instance_Number(server_node->device_id);
+    /* load any static address bindings in our device bindings list */
+    address_init();
+    /* load any entries in the BDT table from backup file */
+    bvlc_bdt_restore_local();
+    /* Initiliaze the bacnet server 'device' */    
+    Device_Init(server_node->device_name);
+    /* Set the password (max 31 chars) for Device Communication Control request. */
+    /* Default in the BACnet stack is hardcoded as "filister" */
+    /* (char *) cast is to remove the cast. The function is incorrectly declared/defined in the BACnet stack! */
+    /* BACnet stack needs to change demo/handler/h_dcc.c and include/handlers.h                               */
+    handler_dcc_password_set((char *)server_node->comm_control_passwd);
+    /* Set callbacks and configure network interface */
+    res = Init_Service_Handlers();
+    if (res < 0) exit(1);
+    res = Init_Network_Interface(
+            server_node->network_interface, // interface    (NULL => use default (eth0))
+            server_node->port_number,       // Port number  (NULL => use default (0xBAC0))
+            NULL,              // apdu_timeout (NULL => use default)
+            NULL               // apdu_retries (NULL => use default)
+           );
+    if (res < 0) {
+        fprintf(stderr, "BACnet plugin: error initializing bacnet server node %%s!\n", server_node->location);
+        exit(1); // kill the server thread!
+    }
+    /* BACnet stack correcly configured. Give user some feedback! */
+    struct in_addr my_addr, broadcast_addr;
+    my_addr.       s_addr = bip_get_addr();
+    broadcast_addr.s_addr = bip_get_broadcast_addr();
+    printf("BACnet plugin:"
+                         " Local IP addr: %%s" 
+                        ", Broadcast IP addr: %%s" 
+                        ", Port number: 0x%%04X [%%hu]" 
+                        ", BACnet Device ID: %%d"
+                        ", Max APDU: %%d"
+                        ", BACnet Stack Version %%s\n",
+                        inet_ntoa(my_addr),
+                        inet_ntoa(broadcast_addr),
+                        ntohs(bip_get_port()), ntohs(bip_get_port()),
+                        Device_Object_Instance_Number(), 
+                        MAX_APDU,
+                        Device_Firmware_Revision()
+                        );
+    /* configure the timeout values */
+    last_seconds = time(NULL);
+    /* broadcast an I-Am on startup */
+    Send_I_Am(&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 */
+        elapsed_seconds = (uint32_t) (current_seconds - last_seconds);
+        if (elapsed_seconds) {
+            last_seconds = current_seconds;
+            dcc_timer_seconds(elapsed_seconds);
+            bvlc_maintenance_timer(elapsed_seconds);
+            dlenv_maintenance_timer(elapsed_seconds);
+            elapsed_milliseconds = elapsed_seconds * 1000;
+            tsm_timer_milliseconds(elapsed_milliseconds);
+        }
+        handler_cov_task();
+        /* scan cache address */
+        address_binding_tmr += elapsed_seconds;
+        if (address_binding_tmr >= 60) {
+            address_cache_timer(address_binding_tmr);
+            address_binding_tmr = 0;
+        }
+    }
+
+    /* should never occur!! */
+    return 0;
+}
+
+
+
+
+
+#include <pthread.h>
+
+static void *__bn_server_thread(void *_server_node)  {
+	server_node_t *server_node = _server_node;
+
+	// Enable thread cancelation. Enabled is default, but set it anyway to be safe.
+	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
+	// bn_server_run() should never return!
+	bn_server_run(server_node);
+	fprintf(stderr, "BACnet plugin: bacnet server for node %%s died unexpectedly!\n", server_node->location); /* should never occur */
+	return NULL;
+}
+
+
+int __cleanup_%(locstr)s ();
+
+int __init_%(locstr)s (int argc, char **argv){
+	int index;
+
+	/* init each local server */
+	/* NOTE: All server_nodes[].init_state are initialised to 0 in the code 
+	 *       generated by the BACnet plugin 
+	 */
+	/* create the BACnet server */
+	server_node.init_state = 1; // we have created the node
+	
+	/* launch a thread to handle this server node */
+	{
+		int res = 0;
+		pthread_attr_t attr;
+		res |= pthread_attr_init(&attr);
+		res |= pthread_create(&(server_node.thread_id), &attr, &__bn_server_thread, (void *)&(server_node));
+		if (res !=  0) {
+			fprintf(stderr, "BACnet plugin: Error starting bacnet server thread for node %%s\n", server_node.location);
+			goto error_exit;
+		}
+	}
+	server_node.init_state = 2; // we have created the node and thread
+
+	return 0;
+	
+error_exit:
+	__cleanup_%(locstr)s ();
+	return -1;
+}
+
+
+
+
+
+void __publish_%(locstr)s (){
+       Analog_Value_Copy_Located_Var_to_Present_Value();
+       Analog_Input_Copy_Located_Var_to_Present_Value();
+      Analog_Output_Copy_Located_Var_to_Present_Value();
+       Binary_Value_Copy_Located_Var_to_Present_Value();
+       Binary_Input_Copy_Located_Var_to_Present_Value();
+      Binary_Output_Copy_Located_Var_to_Present_Value();
+   Multistate_Value_Copy_Located_Var_to_Present_Value();
+   Multistate_Input_Copy_Located_Var_to_Present_Value();
+  Multistate_Output_Copy_Located_Var_to_Present_Value();
+}
+
+
+
+
+
+void __retrieve_%(locstr)s (){
+       Analog_Value_Copy_Present_Value_to_Located_Var();
+       Analog_Input_Copy_Present_Value_to_Located_Var();
+      Analog_Output_Copy_Present_Value_to_Located_Var();
+       Binary_Value_Copy_Present_Value_to_Located_Var();
+       Binary_Input_Copy_Present_Value_to_Located_Var();
+      Binary_Output_Copy_Present_Value_to_Located_Var();
+   Multistate_Value_Copy_Present_Value_to_Located_Var();
+   Multistate_Input_Copy_Present_Value_to_Located_Var();
+  Multistate_Output_Copy_Present_Value_to_Located_Var();
+}
+
+
+
+
+
+int __cleanup_%(locstr)s (){
+	int index, close;
+	int res = 0;
+
+	/* kill thread and close connections of each modbus server node */
+	close = 0;
+	if (server_node.init_state >= 2) {
+		// thread was launched, so we try to cancel it!
+		close  = pthread_cancel(server_node.thread_id);
+		close |= pthread_join  (server_node.thread_id, NULL);
+		if (close < 0)
+			fprintf(stderr, "BACnet plugin: Error closing thread for bacnet server %%s\n", server_node.location);
+	}
+	res |= close;
+
+	close = 0;
+	if (server_node.init_state >= 1) {
+		// bacnet server node was created, so we try to close it!
+		// datalink_cleanup is a pointer that will actually call bip_cleanup()
+		// datalink_cleanup pointer is set by the call datalink_set("BIP")
+		datalink_cleanup();
+	}
+	res |= close;
+	server_node.init_state = 0;
+
+	/* bacnet library close */
+	// Nothing to do ???
+
+	return res;
+}
+