bacnet/runtime/server.c
author Edouard Tisserant
Wed, 01 Jul 2020 10:36:20 +0200
changeset 2686 703ebf57508a
parent 2649 db68cb0e6bdc
permissions -rw-r--r--
Modbus and Bacnet websettings : Rename variables and functions to avoid name collisions.

Websettings for modbus and bacnet are now passed to runtime as python files loaded (execfile) at init of PLCObject with the same globals.
Because if this, same names used in previously different modules now colide.
/**************************************************************************
*
* 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;
}