Use feature 'required-binding'
[apps/hvac.git] / binding / hvac-demo-binding.c
index 4913380..a8993a1 100644 (file)
@@ -1,7 +1,9 @@
 /*
  * Copyright (C) 2015, 2016 "IoT.bzh"
+ * Copyright (C) 2016 Konsulko Group
  * Author "Romain Forlot"
  * Author "Jose Bolo"
+ * Author "Scott Murray <scott.murray@konsulko.com>"
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,7 +19,9 @@
  */
 #define _GNU_SOURCE
 
+#include <stdio.h>
 #include <string.h>
+#include <stdbool.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <net/if.h>
 #include <linux/can.h>
 #include <math.h>
-
 #include <json-c/json.h>
 
+#define AFB_BINDING_VERSION 2
 #include <afb/afb-binding.h>
-#include <afb/afb-service-itf.h>
 
-// Uncomment this line to pass into can simulation mode
-//#define SIMULATE_HVAC
+#define RED "/sys/class/leds/blinkm-3-9-red/brightness"
+#define GREEN "/sys/class/leds/blinkm-3-9-green/brightness"
+#define BLUE "/sys/class/leds/blinkm-3-9-blue/brightness"
 
 #define CAN_DEV "vcan0"
 
+#ifndef NULL
+#define NULL 0
+#endif
+
 static const struct afb_binding_interface *interface;
+static struct afb_event event;
 
 /*****************************************************************************************/
 /*****************************************************************************************/
@@ -89,32 +98,65 @@ static struct {
        { "LeftTemperature", 21 },
        { "RightTemperature", 21 },
        { "Temperature", 21 },
-       { "FanSpeed", 0 }
+       { "FanSpeed", 0 },
+       { "ACEnabled", 0 },
+       { "LeftLed", 15 },
+       { "RightLed", 15 }
+};
+
+// Holds RGB combinations for each temperature
+static struct {
+       const int temperature;
+       const int rgb[3];
+} degree_colours[] = {
+       {15, {0, 0, 229} },
+       {16, {22, 0, 204} },
+       {17, {34, 0, 189} },
+       {18, {46, 0, 175} },
+       {19, {58, 0, 186} },
+       {20, {70, 0, 146} },
+       {21, {82, 0, 131} },
+       {22, {104, 0, 116} },
+       {23, {116, 0, 102} },
+       {24, {128, 0, 87} },
+       {25, {140, 0, 73} },
+       {26, {152, 0, 58} },
+       {27, {164, 0, 43} },
+       {28, {176, 0, 29} },
+       {29, {188, 0, 14} },
+       {30, {201, 0, 5} }
 };
 
 struct can_handler {
        int socket;
+       bool simulation;
+       char *send_msg;
        struct sockaddr_can txAddress;
 };
 
-static struct can_handler can_handler = { .socket = -1 };
+struct led_paths {
+       char *red;
+       char *green;
+       char *blue;
+}led_paths_values = {
+       .red = RED,
+       .green = GREEN,
+       .blue = BLUE
+};
 
-static int open_can_dev()
+static struct can_handler can_handler = { .socket = -1, .simulation = false, .send_msg = "SENDING CAN FRAME"};
+
+static int open_can_dev_helper()
 {
-#if defined(SIMULATE_HVAC)
-       DEBUG(interface, "Defining can handler socket to 0 and return");
-       can_handler.socket = 0;
-       return 0;
-#else
        struct ifreq ifr;
 
-       DEBUG(interface, "CAN Handler socket : %d", can_handler.socket);
+       AFB_DEBUG("CAN Handler socket : %d", can_handler.socket);
        close(can_handler.socket);
 
        can_handler.socket = socket(PF_CAN, SOCK_RAW, CAN_RAW);
        if (can_handler.socket < 0)
        {
-               ERROR(interface, "socket could not be created");
+               AFB_ERROR("socket could not be created");
        }
        else
        {
@@ -122,7 +164,7 @@ static int open_can_dev()
                strcpy(ifr.ifr_name, CAN_DEV);
                if(ioctl(can_handler.socket, SIOCGIFINDEX, &ifr) < 0)
                {
-                       ERROR(interface, "ioctl failed");
+                       AFB_ERROR("ioctl failed");
                }
                else
                {
@@ -132,7 +174,7 @@ static int open_can_dev()
                        // And bind it to txAddress
                        if (bind(can_handler.socket, (struct sockaddr *)&can_handler.txAddress, sizeof(can_handler.txAddress)) < 0)
                        {
-                               ERROR(interface, "bind failed");
+                               AFB_ERROR("bind failed");
                        }
                        else {
                                return 0;
@@ -142,13 +184,26 @@ static int open_can_dev()
                can_handler.socket = -1;
        }
        return -1;
-#endif
+}
+
+static int open_can_dev()
+{
+       int rc = retry(open_can_dev_helper);
+       if(rc < 0)
+       {
+               AFB_ERROR("Open of interface %s failed. Falling back to simulation mode", CAN_DEV);
+               can_handler.socket = 0;
+               can_handler.simulation = true;
+               can_handler.send_msg = "FAKE CAN FRAME";
+               rc = 0;
+       }
+       return rc;
 }
 
 // Get original get temperature function from cpp hvacplugin code
 static uint8_t to_can_temp(uint8_t value)
 {
-       int result = ((0xF0 - 0x10) / 15) * value - 16;
+       int result = ((0xF0 - 0x10) / 15) * (value - 15) + 0x10;
        if (result < 0x10)
                result = 0x10;
        if (result > 0xF0)
@@ -167,6 +222,16 @@ static uint8_t read_temp_right_zone()
        return hvac_values[1].value;
 }
 
+static uint8_t read_temp_left_led()
+{
+       return hvac_values[5].value;
+}
+
+static uint8_t read_temp_right_led()
+{
+       return hvac_values[6].value;
+}
+
 static uint8_t read_temp()
 {
        return (uint8_t)(((int)read_temp_left_zone() + (int)read_temp_right_zone()) >> 1);
@@ -174,7 +239,240 @@ static uint8_t read_temp()
 
 static uint8_t read_fanspeed()
 {
-       return hvac_values[3].value ^ 0xFF;
+       return hvac_values[3].value;
+}
+
+/* 
+ * @param: None
+ *
+ * @brief: Parse JSON configuration file for blinkm path
+ */
+static int parse_config()
+{
+       struct json_object *ledtemp = NULL;
+       struct json_object *jobj = json_object_from_file("/etc/hvac.json");
+
+       // Check if .json file has been parsed as a json_object
+       if (!jobj) {
+               AFB_ERROR("JSON file could not be opened!\n");
+               return 1;
+       }
+
+       // Check if json_object with key "ledtemp" has been found in .json file
+       if (!json_object_object_get_ex(jobj, "ledtemp", &ledtemp)){
+               AFB_ERROR("Key not found!\n");
+               return 1;
+       }
+
+       // Extract directory paths for each LED colour
+       json_object_object_foreach(ledtemp, key, value) {
+               if (strcmp(key, "red") == 0) {
+                       led_paths_values.red = json_object_get_string(value);
+               } else if (strcmp(key, "green") == 0) {
+                       led_paths_values.green = json_object_get_string(value);
+               } else if (strcmp(key, "blue") == 0) {
+                       led_paths_values.blue = json_object_get_string(value);
+               }
+       }
+
+       // return 0 if all succeeded
+       return 0;
+
+}
+
+/* 
+ * @brief Writing to LED for both temperature sliders
+ */
+static int temp_write_led()
+{
+
+       int rc = -1, red_value, green_value, blue_value;
+       int right_temp;
+       int left_temp;
+
+       left_temp = read_temp_left_led() - 15;
+       right_temp = read_temp_right_led() - 15;
+
+       // Calculates average colour value taken from the temperature toggles
+       red_value = (degree_colours[left_temp].rgb[0] + degree_colours[right_temp].rgb[0]) / 2;
+       green_value = (degree_colours[left_temp].rgb[1] + degree_colours[right_temp].rgb[1]) / 2;
+       blue_value = (degree_colours[left_temp].rgb[2] + degree_colours[right_temp].rgb[2]) / 2;
+       
+       // default path: /sys/class/leds/blinkm-3-9-red/brightness
+       FILE* r = fopen(led_paths_values.red, "w");
+       if(r){
+               fprintf(r, "%d", red_value);
+               fclose(r);
+       } else {
+               AFB_ERROR("Unable to open red LED path!\n");
+               return -1;
+       }
+
+       // default path: /sys/class/leds/blinkm-3-9-green/brightness
+       FILE* g = fopen(led_paths_values.green, "w");
+       if(g){
+               fprintf(g, "%d", green_value);
+               fclose(g);
+       } else {
+               AFB_ERROR("Unable to open green LED path!\n");
+               return -1;
+       }
+
+       // default path: /sys/class/leds/blinkm-3-9-blue/brightness
+       FILE* b = fopen(led_paths_values.blue, "w");
+       if(b){
+               fprintf(b, "%d", blue_value);
+               fclose(b);
+       } else {
+               AFB_ERROR("Unable to open blue LED path!\n");
+               return -1;
+       }
+
+       return 0;
+}
+
+/*
+ * @brief Get temperature of left toggle in HVAC system
+ *
+ * @param struct afb_req : an afb request structure
+ *
+ */
+static void temp_left_zone_led(struct afb_req request)
+{
+       int i = 5, rc, x, changed;
+       double d;
+       struct json_object *query, *val;
+       uint8_t values[sizeof hvac_values / sizeof *hvac_values];
+       uint8_t saves[sizeof hvac_values / sizeof *hvac_values];
+
+       AFB_WARNING("In temp_left_zone_led.");
+
+       query = afb_req_json(request);
+
+       /* records initial values */
+       AFB_WARNING("Records initial values");
+       values[i] = saves[i] = hvac_values[i].value;
+
+
+       if (json_object_object_get_ex(query, hvac_values[i].name, &val))
+       {
+               AFB_WARNING("Value of values[i] = %d", values[i]);
+               AFB_WARNING("We got it. Tests if it is an int or double.");
+               if (json_object_is_type(val, json_type_int)) {
+                       x = json_object_get_int(val);
+                       AFB_WARNING("We get an int: %d",x);
+               }
+               else if (json_object_is_type(val, json_type_double)) {
+                       d = json_object_get_double(val);
+                       x = (int)round(d);
+                       AFB_WARNING("We get a double: %f => %d",d,x);
+               }
+               else {
+                       afb_req_fail_f(request, "bad-request",
+                               "argument '%s' isn't integer or double", hvac_values[i].name);
+                       return;
+               }
+               if (x < 0 || x > 255)
+               {
+                       afb_req_fail_f(request, "bad-request",
+                               "argument '%s' is out of bounds", hvac_values[i].name);
+                       return;
+               }
+               if (values[i] != x) {
+                       values[i] = (uint8_t)x;
+                       changed = 1;
+                       AFB_WARNING("%s changed to %d", hvac_values[i].name,x);
+               }
+       }
+       else {
+               AFB_WARNING("%s not found in query!",hvac_values[i].name);
+       }
+
+
+       if (changed) {
+               hvac_values[i].value = values[i]; // update structure at line 102
+               AFB_WARNING("WRITE_LED: value: %d", hvac_values[i].value);
+               rc = temp_write_led();
+               if (rc >= 0)
+                       afb_req_success(request, NULL, NULL);
+               else if (retry(temp_write_led)) {
+                       /* restore initial values */
+                       hvac_values[i].value = saves[i];
+                       afb_req_fail(request, "error", "I2C error");
+               }
+       }
+}
+
+/*
+ * @brief Get temperature of right toggle in HVAC system
+ *
+ * @param struct afb_req : an afb request structure
+ *
+ */
+static void temp_right_zone_led(struct afb_req request)
+{
+       int i = 6, rc, x, changed;
+       double d;
+       struct json_object *query, *val;
+       uint8_t values[sizeof hvac_values / sizeof *hvac_values];
+       uint8_t saves[sizeof hvac_values / sizeof *hvac_values];
+
+       AFB_WARNING("In temp_right_zone_led.");
+
+       query = afb_req_json(request);
+
+       /* records initial values */
+       AFB_WARNING("Records initial values");
+       values[i] = saves[i] = hvac_values[i].value;
+
+
+       if (json_object_object_get_ex(query, hvac_values[i].name, &val))
+       {
+               AFB_WARNING("Value of values[i] = %d", values[i]);
+               AFB_WARNING("We got it. Tests if it is an int or double.");
+               if (json_object_is_type(val, json_type_int)) {
+                       x = json_object_get_int(val);
+                       AFB_WARNING("We get an int: %d",x);
+               }
+               else if (json_object_is_type(val, json_type_double)) {
+                       d = json_object_get_double(val);
+                       x = (int)round(d);
+                       AFB_WARNING("We get a double: %f => %d",d,x);
+               }
+               else {
+                       afb_req_fail_f(request, "bad-request",
+                               "argument '%s' isn't integer or double", hvac_values[i].name);
+                       return;
+               }
+               if (x < 0 || x > 255)
+               {
+                       afb_req_fail_f(request, "bad-request",
+                               "argument '%s' is out of bounds", hvac_values[i].name);
+                       return;
+               }
+               if (values[i] != x) {
+                       values[i] = (uint8_t)x;
+                       changed = 1;
+                       AFB_WARNING("%s changed to %d", hvac_values[i].name,x);
+               }
+       }
+       else {
+               AFB_WARNING("%s not found in query!", hvac_values[i].name);
+       }
+
+
+       if (changed) {
+               hvac_values[i].value = values[i]; // update structure at line 102
+               AFB_WARNING("WRITE_LED: value: %d", hvac_values[i].value);
+               rc = temp_write_led();
+               if (rc >= 0)
+                       afb_req_success(request, NULL, NULL);
+               else if (retry(temp_write_led)) {
+                       /* restore initial values */
+                       hvac_values[i].value = saves[i];
+                       afb_req_fail(request, "error", "I2C error");
+               }
+       }
 }
 
 static int write_can()
@@ -197,30 +495,26 @@ static int write_can()
                txCanFrame.data[6] = 0;
                txCanFrame.data[7] = 0;
 
-               DEBUG(interface, "%s: %d %d [%02x %02x %02x %02x %02x %02x %02x %02x]\n",
-#if defined(SIMULATE_HVAC)
-                       "FAKE CAN FRAME",
-#else
-                       "SENDING CAN FRAME",
-#endif
+               AFB_DEBUG("%s: %d %d [%02x %02x %02x %02x %02x %02x %02x %02x]\n",
+                       can_handler.send_msg,
                        txCanFrame.can_id, txCanFrame.can_dlc,
                        txCanFrame.data[0], txCanFrame.data[1], txCanFrame.data[2], txCanFrame.data[3],
                        txCanFrame.data[4], txCanFrame.data[5], txCanFrame.data[6], txCanFrame.data[7]);
 
-#if !defined(SIMULATE_HVAC)
-               rc = sendto(can_handler.socket, &txCanFrame, sizeof(struct can_frame), 0,
-                       (struct sockaddr*)&can_handler.txAddress, sizeof(can_handler.txAddress));
-               if (rc < 0)
+               if(!can_handler.simulation)
                {
-                       ERROR(interface, "Sending can frame failed. Attempt to reopen can device socket.");
-                       retry(open_can_dev);
+                       rc = (int)sendto(can_handler.socket, &txCanFrame, sizeof(struct can_frame), 0,
+                               (struct sockaddr*)&can_handler.txAddress, sizeof(can_handler.txAddress));
+                       if (rc < 0)
+                       {
+                               AFB_ERROR("Sending CAN frame failed.");
+                       }
                }
-#endif
        }
        else
        {
-               ERROR(interface, "socket not initialized. Attempt to reopen can device socket.");
-               retry(open_can_dev);
+               AFB_ERROR("socket not initialized. Attempt to reopen can device socket.");
+               open_can_dev();
        }
        return rc;
 }
@@ -294,7 +588,7 @@ static void get_temp_left_zone(struct afb_req request)
  */
 static void get(struct afb_req request)
 {
-       DEBUG(interface, "Getting all values");
+       AFB_DEBUG("Getting all values");
        json_object *ret_json;
 
        ret_json = json_object_new_object();
@@ -320,7 +614,7 @@ static void set(struct afb_req request)
        uint8_t saves[sizeof hvac_values / sizeof *hvac_values];
 
        /* records initial values */
-       DEBUG(interface, "Records initial values");
+       AFB_DEBUG("Records initial values");
        i = (int)(sizeof hvac_values / sizeof *hvac_values);
        while (i) {
                i--;
@@ -331,22 +625,22 @@ static void set(struct afb_req request)
        query = afb_req_json(request);
        changed = 0;
        i = (int)(sizeof hvac_values / sizeof *hvac_values);
-       DEBUG(interface, "Looping for args. i: %d", i);
+       AFB_DEBUG("Looping for args. i: %d", i);
        while (i)
        {
                i--;
-               DEBUG(interface, "Searching... query: %s, i: %d, comp: %s", json_object_to_json_string(query), i, hvac_values[i].name);
+               AFB_DEBUG("Searching... query: %s, i: %d, comp: %s", json_object_to_json_string(query), i, hvac_values[i].name);
                if (json_object_object_get_ex(query, hvac_values[i].name, &val))
                {
-                       DEBUG(interface, "We got it. Tests if it is an int or double.");
+                       AFB_DEBUG("We got it. Tests if it is an int or double.");
                        if (json_object_is_type(val, json_type_int)) {
                                x = json_object_get_int(val);
-                               DEBUG(interface, "We get an int: %d",x);
+                               AFB_DEBUG("We get an int: %d",x);
                        }
                        else if (json_object_is_type(val, json_type_double)) {
                                d = json_object_get_double(val);
                                x = (int)round(d);
-                               DEBUG(interface, "We get a double: %f => %d",d,x);
+                               AFB_DEBUG("We get a double: %f => %d",d,x);
                        }
                        else {
                                afb_req_fail_f(request, "bad-request",
@@ -362,16 +656,16 @@ static void set(struct afb_req request)
                        if (values[i] != x) {
                                values[i] = (uint8_t)x;
                                changed = 1;
-                               DEBUG(interface,"%s changed to %d",hvac_values[i].name,x);
+                               AFB_DEBUG("%s changed to %d",hvac_values[i].name,x);
                        }
                }
                else {
-                       DEBUG(interface, "%s not found in query!",hvac_values[i].name);
+                       AFB_DEBUG("%s not found in query!",hvac_values[i].name);
                }
        }
 
        /* attemps to set new values */
-       DEBUG(interface, "Diff: %d", changed);
+       AFB_DEBUG("Diff: %d", changed);
        if (changed)
        {
                i = (int)(sizeof hvac_values / sizeof *hvac_values);
@@ -397,33 +691,108 @@ static void set(struct afb_req request)
        }
 }
 
-// TODO: Have to change session management flag to AFB_SESSION_CHECK to use token auth
-static const struct afb_verb_desc_v1 verbs[]= {
-       {"get_temp_left_zone"    , AFB_SESSION_NONE, get_temp_left_zone , "Get the left zone temperature"},
-       {"get_temp_right_zone"   , AFB_SESSION_NONE, get_temp_right_zone        , "Get the right zone temperature"},
-       {"get_fanspeed"  , AFB_SESSION_NONE, get_fanspeed       , "Read fan speed"},
-       {"get"   , AFB_SESSION_NONE, get        , "Read all values"},
-       {"set"   , AFB_SESSION_NONE, set        , "Set a HVAC component value"},
-       {NULL}
-};
-
-static const struct afb_binding binding_desc = {
-       .type = AFB_BINDING_VERSION_1,
-       .v1 = {
-               .info = "hvac service",
-               .prefix = "hvac",
-               .verbs = verbs
-       }
-};
-
-const struct afb_binding *afbBindingV1Register (const struct afb_binding_interface *itf)
+int bindingServicePreInit(struct afb_service service)
 {
-       interface = itf;
+       return open_can_dev();
+}
 
-       return &binding_desc;
+int bindingServiceInit(struct afb_service service)
+{
+       event = afb_daemon_make_event("language");
+       if(parse_config() != 0)
+               AFB_WARNING("Default values are being used!\n");
+       if(afb_daemon_require_api("identity", 1))
+               return -1;
+       return afb_service_call_sync("identity", "subscribe", NULL, NULL);
 }
 
-int afbBindingV1ServiceInit(struct afb_service service)
+void onEvent(const char *event_name, struct json_object *object)
 {
-       return retry(open_can_dev);
+       json_object *args, *language = json_object_new_object();
+       json_object *id_evt_name, *current_identity;
+
+       AFB_NOTICE("Event '%s' received: %s", event_name,
+               json_object_to_json_string_ext(object, JSON_C_TO_STRING_PRETTY));
+
+       if (json_object_object_get_ex(object, "eventName", &id_evt_name) &&
+         !strcmp(json_object_get_string(id_evt_name), "login") &&
+         !afb_service_call_sync("identity", "get", NULL, &current_identity)) {
+               json_object *response;
+               if (! json_object_object_get_ex(current_identity, "response", &response) || ! json_object_object_get_ex(response, "graphPreferredLanguage", &language)) {
+                       language = json_object_new_string("en_US");
+               }
+               afb_event_broadcast(event, language);
+       }
 }
+
+// TODO: Have to change session management flag to AFB_SESSION_CHECK to use token auth
+static const struct afb_verb_v2 _afb_verbs_v2_hvac[]= {
+       {
+               .verb = "get_temp_left_zone",
+               .callback = get_temp_left_zone,
+               .auth = NULL,
+               .info = "Get the left zone temperature",
+               .session = AFB_SESSION_NONE_V2
+       },
+       {
+               .verb = "get_temp_right_zone",
+               .callback = get_temp_right_zone,
+               .auth = NULL,
+               .info = "Get the right zone temperature",
+               .session = AFB_SESSION_NONE_V2
+       },
+       {
+               .verb = "get_fanspeed",
+               .callback = get_fanspeed,
+               .auth = NULL,
+               .info = "Read fan speed",
+               .session = AFB_SESSION_NONE_V2
+       },
+       {
+               .verb = "get",
+               .callback = get,
+               .auth = NULL,
+               .info = "Read all speed",
+               .session = AFB_SESSION_NONE_V2
+       },
+       {
+               .verb = "set",
+               .callback = set,
+               .auth = NULL,
+               .info = "Set a HVAC component value",
+               .session = AFB_SESSION_NONE_V2
+       },
+       {
+               .verb = "temp_left_zone_led",
+               .callback = temp_left_zone_led,
+               .auth = NULL,
+               .info = "Turn on LED on left temperature zone",
+               .session = AFB_SESSION_NONE_V2
+       },
+       {
+               .verb = "temp_right_zone_led",
+               .callback = temp_right_zone_led,
+               .auth = NULL,
+               .info = "Turn on LED on left temperature zone",
+               .session = AFB_SESSION_NONE_V2
+
+       },
+       {
+               .verb = NULL,
+               .callback = NULL,
+               .auth = NULL,
+               .info = NULL,
+               .session = 0
+       }
+};
+
+const struct afb_binding_v2 afbBindingV2 = {
+    .api = "hvac",
+    .specification = NULL,
+    .info = "HVAC service",
+    .verbs = _afb_verbs_v2_hvac,
+    .preinit = bindingServicePreInit,
+    .init = bindingServiceInit,
+    .onevent = onEvent,
+    .noconcurrency = 0
+};
\ No newline at end of file