
/*
 * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL). See the LICENSE file in the root
 * directory for license terms. You may choose either license, or both.
 */

/*
 * An example of a gateway device which is capable of communicating 
 * with Oracle IoT Cloud Service on behalf of other devices that are incapable 
 * of direct communication with Oracle IoT Cloud Service. This sample illustrates
 * C code for sending data to the cloud service, receiving data 
 * from the cloud service and register indirect connected devices.
 * 
 * The sample uses the virualization API. 
 * It presents a simple humidity sensor device and temperature sensor device which
 * communicates with server through a gateway device. The sample uses the asynchronous
 * message dispatcher to send and receive messages.
 * 
 * Device implements a device models. A device model is a set of related 
 * attributes, actions, and message formats that can be represented in 
 * a real device. For this example the "Humidity Sensor" and the "Temperature Sensor" 
 * device models are used. These device models must be uploaded to the server 
 * before running this example.
 */

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <math.h>

/* include iot cs device model APIs */
#include "iotcs_virtual_device.h"
/* include methods for device client*/
#include "iotcs_device.h"

#ifndef IOTCS_CLIENT_ID_BUFFER_LENGTH
#define IOTCS_CLIENT_ID_BUFFER_LENGTH 76
#endif

#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
#define ARRAY_LEN(X) (sizeof(X)/sizeof(X[0]))

/*
 *                           Constants & Variables
 */
#ifndef USE_POLICY
#define USE_POLICY 0
#endif

/* used for humidity sensor work simulation */

/* The probability that the humidity threshold will be exceeded equals PROBABILITY */
#define HUMIDITY_PROBABILITY 10
/* The max range for humidity value */
#define HUMIDITY_VMAX 100
/* The min range for humidity value */
#define HUMIDITY_VMIN  0
/* Used to generate fluctuation around the 'set point' */
static int humidity_angle = 0;
/* Current humidity sensor value */
static int current_humidity_sensor_value = HUMIDITY_VMAX / 2;
/* Current humidity sensor max threshold */
static int current_humidity_sensor_max_threshold = HUMIDITY_VMAX;

/* used for temperature sensor work simulation */

/* The probability that the temp threshold will be exceeded equals TEMP_PROBABILITY */
#define TEMP_PROBABILITY 10
/* The max range for temp value */
#define TEMP_VMAX 80
/* The min range for temp value */
#define TEMP_VMIN  -20
/* The start temp value */
#define START_TEMP  20.0
/* Used to generate fluctuation around the 'set point' */
static int temp_angle = 0;
/* Current temp sensor value */
static float current_temp_sensor_value = START_TEMP;
/* The maximum value measured by the sensor since power ON or reset */
static float current_temp_sensor_max_value = START_TEMP;
/* The minimum value measured by the sensor since power ON or reset */
static float current_temp_sensor_min_value = START_TEMP;
/* Current temp sensor max threshold */
static int current_temp_sensor_max_threshold = TEMP_VMAX;
/* Current temp sensor min threshold */
static int current_temp_sensor_min_threshold = TEMP_VMIN;
/* Current temp sensor time */
static iotcs_date_time current_temp_sensor_time;
/* Current temp sensor unit */
static const char* current_temp_sensor_unit = "Celsius";
/* Power flag of temp sensor 
 * IOTCS_FALSE - turn off, IOTCS_TRUE - turn on */
static iotcs_bool temp_power = IOTCS_FALSE;
static iotcs_bool temp_was_off = IOTCS_FALSE;

/* other variables */

/* used as sending loop condition */
static volatile int keep_running = 1;
/* temperature device current value */
static float temp_device_current_value;
/* humidity device current value */
static int huminity_device_current_value;
/* temperature/humidity device current threshold */
static int device_current_threshold;
/* device current time */
static iotcs_date_time device_current_time;
/* temperature device model handle */
static iotcs_device_model_handle sensor_temp_dm_handle = NULL;
/* humidity device model handle */
static iotcs_device_model_handle sensor_humidity_dm_handle = NULL;
/* humidity alert handle */
static iotcs_alert_handle humidity_sensor_alert = NULL;
/* temperature hot alert handle */
static iotcs_alert_handle temp_sensor_hot_alert = NULL;
/* temperature cold alert handle */
static iotcs_alert_handle temp_sensor_cold_alert = NULL;
/* temperature device handle */
static iotcs_virtual_device_handle temp_device_handle = NULL;
/* humidity device handle */
static iotcs_virtual_device_handle humidity_device_handle = NULL;
/* means that iotcs_init() method was successful*/
static iotcs_bool is_initialized = IOTCS_FALSE;

/* metadata of indirect connected devices */
const static iotcs_key_value temp_metadata[] = {
    {"manufacturer", "Temp Sample"},
    {"modelNumber", "MN-1000"},
    {"serialNumber", "SN-1000"},
    {"deviceClass", "temp sensor"},
    {"sampleAttr", "test text temp sensor"},
    {"year", "2015"},
    {NULL, NULL}
};

const static iotcs_key_value humidity_metadata[] = {
    {"manufacturer", "Humidity Sample"},
    {"modelNumber", "MN-2000"},
    {"serialNumber", "SN-2000"},
    {"deviceClass", "humidity sensor"},
    {"sampleStrAttr", "test text humidity sensor"},
    {"year", "2015"},
    {NULL, NULL}
};

/* device models supported by indirect connected devices */
static const char *ia_temp_device_urns[] = {
    "urn:com:oracle:iot:device:temperature_sensor",
    NULL
};

static const char *ia_humidity_device_urns[] = {
    "urn:com:oracle:iot:device:humidity_sensor",
    NULL
};

/*
 *                              Functions
 */

static void finalize_library() {
    /* Clean up */
    /* free alert handles */
#if (!USE_POLICY)
    if (humidity_sensor_alert) {
        iotcs_virtual_device_free_alert_handle(humidity_sensor_alert);
        humidity_sensor_alert = NULL;
    }
    if (temp_sensor_hot_alert) {
        iotcs_virtual_device_free_alert_handle(temp_sensor_hot_alert);
        temp_sensor_hot_alert = NULL;
    }
    if (temp_sensor_cold_alert) {
        iotcs_virtual_device_free_alert_handle(temp_sensor_cold_alert);
        temp_sensor_cold_alert = NULL;
    }
#endif
    /* free device handles */
    if (temp_device_handle) {
        iotcs_free_virtual_device_handle(temp_device_handle);
        temp_device_handle = NULL;
    }
    if (humidity_device_handle) {
        iotcs_free_virtual_device_handle(humidity_device_handle);
        humidity_device_handle = NULL;
    }
    /* free device model handles */
    if (sensor_temp_dm_handle) {
        iotcs_free_device_model(sensor_temp_dm_handle);
        sensor_temp_dm_handle = NULL;
    }
    if (sensor_humidity_dm_handle) {
        iotcs_free_device_model(sensor_humidity_dm_handle);
        sensor_humidity_dm_handle = NULL;
    }

    /* 
     * Calling finalization of the library ensures communications channels are closed,
     * previously allocated temporary resources are released.
     */
    if (is_initialized) {
        iotcs_finalize();
        is_initialized = IOTCS_FALSE;
    }
}

/* print error message and terminate the program execution */
static void error(const char* message) {
    printf("Error occurred: %s\n", message);
    finalize_library();
    exit(EXIT_FAILURE);
}

/* handler for interrupt signal */
static void int_handler() {
    keep_running = 0;
}

/* set all temperature device attributes */
static void set_all_temp_attributes() {
    iotcs_result rv;
    static int temp_init = 1;

    rv = iotcs_virtual_device_set_date_time(temp_device_handle, "startTime", current_temp_sensor_time);

    if (rv != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_set_date_time method failed\n");
    }

    rv = iotcs_virtual_device_set_float(temp_device_handle, "temp", current_temp_sensor_value);

    if (rv != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_set_float method failed\n");
    }

    rv = iotcs_virtual_device_set_string(temp_device_handle, "unit", current_temp_sensor_unit);

    if (rv != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_set_string method failed\n");
    }

    rv = iotcs_virtual_device_set_float(temp_device_handle, "minTemp", current_temp_sensor_min_value);

    if (rv != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_set_float method failed\n");
    }

    rv = iotcs_virtual_device_set_float(temp_device_handle, "maxTemp", current_temp_sensor_max_value);

    if (rv != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_set_float method failed\n");
    }

    /* set the temp min and max threshold attributes on subsequent sets
     * on the initial call the device model's default values will be used. */
    if (!temp_init) {
        rv = iotcs_virtual_device_set_integer(temp_device_handle, "minThreshold", current_temp_sensor_min_threshold);

        if (rv != IOTCS_RESULT_OK) {
            error("iotcs_virtual_device_set_integer method failed\n");
        }

        rv = iotcs_virtual_device_set_integer(temp_device_handle, "maxThreshold", current_temp_sensor_max_threshold);

        if (rv != IOTCS_RESULT_OK) {
            error("iotcs_virtual_device_set_integer method failed\n");
        }
    } else {
        temp_init = 0;
    }
}

/* set all humidity device attributes */
static void set_all_humidity_attributes() {
    iotcs_result rv;
    static int humidity_init = 1;

    rv = iotcs_virtual_device_set_integer(humidity_device_handle,
            "humidity", current_humidity_sensor_value);

    if (rv != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_set_integer method failed\n");
    }

    /* set humidity device max threshold only on subsequent sets
     * on the initial call the device model's default values will be 
     * used */
    if (!humidity_init) {
        rv = iotcs_virtual_device_set_integer(humidity_device_handle, "maxThreshold", current_humidity_sensor_max_threshold);

        if (rv != IOTCS_RESULT_OK) {
            error("iotcs_virtual_device_set_integer method failed\n");
        }
    } else {
        humidity_init = 0;
    }
}

/* print all humidity device attributes */
static void print_all_humidity_attributes() {
    if (iotcs_virtual_device_get_integer(humidity_device_handle, "humidity", &huminity_device_current_value) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_get_integer method failed\n");
    }
    printf("Humidity device value = %d\n", huminity_device_current_value);

    if (iotcs_virtual_device_get_integer(humidity_device_handle, "maxThreshold", &device_current_threshold) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_get_integer method failed\n");
    }
    printf("Humidity device max threshold value = %d\n", device_current_threshold);
}

/* print all temperature device attributes */
static void print_all_temp_attributes() {
    const char* device_current_unit;
    if (iotcs_virtual_device_get_date_time(temp_device_handle, "startTime", &device_current_time) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_get_date_time method failed\n");
    }
    printf("Device start time = %ld\n", (long int) device_current_time);

    if (iotcs_virtual_device_get_float(temp_device_handle, "temp", &temp_device_current_value) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_get_float method failed\n");
    }
    printf("Device temp value = %g\n", temp_device_current_value);

    if (iotcs_virtual_device_get_string(temp_device_handle, "unit", &device_current_unit) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_get_string method failed\n");
    }
    printf("Device unit = %s\n", device_current_unit);
    free((char*) device_current_unit);

    if (iotcs_virtual_device_get_float(temp_device_handle, "minTemp", &temp_device_current_value) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_get_float method failed\n");
    }
    printf("Device min temp = %g\n", temp_device_current_value);

    if (iotcs_virtual_device_get_float(temp_device_handle, "maxTemp", &temp_device_current_value) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_get_float method failed\n");
    }
    printf("Device max temp = %g\n", temp_device_current_value);

    if (iotcs_virtual_device_get_integer(temp_device_handle, "minThreshold", &device_current_threshold) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_get_integer method failed\n");
    }
    printf("Device min temp threshold = %d\n", device_current_threshold);

    if (iotcs_virtual_device_get_integer(temp_device_handle, "maxThreshold", &device_current_threshold) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_get_integer method failed\n");
    }
    printf("Device max temp threshold = %d\n", device_current_threshold);
}

/* get random number between min and max values */
static int randomr(unsigned int min, unsigned int max) {
    return (rand() % max) +min;
}

/* a simple implementation of the humidity sensor work */
static void humidity_sensor_work() {
    int random = randomr(0, 100);
    /* this variable equals 1 with probability = HUMIDITY_PROBABILITY.It's simulate alert */
    const int exceed = (random < HUMIDITY_PROBABILITY / 2) || (random >= 100 - HUMIDITY_PROBABILITY / 2);

    int add_value = 0;
    if (exceed) {
        add_value = current_humidity_sensor_max_threshold - current_humidity_sensor_max_threshold / 2 + 1;
        printf("Humidity sensor: Alert\n");
    }

    /* 
     * get new sensor value. 
     * If exceed threshold (alert) value will be bigger then max threshold
     */
    current_humidity_sensor_value = MIN(HUMIDITY_VMAX, (current_humidity_sensor_max_threshold / 2) + add_value + fabs(10 * sin(humidity_angle)));
    humidity_angle = (humidity_angle + 20) % 360;
}

/* a simple implementation of the temperature sensor work */
static void temp_sensor_work() {
    if (temp_power == IOTCS_TRUE) {
        int random = randomr(0, 100);
        const int exceed = (random < TEMP_PROBABILITY / 2) || (random >= 100 - TEMP_PROBABILITY / 2);
        int add_value = 0;
        if (exceed) {
            int direction = (randomr(0, 100) < 50) ? 1 : -1;
            add_value = direction * abs(current_temp_sensor_max_threshold - current_temp_sensor_min_threshold + 10);
            printf("Temp sensor: Alert\n");
        }

        current_temp_sensor_value = MAX(TEMP_VMIN, MIN(TEMP_VMAX, (current_temp_sensor_min_threshold + current_temp_sensor_max_threshold)/2 + add_value + 5 * sin(temp_angle)));
        temp_angle = (temp_angle + 20) % 360;
        current_temp_sensor_min_value = MIN(current_temp_sensor_min_value, current_temp_sensor_value);
        current_temp_sensor_max_value = MAX(current_temp_sensor_max_value, current_temp_sensor_value);
    }
}

/* action handler for temperature_sensor/actions/power */
static void temp_sensor_power(int on) {
    if (on) {
        if (temp_power == IOTCS_TRUE) {
            return;
        }
        // omit nano sec
        current_temp_sensor_time = time(NULL) * 1000;

        printf("Temp sensor start: %ld\n", (long int) current_temp_sensor_time);
        temp_power = IOTCS_TRUE;
        return;
    }
    temp_power = IOTCS_FALSE;
}

/* action handler for temperature_sensor/actions/reset */
static void temp_sensor_reset() {
    /* set temperature sensor min and max values to current value */
    current_temp_sensor_max_value = current_temp_sensor_value;
    current_temp_sensor_min_value = current_temp_sensor_value;
}

/* change temperature device attributes callback */
static void on_change_temp_cb(iotcs_virtual_device_change_event *event) {
    printf("Run temp on change callback\n");
    iotcs_result rv;
    iotcs_named_value *current_event = &event->named_value;
    while (current_event != NULL) {
        if (strcasecmp(current_event->name, "minThreshold") == 0) {
            current_temp_sensor_min_threshold = current_event->typed_value.value.int_value;

            rv = iotcs_virtual_device_set_integer(event->virtual_device_handle,
                    current_event->name, current_temp_sensor_min_threshold);

            if (rv != IOTCS_RESULT_OK) {
                error("iotcs_virtual_device_set_integer method failed\n");
            }

            rv = iotcs_virtual_device_get_integer(event->virtual_device_handle,
                    current_event->name, &device_current_threshold);

            if (rv != IOTCS_RESULT_OK) {
                error("iotcs_virtual_device_get_integer method failed\n");
            }
            printf("Set device min temp threshold = %d\n", device_current_threshold);
        } else if (strcasecmp(current_event->name, "maxThreshold") == 0) {
            current_temp_sensor_max_threshold = current_event->typed_value.value.int_value;

            rv = iotcs_virtual_device_set_integer(event->virtual_device_handle,
                    current_event->name, current_temp_sensor_max_threshold);

            if (rv != IOTCS_RESULT_OK) {
                error("iotcs_virtual_device_set_integer method failed\n");
            }

            rv = iotcs_virtual_device_get_integer(event->virtual_device_handle,
                    current_event->name, &device_current_threshold);

            if (rv != IOTCS_RESULT_OK) {
                error("iotcs_virtual_device_get_integer method failed\n");
            }
            printf("Set device max temp threshold = %d\n", device_current_threshold);
        } else {
            printf("Unexpected attribute in temp on change callback\n");
        }
        current_event = current_event->next;
    }
}

/* reset temperature device callback */
static void reset_temp_cb(iotcs_virtual_device_handle device_handle, iotcs_typed_value argument) {
    printf("Reset temp sensor!!!\n");
    temp_sensor_reset();
    iotcs_virtual_device_start_update(device_handle);
    set_all_temp_attributes();
    iotcs_virtual_device_finish_update(device_handle);
}

/* power temperature device callback */
static void power_temp_cb(iotcs_virtual_device_handle device_handle, iotcs_typed_value argument) {
    if (argument.value.int_value) {
        printf("Power on temp sensor!!!\n");
    } else {
        printf("Power off temp sensor!!!\n");
    }
    temp_sensor_power(argument.value.int_value);
    iotcs_virtual_device_start_update((iotcs_virtual_device_handle) device_handle);
    set_all_temp_attributes();
    iotcs_virtual_device_finish_update((iotcs_virtual_device_handle) device_handle);
}

/* change humidity device max threshold callback */
static void on_change_max_threshold_cb(iotcs_virtual_device_change_event *event) {
    printf("Run max threshold callback");
    current_humidity_sensor_max_threshold = event->named_value.typed_value.value.int_value;
    printf("New sensor max threshold equals %d\n", current_humidity_sensor_max_threshold);
    iotcs_virtual_device_start_update(event->virtual_device_handle);
    set_all_humidity_attributes();
    iotcs_virtual_device_finish_update(event->virtual_device_handle);
}

/* error device callback */
static void on_error_cb(iotcs_virtual_device_error_event *event) {
    printf("Run ERROR callback with reason %s\n", event->message);
}

int main(int argc, char** argv) {
    iotcs_result rv;

    /* This is the URNs of humidity and temperature device models 
     * which supported by gateway device
     */
    const char* device_urns[] = {
        "urn:com:oracle:iot:device:temperature_sensor",
        "urn:com:oracle:iot:device:humidity_sensor",
        NULL
    };

    srand(time(NULL));

    if (argc < 3) {
        error("Too few parameters.\n"
                "\nUsage:"
                "\n\tgateway_device.out <PATH> <PASSWORD>"
                "\n\t<PATH> is a path to trusted assets store."
                "\n\t<PASSWORD> is a password for trusted assets store."
                "\n\t<CONNECTED_DEVICE_ID1>, <CONNECTED_DEVICE_ID2>... are connected device ids from trusted assets store."
                "\n\tNote: IOTCS_OS_NAME and IOTCS_OS_VERSION must be setted before using");
    }

    const char* ts_path = argv[1];
    const char* ts_password = argv[2];

    /* 
     * Initialize the library before any other calls. 
     * Initiate all subsystems like ssl, TAM, request dispatcher, 
     * async message dispatcher, etc which needed for correct library work. 
     */
    if (iotcs_init(ts_path, ts_password) != IOTCS_RESULT_OK) {
        error("Initialization failed");
    }

    /* Marks that iotcs_finalize() method should be called before exit*/
    is_initialized = IOTCS_TRUE;

    /* 
     * Activate the device, if it's not already activated.
     * Always check if the device is activated before calling activate.
     * The device model URN is passed into the activate call to tell 
     * the server the device model(s) that are supported by this
     * directly connected device
     */
    if (!iotcs_is_activated()) {
        if (iotcs_activate(device_urns) != IOTCS_RESULT_OK) {
            error("Sending activation request failed");
        }
    }

    /* get temperature device model handle */
    if (iotcs_get_device_model("urn:com:oracle:iot:device:temperature_sensor", &sensor_temp_dm_handle) != IOTCS_RESULT_OK) {
        error("iotcs_get_device_model method failed\n");
    }

    /* get humidity device model handle */
    if (iotcs_get_device_model("urn:com:oracle:iot:device:humidity_sensor", &sensor_humidity_dm_handle) != IOTCS_RESULT_OK) {
        error("iotcs_get_device_model method failed\n");
    }

    /*
     * The hardware id for the sensor instances are fabricated
     * from the gateway's endpoint id and a suffix, the combination of
     * which is likely to be unique. On subsequent runs of the
     * gateway_device_sample (with the same gateway device), the
     * endpoint id from the previously registered indirectly connected
     * device will be returned by the registerDevice call. In other
     * words, the same indirectly connected device will be used.
     */
    char hardware_id[IOTCS_CLIENT_ID_BUFFER_LENGTH];

    /* endpoints for indirect activation */
    char temp_endpoint_id[IOTCS_CLIENT_ID_BUFFER_LENGTH];
    char humidity_endpoint_id[IOTCS_CLIENT_ID_BUFFER_LENGTH];

    int written;
    iotcs_bool restricted;

    /**
     * If the user gave a hardware id for the temperature sensor,
     * then restrict the sensor to this gateway. This means that
     * the sensor cannot be connected through other gateways.
     */
    if (argc > 3) {
        written = snprintf(hardware_id, ARRAY_LEN(hardware_id), "%s", argv[3]);
        restricted = IOTCS_TRUE;
    } else {
        written = snprintf(hardware_id, ARRAY_LEN(hardware_id), "%s_sample_ts", iotcs_get_endpoint_id());
        restricted = IOTCS_FALSE;
    }

    if (written >= IOTCS_CLIENT_ID_BUFFER_LENGTH || written < 0) {
        error("snprintf function failed");
    }

    /* register an indirectly-connected temperature device */
    if (IOTCS_RESULT_OK == iotcs_register_device(restricted, hardware_id, temp_metadata, ia_temp_device_urns, temp_endpoint_id)) {
        printf("Indirect activation was completed. Temp sensor endpoint = %s\n", temp_endpoint_id);
    } else {
        error("Indirect activation was failed.\n");
    }

    /**
     * If the user gave a hardware id for the humidity sensor,
     * then restrict the sensor to this gateway. This means that
     * the sensor cannot be connected through other gateways.
     */
    if (argc > 4) {
        written = snprintf(hardware_id, ARRAY_LEN(hardware_id), "%s", argv[4]);
        restricted = IOTCS_TRUE;
    } else {
        written = snprintf(hardware_id, ARRAY_LEN(hardware_id), "%s_sample_hs", iotcs_get_endpoint_id());
        restricted = IOTCS_FALSE;
    }

    if (written >= IOTCS_CLIENT_ID_BUFFER_LENGTH || written < 0) {
        error("snprintf function failed");
    }

    /* register an indirectly-connected humidity device */
    if (IOTCS_RESULT_OK == iotcs_register_device(restricted, hardware_id, humidity_metadata, ia_humidity_device_urns, humidity_endpoint_id)) {
        printf("Indirect activation was completed. Humidity sensor endpoint = %s\n", humidity_endpoint_id);
    } else {
        error("Indirect activation was failed.\n");
    }

    /* get temperature device handle */
    if (iotcs_create_virtual_device_handle(temp_endpoint_id, sensor_temp_dm_handle, &temp_device_handle) != IOTCS_RESULT_OK) {
        error("iotcs_get_device_handle method failed\n");
    }

    /* get humidity device handle */
    if (iotcs_create_virtual_device_handle(humidity_endpoint_id, sensor_humidity_dm_handle, &humidity_device_handle) != IOTCS_RESULT_OK) {
        error("iotcs_get_device_handle method failed\n");
    }

    /* For The temperature sensor model, the min and max threshold values
     * can be written. Create a callback that will handle setting
     * the value on the temperature sensor device. 
     */
    iotcs_virtual_device_set_on_change(temp_device_handle, on_change_temp_cb);
    /* The temperatureSensor model has a 'reset' action, which
     * resets the temperature sensor to factory defaults. Create
     * a callback that will handle calling reset on the temperature
     * sensor device.
     */
    iotcs_virtual_device_set_callback(temp_device_handle, "reset", reset_temp_cb);
    /* The temperatureSensor model has a 'power' action, which
     * takes a boolean argument: true to power on the simulated device,
     * false to power off the simulated device. Create a callback that
     * will handle calling power on and power off on the temperature
     * sensor device.
     */
    iotcs_result rev = iotcs_virtual_device_set_callback(temp_device_handle, "power", power_temp_cb);
    if (rev != IOTCS_RESULT_OK) {
        printf("iotcs_virtual_device_set_callback method failed %d\n", rev);
    }
    /* For the humidity sensor model, the maxThreshold attribute can be
     * written. Create a callback for setting the maxThreshold on the
     * humidity sensor device.
     */
    iotcs_virtual_device_attribute_set_on_change(humidity_device_handle, "maxThreshold", on_change_max_threshold_cb);
    /* Create a handler for errors that may be generated when trying
     * to set values on the virtual device. The same callback is used
     * for both virtual devices.
     */
    iotcs_virtual_device_set_on_error(temp_device_handle, on_error_cb);
    iotcs_virtual_device_set_on_error(humidity_device_handle, on_error_cb);

    /* get humidity device alert handle */
    if (iotcs_virtual_device_create_alert_handle(humidity_device_handle, "urn:com:oracle:iot:device:humidity_sensor:too_humid", &humidity_sensor_alert) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_create_alert_handle method failed\n");
    }

    /* get temperature device hot alert handle */
    if (iotcs_virtual_device_create_alert_handle(temp_device_handle, "urn:com:oracle:iot:device:temperature_sensor:too_hot", &temp_sensor_hot_alert) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_create_alert_handle method failed\n");
    }

    /* get temperature device cold alert handle */
    if (iotcs_virtual_device_create_alert_handle(temp_device_handle, "urn:com:oracle:iot:device:temperature_sensor:too_cold", &temp_sensor_cold_alert) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_create_alert_handle method failed\n");
    }

    /* Get the temperature device model's default value for minThreshold */
    if (iotcs_virtual_device_get_integer(temp_device_handle, "minThreshold", &current_temp_sensor_min_threshold) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_get_integer method failed\n");
    }

    /* Get the temperature device model's default value for maxThreshold */
    if (iotcs_virtual_device_get_integer(temp_device_handle, "maxThreshold", &current_temp_sensor_max_threshold) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_get_integer method failed\n");
    }

    /* Get the humidity device model's default value for maxThreshold */
    if (iotcs_virtual_device_get_integer(humidity_device_handle, "maxThreshold", &current_humidity_sensor_max_threshold) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_get_integer method failed\n");
    }

    /* turn on temperature sensor */
    temp_sensor_power(1);

    /* Suspend background network notifications of changed attribute values */
    printf("Start update temp device handle\n");
    iotcs_virtual_device_start_update(temp_device_handle);
    /* Set all temperature attributes */
    set_all_temp_attributes();
    /* Resume background network notifications of changed attribute values and 
     * send all changes to server */
    printf("Finish update temp device handle\n");
    iotcs_virtual_device_finish_update(temp_device_handle);

    /* Suspend background network notifications of changed attribute values */
    printf("Start update humidity device handle\n");
    iotcs_virtual_device_start_update(humidity_device_handle);
    /* Set all humidity attributes */
    set_all_humidity_attributes();
    /* Resume background network notifications of changed attribute values and 
     * send all changes to server */
    printf("Finish update humidity device handle\n");
    iotcs_virtual_device_finish_update(humidity_device_handle);

    print_all_temp_attributes();
    print_all_humidity_attributes();

    /* Set handler for interrupt signal */
    signal(SIGINT, int_handler);

    /* 
     * Emulate device work. 
     * Sensors changed values, then device send data message with new values 
     * to the server. If sensors values is out of threshold then 
     * send alert message.
     * Async message dispatcher send messages to the server and handle 
     * message delivery and errors during send. 
     * Request dispatcher handle incoming requests from the server using 
     * custom request handlers.
     */
    while (keep_running) {
        printf("Sleep 5 seconds!\n");
        sleep(5);

        /* Emulate humidity sensor work */
        humidity_sensor_work();
        /* Emulate temperature sensor work */
        temp_sensor_work();
#if (USE_POLICY)
        /* offer humidity device value */
        iotcs_virtual_device_start_update(humidity_device_handle);
        rv = iotcs_virtual_device_offer_integer(humidity_device_handle,
                "humidity", current_humidity_sensor_value);
        iotcs_virtual_device_finish_update(humidity_device_handle);
        if (rv != IOTCS_RESULT_OK) {
            error("iotcs_virtual_device_offer_integer method failed\n");
        }
#else
        /* set humidity device value */
        rv = iotcs_virtual_device_set_integer(humidity_device_handle,
                "humidity", current_humidity_sensor_value);
        if (rv != IOTCS_RESULT_OK) {
            error("iotcs_virtual_device_set_integer method failed\n");
        }
#endif

        /* get humidity device value */
        rv = iotcs_virtual_device_get_integer(humidity_device_handle,
                "humidity", &huminity_device_current_value);

        if (rv != IOTCS_RESULT_OK) {
            error("iotcs_virtual_device_get_integer method failed\n");
        }
        printf("Current sensor value is %d\n", huminity_device_current_value);

#if (!USE_POLICY)
        /* Send alert if current value more than max threshold */
        if (current_humidity_sensor_value >= current_humidity_sensor_max_threshold) {
            /* set humidity alert value */
            iotcs_alert_set_integer(humidity_sensor_alert, "humidity", current_humidity_sensor_value);
            /* async alert send */
            rv = iotcs_alert_raise(humidity_sensor_alert);

            if (rv != IOTCS_RESULT_OK) {
                error("iotcs_alert_raise method failed\n");
            }
        }
#endif

        if (temp_power == IOTCS_FALSE) {
            temp_was_off = IOTCS_TRUE;
            continue;
        }

        if (temp_was_off == IOTCS_TRUE) {
            temp_was_off = IOTCS_FALSE;
            printf("Power back on, update all attributes\n");
            /* Suspend background network notifications of changed attribute values */
            iotcs_virtual_device_start_update(temp_device_handle);
            /* Set all temperature attributes */
            set_all_temp_attributes();
            iotcs_virtual_device_finish_update(temp_device_handle);
            /* Resume background network notifications of changed attribute values and 
             * send all changes to server */
            continue;
        }

        /* Suspend background network notifications of changed attribute values */
        iotcs_virtual_device_start_update(temp_device_handle);
#if (USE_POLICY)
        /* Offer temperature current value */
        rv = iotcs_virtual_device_offer_float(temp_device_handle,
                "temp", current_temp_sensor_value);
        if (rv != IOTCS_RESULT_OK) {
            error("iotcs_virtual_device_offer_float method failed\n");
        }
        printf("Device temp value = %g\n", temp_device_current_value);
        /* Offer temperature min value */
        rv = iotcs_virtual_device_offer_float(temp_device_handle,
                "minTemp", current_temp_sensor_min_value);
        if (rv != IOTCS_RESULT_OK) {
            error("iotcs_virtual_device_offer_float method failed\n");
        }
        printf("Device temp min value = %g\n", temp_device_current_value);
        /* Offer temperature max value */
        rv = iotcs_virtual_device_offer_float(temp_device_handle,
                "maxTemp", current_temp_sensor_max_value);

        if (rv != IOTCS_RESULT_OK) {
            error("iotcs_virtual_device_offer_float method failed\n");
        }
#else
        /* Set temperature current value */
        rv = iotcs_virtual_device_set_float(temp_device_handle,
                "temp", current_temp_sensor_value);
        if (rv != IOTCS_RESULT_OK) {
            error("iotcs_virtual_device_set_float method failed\n");
        }
        printf("Device temp value = %g\n", temp_device_current_value);
        /* Set temperature min value */
        rv = iotcs_virtual_device_set_float(temp_device_handle,
                "minTemp", current_temp_sensor_min_value);
        if (rv != IOTCS_RESULT_OK) {
            error("iotcs_virtual_device_set_float method failed\n");
        }
        printf("Device temp min value = %g\n", temp_device_current_value);
        /* Set temperature max value */
        rv = iotcs_virtual_device_set_float(temp_device_handle,
                "maxTemp", current_temp_sensor_max_value);

        if (rv != IOTCS_RESULT_OK) {
            error("iotcs_virtual_device_set_float method failed\n");
        }
#endif

        printf("Device temp max value = %g\n", temp_device_current_value);
        
        /* Resume background network notifications of changed attribute values and 
         * send all changes to server */
        iotcs_virtual_device_finish_update(temp_device_handle);

#if (!USE_POLICY)
        /* Send alert if current value less than min threshold */
        if (current_temp_sensor_value <= current_temp_sensor_min_threshold) {
            printf("Send cold alert\n");
            /* set temperature alert values */
            iotcs_alert_set_float(temp_sensor_cold_alert, "temp", current_temp_sensor_value);
            iotcs_alert_set_string(temp_sensor_cold_alert, "unit", current_temp_sensor_unit);
            iotcs_alert_set_integer(temp_sensor_cold_alert, "minThreshold", current_temp_sensor_min_threshold);
            /* async alert send */
            rv = iotcs_alert_raise(temp_sensor_cold_alert);

            if (rv != IOTCS_RESULT_OK) {
                error("iotcs_alert_raise method failed\n");
            }
            /* Send alert if current value more than max threshold */
        } else if (current_temp_sensor_value >= current_temp_sensor_max_threshold) {
            printf("Send hot alert\n");
            /* set temperature alert values */
            iotcs_alert_set_float(temp_sensor_hot_alert, "temp", current_temp_sensor_value);
            iotcs_alert_set_string(temp_sensor_hot_alert, "unit", current_temp_sensor_unit);
            iotcs_alert_set_integer(temp_sensor_hot_alert, "maxThreshold", current_temp_sensor_max_threshold);
            /* async alert send */
            rv = iotcs_alert_raise(temp_sensor_hot_alert);

            if (rv != IOTCS_RESULT_OK) {
                error("iotcs_alert_raise method failed\n");
            }
        }
#endif
        print_all_temp_attributes();
    } // end while

    /* Clean up */
    finalize_library();

    printf("OK\n");
    return EXIT_SUCCESS;
}
