
/*
 * 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 directly connected device which is capable of communicating 
 * directly with Oracle IoT Cloud Service. This sample illustrates  
 * C code for sending data to the cloud service, receiving data from the cloud 
 * service and does not fully explore the Client Library API.
 * 
 * The sample uses the virtualization API. It presents a simple humidity sensor device.
 * 
 * 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" device model is used. 
 * This device model 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"

#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))

/*
 *                           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;

/* other variables */

/* used as sending loop condition */
static volatile int keep_running = 1;
/* Current humidity device value */
static int device_current_value;
/* Current humidity device max threshold value */
static int device_current_max_threshold;
/* humidity device model handle */
static iotcs_device_model_handle sensor_dm_handle = NULL;
/* humidity alert handle */
static iotcs_alert_handle sensor_alert = NULL;
/* humidity device handle */
static iotcs_virtual_device_handle device_handle = NULL;
/* means that iotcs_init() method was successful*/
static iotcs_bool is_initialized = IOTCS_FALSE;

/*
 *                              Functions
 */

static void finalize_library() {
#if (!USE_POLICY)
    /* free alert handle */
    if (sensor_alert) {
        iotcs_virtual_device_free_alert_handle(sensor_alert);
        sensor_alert = NULL;
    }
#endif
    /* free device handle */
    if (device_handle) {
        iotcs_free_virtual_device_handle(device_handle);
        device_handle = NULL;
    }
    /* free device model handle */
    if (sensor_dm_handle) {
        iotcs_free_device_model(sensor_dm_handle);
        sensor_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);
}

/* global change device callback */
static void on_change_global_cb(iotcs_virtual_device_change_event *event) {
    printf("Run global on change callback\n");
}

/* change device max threshold callback */
static void on_change_max_threshold_cb(iotcs_virtual_device_change_event *event) {
    printf("Run max threshold callback");
    iotcs_result rv;
    // get new max threshold value from event
    current_humidity_sensor_max_threshold = event->named_value.typed_value.value.int_value;

    // set new max threshold value
    rv = iotcs_virtual_device_set_integer(event->virtual_device_handle, "maxThreshold", current_humidity_sensor_max_threshold);

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

    // get new max threshold value from device
    if (iotcs_virtual_device_get_integer(event->virtual_device_handle, "maxThreshold", &device_current_max_threshold) != IOTCS_RESULT_OK) {
        printf("iotcs_virtual_device_get_integer method failed\n");
        return;
    }
    printf("Sensor max threshold set to %d\n", device_current_max_threshold);
}

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

/* attributes error device callback */
static void on_error_attribute_cb(iotcs_virtual_device_error_event *event) {
    printf("Run on ERROR \"%s\" attribute callback with reason %s\n", event->named_value.name, event->message);
}

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

/* 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 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 = MAX(HUMIDITY_VMIN, MIN(HUMIDITY_VMAX,
            (current_humidity_sensor_max_threshold / 2) + add_value + fabs(10 * sin(humidity_angle))));
    humidity_angle = (humidity_angle + 20) % 360;
}

int main(int argc, char** argv) {
    /* This is the URN of our humidity device model. */
    const char* device_urns[] = {
        "urn:com:oracle:iot:device:humidity_sensor",
        NULL
    };

    srand(time(NULL));

    iotcs_result rv;

    if (argc < 3) {
        error("Too few parameters.\n"
                "\nUsage:"
                "\n\tdirectly_connected_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\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) {
        }
    }

    /* get device model handle */
    if (iotcs_get_device_model("urn:com:oracle:iot:device:humidity_sensor", &sensor_dm_handle) != IOTCS_RESULT_OK) {
    }

    /* get device handle */
    if (iotcs_create_virtual_device_handle(iotcs_get_endpoint_id(), sensor_dm_handle, &device_handle) != IOTCS_RESULT_OK) {
    }

    /* get alert handle */
    if (iotcs_virtual_device_create_alert_handle(device_handle, "urn:com:oracle:iot:device:humidity_sensor:too_humid", &sensor_alert) != IOTCS_RESULT_OK) {
    }

    /* set global callbacks for device */
    iotcs_virtual_device_set_on_change(device_handle, on_change_global_cb);
    /* Create a handler for errors that may be generated when trying
     * to set values on the virtual device.
     */
    iotcs_virtual_device_set_on_error(device_handle, on_error_cb);
    /* 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(device_handle, "maxThreshold", on_change_max_threshold_cb);
    /* Create a handler for errors that may be generated when trying
     * to set maxThreshold value on the virtual device.
     */
    iotcs_virtual_device_attribute_set_on_error(device_handle, "maxThreshold", on_error_attribute_cb);

    /* get current humidity device max threshold this will be the
     * device model's default value, before starting the sensor_work */
    if (iotcs_virtual_device_get_integer(device_handle, "maxThreshold", &current_humidity_sensor_max_threshold) != IOTCS_RESULT_OK) {
    }

    /* Emulate humidity sensor work */
    sensor_work();

    /* Suspend background network notifications of changed attribute values */
    iotcs_virtual_device_start_update(device_handle);
    printf("Start update device handle\n");
    /* set humidity device value */
    rv = iotcs_virtual_device_set_integer(device_handle, "humidity", current_humidity_sensor_value);
    if (rv != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_set_integer method failed\n");
    }


    /* get current humidity device value */
    if (iotcs_virtual_device_get_integer(device_handle, "humidity", &device_current_value) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_get_integer method failed\n");
    }
    printf("New sensor value is %d\n", device_current_value);

    /* get default humidity device max threshold
     * No need to update this value on start up */
    if (iotcs_virtual_device_get_integer(device_handle, "maxThreshold", &device_current_max_threshold) != IOTCS_RESULT_OK) {
        error("iotcs_virtual_device_get_integer method failed\n");
    }
    printf("The default max sensor threshold value is %d\n", device_current_max_threshold);

    /* Resume background network notifications of changed attribute values and 
     * send all changes to server */
    iotcs_virtual_device_finish_update(device_handle);
    printf("Finish update device handle\n");

    /* 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 */
        sensor_work();

#if (USE_POLICY)
        /* offer humidity device value */
        iotcs_virtual_device_start_update(device_handle);
        rv = iotcs_virtual_device_offer_integer(device_handle, "humidity", current_humidity_sensor_value);
        if (rv != IOTCS_RESULT_OK) {
            error("iotcs_virtual_device_offer_integer method failed\n");
        }
        iotcs_virtual_device_finish_update(device_handle);
#else
        /* set humidity device value */
        rv = iotcs_virtual_device_set_integer(device_handle, "humidity", current_humidity_sensor_value);
        if (rv != IOTCS_RESULT_OK) {
            error("iotcs_virtual_device_set_integer method failed\n");
        }
        /* 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(sensor_alert, "humidity", current_humidity_sensor_value);
            /* async alert send */
            rv = iotcs_alert_raise(sensor_alert);

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

        /* get current humidity device value */
        if (iotcs_virtual_device_get_integer(device_handle, "humidity", &device_current_value) != IOTCS_RESULT_OK) {
            error("iotcs_virtual_device_get_integer method failed\n");
        }
        printf("Current sensor value is %d\n", device_current_value);
        printf("Current max sensor threshold value is %d\n", current_humidity_sensor_max_threshold);
    } // end while

    finalize_library();

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