bacnet/runtime/server.c
author Edouard Tisserant <edouard.tisserant@gmail.com>
Sat, 20 May 2023 00:35:41 +0200
changeset 3809 45accb98d761
parent 2649 db68cb0e6bdc
permissions -rw-r--r--
Fake_wx objects now support comparison and hashing
/**************************************************************************
*
* 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;
}


// This mutex blocks execution of __init_%(locstr)s() until initialization is done
static int init_done = 0;
static pthread_mutex_t init_done_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t init_done_cond = PTHREAD_COND_INITIALIZER;

/** 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);
    
    /* Although the default values for the following properties are hardcoded into
     * the respective variable definition+initialization in the C code,
     * these values may be potentially changed after compilation but before 
     * code startup. This is done by the web interface(1), directly loading the .so file, 
     * and changing the values in the server_node_t variable.
     * We must therefore honor those values when we start running the BACnet device server
     * 
     * (1) Web interface is implemented in runtime/BACnet_config.py
     *     which works as an extension of the web server in runtime/NevowServer.py
     *     which in turn is initialised/run by the Beremiz_service.py deamon
     */
    Device_Set_Location                    (server_node->device_location,        strlen(server_node->device_location));
    Device_Set_Description                 (server_node->device_description,     strlen(server_node->device_description));
    Device_Set_Application_Software_Version(server_node->device_appsoftware_ver, strlen(server_node->device_appsoftware_ver));
    /* 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);

    
    pthread_mutex_lock(&init_done_lock);
    init_done = 1;
    pthread_cond_signal(&init_done_cond);
    pthread_mutex_unlock(&init_done_lock);

    /* 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); // already called by dlenv_maintenance_timer() => do _not_ call here!
            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_done = 0;
	/* 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;
		}
	}

    pthread_mutex_lock(&init_done_lock);
    while (!init_done) {
        pthread_cond_wait(&init_done_cond, &init_done_lock);
    }
    pthread_mutex_unlock(&init_done_lock);

	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;
}





/**********************************************/
/** Functions for Beremiz web interface.     **/
/**********************************************/

/*
 * Beremiz has a program to run on the PLC (Beremiz_service.py)
 * to handle downloading of compiled programs, start/stop of PLC, etc.
 * (see runtime/PLCObject.py for start/stop, loading, ...)
 * 
 * This service also includes a web server to access PLC state (start/stop)
 * and to change some basic confiuration parameters.
 * (see runtime/NevowServer.py for the web server)
 * 
 * The web server allows for extensions, where additional configuration
 * parameters may be changed on the running/downloaded PLC.
 * BACnet plugin also comes with an extension to the web server, through
 * which the basic BACnet plugin configuration parameters may be changed
 * (basically, only the parameters in server_node_t may be changed)
 * 
 * The following functions are never called from other C code. They are 
 * called instead from the python code in runtime/BACnet_config.py, that
 * implements the web server extension for configuring BACnet parameters.
 */


/* The location (in the Config. Node Tree of Beremiz IDE)
 * of the BACnet plugin.
 * 
 * This variable is also used by the BACnet web config code to determine 
 * whether the current loaded PLC includes the BACnet plugin
 * (so it should make the BACnet parameter web interface visible to the user).
 */
const char * __bacnet_plugin_location = "%(locstr)s";


/* NOTE: We could have the python code in runtime/BACnet_config.py
 *       directly access the server_node_t structure, however
 *       this would create a tight coupling between these two
 *       disjoint pieces of code.
 *       Any change to the server_node_t structure would require the 
 *       python code to be changed accordingly. I have therefore opted
 *       to cretae get/set functions, one for each parameter.
 *       
 * NOTE: since the BACnet plugin can only support/allow at most one instance 
 *       of the BACnet plugin in Beremiz (2 or more are not allowed due to 
 *       limitations of the underlying BACnet protocol stack being used),
 *       a single generic version of each of the following functions would work.
 *       However, simply for the sake of keeping things general, we create
 *       a diferent function for each plugin instance (which, as I said,
 *       will never occur for now).
 * 
 *      The functions being declared are therefoe named:
 *          __bacnet_0_get_ConfigParam_location
 *          __bacnet_0_get_ConfigParam_device_name
 *        etc...
 *      where the 0 will be replaced by the location of the BACnet plugin
 *      in the Beremiz configuration tree (will change depending on where
 *      the user inserted the BACnet plugin into their project) 
 */

/* macro works for all data types */
#define __bacnet_get_ConfigParam(param_type,param_name)                 \
param_type __bacnet_%(locstr)s_get_ConfigParam_##param_name(void) {     \
    return server_node.param_name;                                      \
}

/* macro only works for char * data types */
/* Note that the storage space (max string size) reserved for each parameter
 * (this storage space is reserved in device.h)
 * is set to a minimum of 
 *     %(BACnet_Param_String_Size)d 
 * which is set to the value of
 *     BACNET_PARAM_STRING_SIZE
 * in bacnet.py
 */
#define __bacnet_set_ConfigParam(param_type,param_name)                 \
void __bacnet_%(locstr)s_set_ConfigParam_##param_name(param_type val) { \
    strncpy(server_node.param_name, val, %(BACnet_Param_String_Size)d); \
    server_node.param_name[%(BACnet_Param_String_Size)d - 1] = '\0';    \
}


#define __bacnet_ConfigParam_str(param_name)        \
__bacnet_get_ConfigParam(const char*,param_name)    \
__bacnet_set_ConfigParam(const char*,param_name)     


__bacnet_ConfigParam_str(location)
__bacnet_ConfigParam_str(network_interface)
__bacnet_ConfigParam_str(port_number)
__bacnet_ConfigParam_str(device_name)
__bacnet_ConfigParam_str(device_location)
__bacnet_ConfigParam_str(device_description)
__bacnet_ConfigParam_str(device_appsoftware_ver)
__bacnet_ConfigParam_str(comm_control_passwd)

__bacnet_get_ConfigParam(uint32_t,device_id)
void __bacnet_%(locstr)s_set_ConfigParam_device_id(uint32_t val) {
    server_node.device_id = val;
}