Languages changing depending on identity logged in
[apps/hvac.git] / binding / hvac-demo-binding.c
1 /*
2  * Copyright (C) 2015, 2016 "IoT.bzh"
3  * Copyright (C) 2016 Konsulko Group
4  * Author "Romain Forlot"
5  * Author "Jose Bolo"
6  * Author "Scott Murray <scott.murray@konsulko.com>"
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *   http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 #define _GNU_SOURCE
21
22 #include <string.h>
23 #include <stdbool.h>
24 #include <unistd.h>
25 #include <sys/types.h>
26 #include <sys/socket.h>
27 #include <sys/ioctl.h>
28 #include <net/if.h>
29 #include <linux/can.h>
30 #include <math.h>
31
32 #include <json-c/json.h>
33
34 #define AFB_BINDING_VERSION 2
35 #include <afb/afb-binding.h>
36
37 #define CAN_DEV "vcan0"
38
39 #ifndef NULL
40 #define NULL 0
41 #endif
42
43 static const struct afb_binding_interface *interface;
44 static struct afb_event event;
45
46 /*****************************************************************************************/
47 /*****************************************************************************************/
48 /**                                                                                     **/
49 /**                                                                                     **/
50 /**        SECTION: UTILITY FUNCTIONS                                                   **/
51 /**                                                                                     **/
52 /**                                                                                     **/
53 /*****************************************************************************************/
54 /*****************************************************************************************/
55
56 /*
57  * @brief Retry a function 3 times
58  *
59  * @param int function(): function that return an int wihtout any parameter
60  *
61  * @ return : 0 if ok, -1 if failed
62  *
63  */
64 static int retry( int(*func)())
65 {
66         int i;
67
68         for (i=0;i<4;i++)
69         {
70                 if ( (*func)() >= 0)
71                 {
72                         return 0;
73                 }
74                 usleep(100000);
75         }
76         return -1;
77 }
78
79 /*****************************************************************************************/
80 /*****************************************************************************************/
81 /**                                                                                     **/
82 /**                                                                                     **/
83 /**        SECTION: HANDLE CAN DEVICE                                                   **/
84 /**                                                                                     **/
85 /**                                                                                     **/
86 /*****************************************************************************************/
87 /*****************************************************************************************/
88
89 // Initialize CAN hvac array that will be sent trough the socket
90 static struct {
91         const char *name;
92         uint8_t value;
93 } hvac_values[] = {
94         { "LeftTemperature", 21 },
95         { "RightTemperature", 21 },
96         { "Temperature", 21 },
97         { "FanSpeed", 0 }
98 };
99
100 struct can_handler {
101         int socket;
102         bool simulation;
103         char *send_msg;
104         struct sockaddr_can txAddress;
105 };
106
107 static struct can_handler can_handler = { .socket = -1, .simulation = false, .send_msg = "SENDING CAN FRAME"};
108
109 static int open_can_dev_helper()
110 {
111         struct ifreq ifr;
112
113         AFB_DEBUG("CAN Handler socket : %d", can_handler.socket);
114         close(can_handler.socket);
115
116         can_handler.socket = socket(PF_CAN, SOCK_RAW, CAN_RAW);
117         if (can_handler.socket < 0)
118         {
119                 AFB_ERROR("socket could not be created");
120         }
121         else
122         {
123                 // Attempts to open a socket to CAN bus
124                 strcpy(ifr.ifr_name, CAN_DEV);
125                 if(ioctl(can_handler.socket, SIOCGIFINDEX, &ifr) < 0)
126                 {
127                         AFB_ERROR("ioctl failed");
128                 }
129                 else
130                 {
131                         can_handler.txAddress.can_family = AF_CAN;
132                         can_handler.txAddress.can_ifindex = ifr.ifr_ifindex;
133
134                         // And bind it to txAddress
135                         if (bind(can_handler.socket, (struct sockaddr *)&can_handler.txAddress, sizeof(can_handler.txAddress)) < 0)
136                         {
137                                 AFB_ERROR("bind failed");
138                         }
139                         else {
140                                 return 0;
141                         }
142                 }
143                 close(can_handler.socket);
144                 can_handler.socket = -1;
145         }
146         return -1;
147 }
148
149 static int open_can_dev()
150 {
151         int rc = retry(open_can_dev_helper);
152         if(rc < 0)
153         {
154                 AFB_ERROR("Open of interface %s failed. Falling back to simulation mode", CAN_DEV);
155                 can_handler.socket = 0;
156                 can_handler.simulation = true;
157                 can_handler.send_msg = "FAKE CAN FRAME";
158                 rc = 0;
159         }
160         return rc;
161 }
162
163 // Get original get temperature function from cpp hvacplugin code
164 static uint8_t to_can_temp(uint8_t value)
165 {
166         int result = ((0xF0 - 0x10) / 15) * (value - 15) + 0x10;
167         if (result < 0x10)
168                 result = 0x10;
169         if (result > 0xF0)
170                 result = 0xF0;
171
172         return (uint8_t)result;
173 }
174
175 static uint8_t read_temp_left_zone()
176 {
177         return hvac_values[0].value;
178 }
179
180 static uint8_t read_temp_right_zone()
181 {
182         return hvac_values[1].value;
183 }
184
185 static uint8_t read_temp()
186 {
187         return (uint8_t)(((int)read_temp_left_zone() + (int)read_temp_right_zone()) >> 1);
188 }
189
190 static uint8_t read_fanspeed()
191 {
192         return hvac_values[3].value;
193 }
194
195 static int write_can()
196 {
197         struct can_frame txCanFrame;
198         int rc = 0;
199
200         rc = can_handler.socket;
201         if (rc >= 0)
202         {
203                 // Hardcoded can_id and dlc (data lenght code)
204                 txCanFrame.can_id = 0x30;
205                 txCanFrame.can_dlc = 8;
206                 txCanFrame.data[0] = to_can_temp(read_temp_left_zone());
207                 txCanFrame.data[1] = to_can_temp(read_temp_right_zone());
208                 txCanFrame.data[2] = to_can_temp(read_temp());
209                 txCanFrame.data[3] = 0xf0;
210                 txCanFrame.data[4] = read_fanspeed();
211                 txCanFrame.data[5] = 1;
212                 txCanFrame.data[6] = 0;
213                 txCanFrame.data[7] = 0;
214
215                 AFB_DEBUG("%s: %d %d [%02x %02x %02x %02x %02x %02x %02x %02x]\n",
216                         can_handler.send_msg,
217                         txCanFrame.can_id, txCanFrame.can_dlc,
218                         txCanFrame.data[0], txCanFrame.data[1], txCanFrame.data[2], txCanFrame.data[3],
219                         txCanFrame.data[4], txCanFrame.data[5], txCanFrame.data[6], txCanFrame.data[7]);
220
221                 if(!can_handler.simulation)
222                 {
223                         rc = (int)sendto(can_handler.socket, &txCanFrame, sizeof(struct can_frame), 0,
224                                     (struct sockaddr*)&can_handler.txAddress, sizeof(can_handler.txAddress));
225                         if (rc < 0)
226                         {
227                                 AFB_ERROR("Sending CAN frame failed.");
228                         }
229                 }
230         }
231         else
232         {
233                 AFB_ERROR("socket not initialized. Attempt to reopen can device socket.");
234                 open_can_dev();
235         }
236         return rc;
237 }
238
239 /*****************************************************************************************/
240 /*****************************************************************************************/
241 /**                                                                                     **/
242 /**                                                                                     **/
243 /**        SECTION: BINDING VERBS IMPLEMENTATION                                        **/
244 /**                                                                                     **/
245 /**                                                                                     **/
246 /*****************************************************************************************/
247 /*****************************************************************************************/
248
249 /*
250  * @brief Get fan speed HVAC system
251  *
252  * @param struct afb_req : an afb request structure
253  *
254  */
255 static void get_fanspeed(struct afb_req request)
256 {
257         json_object *ret_json;
258         uint8_t fanspeed = read_fanspeed();
259
260         ret_json = json_object_new_object();
261         json_object_object_add(ret_json, "FanSpeed", json_object_new_int(fanspeed));
262
263         afb_req_success(request, ret_json, NULL);
264 }
265
266 /*
267  * @brief Read Consign right zone temperature for HVAC system
268  *
269  * @param struct afb_req : an afb request structure
270  *
271  */
272 static void get_temp_right_zone(struct afb_req request)
273 {
274         json_object *ret_json;
275         uint8_t temp = read_temp_right_zone();
276
277         ret_json = json_object_new_object();
278         json_object_object_add(ret_json, "RightTemperature", json_object_new_int(temp));
279
280         afb_req_success(request, ret_json, NULL);
281 }
282
283 /*
284  * @brief Read Consign left zone temperature for HVAC system
285  *
286  * @param struct afb_req : an afb request structure
287  *
288  */
289 static void get_temp_left_zone(struct afb_req request)
290 {
291         json_object *ret_json;
292         uint8_t temp = read_temp_left_zone();
293
294         ret_json = json_object_new_object();
295         json_object_object_add(ret_json, "LeftTemperature", json_object_new_int(temp));
296
297         afb_req_success(request, ret_json, NULL);
298 }
299
300 /*
301  * @brief Read all values
302  *
303  * @param struct afb_req : an afb request structure
304  *
305  */
306 static void get(struct afb_req request)
307 {
308         AFB_DEBUG("Getting all values");
309         json_object *ret_json;
310
311         ret_json = json_object_new_object();
312         json_object_object_add(ret_json, "LeftTemperature", json_object_new_int(read_temp_left_zone()));
313         json_object_object_add(ret_json, "RightTemperature", json_object_new_int(read_temp_right_zone()));
314         json_object_object_add(ret_json, "FanSpeed", json_object_new_int(read_fanspeed()));
315
316         afb_req_success(request, ret_json, NULL);
317 }
318
319 /*
320  * @brief Set a component value using a json object retrieved from request
321  *
322  * @param struct afb_req : an afb request structure
323  *
324  */
325 static void set(struct afb_req request)
326 {
327         int i, rc, x, changed;
328         double d;
329         struct json_object *query, *val;
330         uint8_t values[sizeof hvac_values / sizeof *hvac_values];
331         uint8_t saves[sizeof hvac_values / sizeof *hvac_values];
332
333         /* records initial values */
334         AFB_DEBUG("Records initial values");
335         i = (int)(sizeof hvac_values / sizeof *hvac_values);
336         while (i) {
337                 i--;
338                 values[i] = saves[i] = hvac_values[i].value;
339         }
340
341         /* Loop getting arguments */
342         query = afb_req_json(request);
343         changed = 0;
344         i = (int)(sizeof hvac_values / sizeof *hvac_values);
345         AFB_DEBUG("Looping for args. i: %d", i);
346         while (i)
347         {
348                 i--;
349                 AFB_DEBUG("Searching... query: %s, i: %d, comp: %s", json_object_to_json_string(query), i, hvac_values[i].name);
350                 if (json_object_object_get_ex(query, hvac_values[i].name, &val))
351                 {
352                         AFB_DEBUG("We got it. Tests if it is an int or double.");
353                         if (json_object_is_type(val, json_type_int)) {
354                                 x = json_object_get_int(val);
355                                 AFB_DEBUG("We get an int: %d",x);
356                         }
357                         else if (json_object_is_type(val, json_type_double)) {
358                                 d = json_object_get_double(val);
359                                 x = (int)round(d);
360                                 AFB_DEBUG("We get a double: %f => %d",d,x);
361                         }
362                         else {
363                                 afb_req_fail_f(request, "bad-request",
364                                         "argument '%s' isn't integer or double", hvac_values[i].name);
365                                 return;
366                         }
367                         if (x < 0 || x > 255)
368                         {
369                                 afb_req_fail_f(request, "bad-request",
370                                         "argument '%s' is out of bounds", hvac_values[i].name);
371                                 return;
372                         }
373                         if (values[i] != x) {
374                                 values[i] = (uint8_t)x;
375                                 changed = 1;
376                                 AFB_DEBUG("%s changed to %d",hvac_values[i].name,x);
377                         }
378                 }
379                 else {
380                         AFB_DEBUG("%s not found in query!",hvac_values[i].name);
381                 }
382         }
383
384         /* attemps to set new values */
385         AFB_DEBUG("Diff: %d", changed);
386         if (changed)
387         {
388                 i = (int)(sizeof hvac_values / sizeof *hvac_values);
389                 while (i) {
390                         i--;
391                         hvac_values[i].value = values[i];
392                 }
393                 rc = write_can();
394                 if (rc >= 0)
395                         afb_req_success(request, NULL, NULL);
396                 else if (retry(write_can)) {
397                         /* restore initial values */
398                         i = (int)(sizeof hvac_values / sizeof *hvac_values);
399                         while (i) {
400                                 i--;
401                                 hvac_values[i].value = saves[i];
402                         }
403                         afb_req_fail(request, "error", "CAN error");
404                 }
405         }
406         else {
407                 afb_req_success(request, NULL, "No changes");
408         }
409 }
410
411 int bindingServicePreInit(struct afb_service service)
412 {
413         return open_can_dev();
414 }
415
416 int bindingServiceInit(struct afb_service service)
417 {
418         event = afb_daemon_make_event("language");
419         if(afb_daemon_require_api("identity", 1))
420                 return -1;
421         return afb_service_call_sync("identity", "subscribe", NULL, NULL);
422 }
423
424 void onEvent(const char *event_name, struct json_object *object)
425 {
426         json_object *args, *language = json_object_new_object();
427         json_object *id_evt_name, *current_identity;
428
429         AFB_NOTICE("Event '%s' received: %s", event_name,
430                 json_object_to_json_string_ext(object, JSON_C_TO_STRING_PRETTY));
431
432         if (json_object_object_get_ex(object, "eventName", &id_evt_name) &&
433           !strcmp(json_object_get_string(id_evt_name), "login") &&
434           !afb_service_call_sync("identity", "get", NULL, &current_identity)) {
435                 json_object *response;
436                 if (! json_object_object_get_ex(current_identity, "response", &response) || ! json_object_object_get_ex(response, "graphPreferredLanguage", &language)) {
437                         language = json_object_new_string("en_US");
438                 }
439                 afb_event_broadcast(event, language);
440         }
441 }
442
443 // TODO: Have to change session management flag to AFB_SESSION_CHECK to use token auth
444 static const struct afb_verb_v2 _afb_verbs_v2_hvac[]= {
445         {
446                 .verb = "get_temp_left_zone",
447                 .callback = get_temp_left_zone,
448                 .auth = NULL,
449                 .info = "Get the left zone temperature",
450                 .session = AFB_SESSION_NONE_V2
451         },
452         {
453                 .verb = "get_temp_right_zone",
454                 .callback = get_temp_right_zone,
455                 .auth = NULL,
456                 .info = "Get the right zone temperature",
457                 .session = AFB_SESSION_NONE_V2
458         },
459         {
460                 .verb = "get_fanspeed",
461                 .callback = get_fanspeed,
462                 .auth = NULL,
463                 .info = "Read fan speed",
464                 .session = AFB_SESSION_NONE_V2
465         },
466         {
467                 .verb = "get",
468                 .callback = get,
469                 .auth = NULL,
470                 .info = "Read all speed",
471                 .session = AFB_SESSION_NONE_V2
472         },
473         {
474                 .verb = "set",
475                 .callback = set,
476                 .auth = NULL,
477                 .info = "Set a HVAC component value",
478                 .session = AFB_SESSION_NONE_V2
479         },
480         {
481                 .verb = NULL,
482                 .callback = NULL,
483                 .auth = NULL,
484                 .info = NULL,
485                 .session = 0
486         }
487 };
488
489 const struct afb_binding_v2 afbBindingV2 = {
490     .api = "hvac",
491     .specification = NULL,
492     .info = "HVAC service",
493     .verbs = _afb_verbs_v2_hvac,
494     .preinit = bindingServicePreInit,
495     .init = bindingServiceInit,
496     .onevent = onEvent,
497     .noconcurrency = 0
498 };