การส่งค่า การอ่าน(รับ)ค่า topic หรือตัวแปร ไปยัง netpie ด้วย C โดยใช้ library cURL (ผ่าน REST API) และ json-c (อ่านค่าที่รับจาก netpie ซึ่งเป็น json array)
โครงสร้างของ rest api ปกติที่เราเรียกด้วย curl ก็จะเป็นประมาณนี้
curl -X PUT https://api.netpie.io/topic/ThingsControl/seal/status?retain&auth=แทนค่าด้วย app key:แทนค่าด้วย app secret -d “ON”
https://api.netpie.io/topic/ThingsControl/seal/status?retain&auth=xxxxxxxx:yyyyyy
uri : https://api.netpie.io/topic/
/ThingsControl คือ Application ที่สร้างไว้ใน netpie
/seal/status คือ topic หรือตัวแปร
retain คือการให้คงค่าไว้
auth คือ parameter ที่กำหนดการพิสูจน์ตัวตน (ระบุด้วย app key, app secret)
-d “ON” คือ ค่าที่เราจะส่งไปยังตัวแปร
ตัวอย่างที่เขียนด้วย C โดยการใช้ cURL ศึกษาได้จาก https://curl.haxx.se/libcurl/c/example.html และ https://stackoverflow.com/questions/7569826/send-string-in-put-request-with-libcurl stackoverflow ช่วยได้เยอะถ้าหาเจอ ha ha ha 😀
การติดตั้ง cURL บน Raspberry pi
sudo apt-get install libcurl4-openssl-dev
ดูเพิ่มเติมได้ที่นี่ https://bitcontrol.ch/en/2016/02/04/iot-remote-power-switch-part-10/
ตัวอย่าง
/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at https://curl.haxx.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ***************************************************************************/ /* <DESC> * simple HTTP PUT using the easy interface * </DESC> */ /* Things Control. Control anything you want. Copyright (C) 2017 Pornthep Nivatyakul This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. ThingsControl Copyright (C) 2017 Pornthep Nivatyakul, kaebmoo@gmail.com, seal@ogonan.com This program comes with ABSOLUTELY NO WARRANTY; This is free software, and you are welcome to redistribute it under certain conditions. */ #include <stdio.h> #include <string.h> #include <curl/curl.h> #include <stdlib.h> #include <fcntl.h> #ifdef WIN32 #include <io.h> #else #include <unistd.h> #endif #include <sys/types.h> #include <sys/stat.h> #if LIBCURL_VERSION_NUM < 0x070c03 #error "upgrade your libcurl to no less than 7.12.3" #endif int main(void) { CURL *curl; CURLcode res; typedef struct { char *key; char *secret; char *param; char *uri; } app_key; app_key AppKey; char *key = "app key"; char *secret = "app secret"; char *param = "auth="; char *netpie = "https://api.netpie.io/topic"; char *app_id = "/ThingsControl"; char *id = "/seal"; char *topic = "/status?retain&"; /* https://api.netpie.io/topic/ThingsControl/seal/status?retain&auth=xxxxx:yyyy*/ int alloc = 0; AppKey.key = malloc(strlen(key)+1); AppKey.secret = malloc(strlen(secret)+1); alloc += strlen(param) + strlen(key) + strlen(":") + strlen(secret); alloc++; AppKey.param = malloc(alloc); AppKey.uri = malloc(strlen(netpie)+strlen(app_id)+strlen(id)+strlen(topic)+strlen(param)+strlen(key)+strlen(":")+strlen(secret)+1); strcpy(AppKey.key, key); strcpy(AppKey.secret, secret); strcpy(AppKey.param, param); /* In windows, this will init the winsock stuff */ curl_global_init(CURL_GLOBAL_ALL); /* get a curl handle */ curl = curl_easy_init(); if(curl) { /* First set the URL that is about to receive our PUT. This URL can just as well be a https:// URL if that is what should receive the data. */ /* printf("Parameter %s\n", AppKey.param); */ AppKey.param = strcat(strcat(strcat(AppKey.param, AppKey.key),":"), AppKey.secret); AppKey.uri = strcat(strcat(strcat(strcat(strcat(AppKey.uri,netpie), app_id), id), topic), AppKey.param); printf("Parameter %s, \nuri %s\n", AppKey.param, AppKey.uri); curl_easy_setopt(curl, CURLOPT_URL, AppKey.uri); /* Now specify the PUT data */ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "ON"); /* Perform the request, res will get the return code */ res = curl_easy_perform(curl); printf("return code %d\n", res); /* Check for errors */ if(res != CURLE_OK) fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); /* always cleanup */ curl_easy_cleanup(curl); } curl_global_cleanup(); free(AppKey.param); free(AppKey.key); free(AppKey.secret); free(AppKey.uri); return 0; }
ตัวหลักหลักก็อยู่ที่
CURL *curl;
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, AppKey.uri); กำหนด URL
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, “PUT”); บอกให้ PUT
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, “ON”); ค่าที่จะส่งขึ้นไป
res = curl_easy_perform(curl);
ที่นี้มาถึงตอนอ่านค่า
เวลาที่เรียก GET rest api netpie จะส่งค่าคืนมาเป็น json array หน้าตาประมาณนี้
เรียกผ่าน browser
https://api.netpie.io/topic/ThingsControl/seal/status?auth=xxxx:yyyy
[{“topic”:”/ThingsControl/seal/status”,”payload”:”ON”,”lastUpdated”:1497340128,”retain”:true}]
โดย key “topic” ก็เป็นชื่อ topic ตามที่เราสนใจเรียกอ่าน
“payload” ก็เป็นค่าของ topic หรือค่าของตัวแปร
“lastUpdated” เป็นค่าเวลา (epoch time) ที่ตัวแปรถูกกำหนดค่า เช่น 1497340128 อยากรู้ว่าเป็นเวลาเท่าไหร่ ลองเอาไปแปลงค่าดู https://www.epochconverter.com/ ก็จะได้เท่ากับ
GMT: Tuesday, June 13, 2017 7:48:48 AM
Your time zone: Tuesday, June 13, 2017 2:48:48 PM GMT+07:00
“retain” เป็น Boolean บอกว่าตัวแปรมีการคงค่าไว้หรือไม่
ตัวอย่างการเขียนอ่านค่าด้วย C จะมีสองส่วนคือ ส่วนของ rest api ที่เรียกผ่าน cURL ซึ่งก็เรียกผ่าน url ตามปกติ https://api.netpie.io/topic/ThingsControl/seal/status?auth=xxxx:yyyy แต่จะมีการเรียก callback function เพื่อให้มีการเก็บค่าที่ได้มาจาก server มาเก็บไว้ในตัวแปร chuck.memory ด้วย ซึ่งก็จะได้เป็น string แบบ json array มา
จากนั้นก็เอามา Parse ด้วย library json-c (http://json.org/ ทำไม ใช้ตัวนี้? ค้นหาด้วย google แล้วมันโผล่ขึ้นมา ก็เลยลองลองเล่นดู ถ้าดูใน json.org จะเห็นว่ามี library เยอะมาก ชอบตัวไหน ใช้ตัวไหนก็ตามลำบากครับท่าน ผม งมงม ตัวนี้แล้วใช้งานลองผิดไปหลายตลบแล้วมันได้ก็เลยใช้ใช้ไป)
ตัวอย่างที่ใช้งม
- https://alan-mushi.github.io/2014/10/28/json-c-tutorial-part-2.html
- https://linuxprograms.wordpress.com/2010/05/20/json-c-libjson-tutorial/
- https://coolaj86.com/articles/json-c-example.html
- https://json-c.github.io/json-c/json-c-0.10/doc/html/globals.html อันนี้ขาดไม่ได้เลย
- validate.jsontest.com/?json=[{“topic”:”/ThingsControl/seal/status”,”payload”:”ON”,”lastUpdated”:1497340128,”retain”:true}] ตัวช่วย
การติดตั้ง json-c https://github.com/json-c/json-c
ตัวช่วยถ้า compile แล้ว error https://stackoverflow.com/questions/480764/linux-error-while-loading-shared-libraries-cannot-open-shared-object-file-no-s
การแปลงเวลาจาก epoch time มาให้อ่านกันรู้เรื่อง ดูตัวอย่างจากนี่ https://www.epochconverter.com/programming/c
การใช้ json-c ก่อนจะนำค่าที่ได้จากการ parse json เขาก็แนะนำว่าควรจะทดสอบ type ก่อน เพื่อป้องกันข้อผิดพลาดของการกำหนดค่าตัวแปร เช่น ดูว่าข้อมูลเป็น string, int, Boolean ฯลฯ คงจะเนื่องจากภาษา C การกำหนดค่าตัวแปร เข้มงวดกว่าภาษาอื่น ถ้าผิดประเภทก็จะ error ได้ แต่ถ้าเรารู้โครงสร้างของข้อมูลที่ได้รับมาอยู่แล้วเราก็สามารถระบุ function ที่จะใช้เรียกอ่านค่าได้เลยไม่ว่าจะ get_string() get_int() อะไรทำนองนั้น
คำสั่ง compile ก็ประมาณนี้ cc getnetpie.c -lcurl -ljson-c -o getnetpie
/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at https://curl.haxx.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ***************************************************************************/ /* <DESC> * Shows how the write callback function can be used to download data into a * chunk of memory instead of storing it in a file. * </DESC> */ /* Things Control. Control anything you want. Copyright (C) 2017 Pornthep Nivatyakul This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. ThingsControl Copyright (C) 2017 Pornthep Nivatyakul, kaebmoo@gmail.com, seal@ogonan.com This program comes with ABSOLUTELY NO WARRANTY; This is free software, and you are welcome to redistribute it under certain conditions. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <curl/curl.h> #include <json-c/json.h> struct MemoryStruct { char *memory; size_t size; }; void json_parse(json_object * jobj) { enum json_type type; json_object_object_foreach(jobj, key, val) { type = json_object_get_type(val); switch (type) { case json_type_string: printf("type: json_type_string, "); printf("value: %s\n", json_object_get_string(val)); break; case json_type_int: printf("type: json_type_int, "); printf("value: %d\n", json_object_get_int(val)); break; } } /* enum json_type type; json_object_object_foreach(jobj, key, val) { type = json_object_get_type(val); switch (type) { case json_type_int: printf("type: json_type_int, "); printf("value: %dn", json_object_get_int(val)); break; } } */ } static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; struct MemoryStruct *mem = (struct MemoryStruct *)userp; mem->memory = realloc(mem->memory, mem->size + realsize + 1); if(mem->memory == NULL) { /* out of memory! */ printf("not enough memory (realloc returned NULL)\n"); return 0; } memcpy(&(mem->memory[mem->size]), contents, realsize); mem->size += realsize; mem->memory[mem->size] = 0; return realsize; } int main(void) { CURL *curl_handle; CURLcode res; struct MemoryStruct chunk; int array_len, i; chunk.memory = malloc(1); /* will be grown as needed by the realloc above */ chunk.size = 0; /* no data at this point */ curl_global_init(CURL_GLOBAL_ALL); /* init the curl session */ curl_handle = curl_easy_init(); /* specify URL to get */ curl_easy_setopt(curl_handle, CURLOPT_URL, "https://api.netpie.io/topic/ThingsControl/seal/status?auth=xxx:yyy"); /* send all data to this function */ curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); /* we pass our 'chunk' struct to the callback function */ curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk); /* some servers don't like requests that are made without a user-agent field, so we provide one */ curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); /* get it! */ res = curl_easy_perform(curl_handle); /* check for errors */ if(res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); } else { /* * Now, our chunk.memory points to a memory block that is chunk.size * bytes big and contains the remote file. * * Do something nice with it! */ printf("%s\n", chunk.memory); printf("%lu bytes retrieved\n", (long)chunk.size); json_object *jobj; json_object *array; int stringlen = 0; enum json_tokener_error jerr; enum json_type jtype; struct json_tokener *tok; tok = json_tokener_new(); char *type_str; time_t now; struct tm ts; char lastUpdated[80]; do { stringlen = strlen(chunk.memory); jobj = json_tokener_parse_ex(tok, chunk.memory, stringlen); } while ((jerr = json_tokener_get_error(tok)) == json_tokener_continue); if (jerr != json_tokener_success) { fprintf(stderr, "Error: %s\n", json_tokener_error_desc(jerr)); // Handle errors, as appropriate for your application. } if (tok->char_offset < stringlen) // XXX shouldn't access internal fields { fprintf(stderr, "Error: tok->char_offset < stringlen"); // Handle extra characters after parsed object as desired. // e.g. issue an error, parse another object from that point, etc... } jtype = json_object_get_type(jobj); switch(jtype) { case json_type_null: type_str = "NULL"; break; case json_type_boolean: type_str = "BOOLEAN"; break; case json_type_double: type_str = "DOUBLE"; break; case json_type_int: type_str = "INT"; break; case json_type_string: type_str = "STRING"; printf("%s\n", json_object_get_string(jobj)); break; case json_type_object: type_str = "OBJECT"; break; case json_type_array: type_str = "ARRAY"; break; } printf("Type %s\n", type_str); printf("new_obj.to_string()=%s\n", json_object_to_json_string(jobj)); printf("array length %d\n", json_object_array_length(jobj)); for (i=0; i < json_object_array_length(jobj); i++) { printf("%s\n", json_object_to_json_string(json_object_array_get_idx(jobj, i))); } array = json_object_object_get(json_object_array_get_idx(jobj,0), "topic"); printf("payload : %s\n", json_object_to_json_string(array)); array = json_object_object_get(json_object_array_get_idx(jobj,0), "payload"); printf("payload : %s\n", json_object_to_json_string(array)); array = json_object_object_get(json_object_array_get_idx(jobj,0), "lastUpdated"); now = json_object_get_int(array); ts = *localtime((const time_t *) &now); strftime(lastUpdated, sizeof(lastUpdated), "%a %Y-%m-%d %H:%M:%S %Z", &ts); printf("payload : %d, %s\n", json_object_get_int(array), lastUpdated); array = json_object_object_get(json_object_array_get_idx(jobj,0), "retain"); printf("payload : %d\n", json_object_get_boolean(array)); /* jobj = json_object_object_get(jobj, "payload"); printf("topic : %s\n", json_object_to_json_string(jobj)); */ } /* cleanup curl stuff */ curl_easy_cleanup(curl_handle); free(chunk.memory); /* we're done with libcurl, so clean it up */ curl_global_cleanup(); return 0; }
run program ก็จะได้ประมาณนี้
pi@register:~/thingscontrol $ ./getnetpie [{"topic":"/ThingsControl/seal/status","payload":"ON","lastUpdated":1497340128,"retain":true}] 94 bytes retrieved Type ARRAY new_obj.to_string()=[ { "topic": "\/ThingsControl\/seal\/status", "payload": "ON", "lastUpdated": 1497340128, "retain": true } ] array length 1 { "topic": "\/ThingsControl\/seal\/status", "payload": "ON", "lastUpdated": 1497340128, "retain": true } payload : "\/ThingsControl\/seal\/status" payload : "ON" payload : 1497340128, Tue 2017-06-13 14:48:48 +07 payload : 1 pi@register:~/thingscontrol $
ความที่ netpie ส่งมาเป็น array กว่าจะงมหาวิธีแงะได้ ลองอยู่หลายรอบ เพราะตัวอย่างที่ค้นเจอ มันมักจะ Parse json แบบ { “topic”:”value”, “status”,”ON” } อะไรงี้ แถม array ไม่มีชื่อ object แบบในตัวอย่างอีก โห กูเหนื่อย 😉 เริ่มมา ปิ๊ง ปิ๊ง หลังจากเจออันนี้ https://stackoverflow.com/questions/10164741/get-jsonarray-without-array-name และนี่ http://www.jsontest.com/#validate ก็เลยรอดมาได้
เริ่มจากไม่รู้เรื่องเลยก็ใช้เวลาประมาณ 2 วัน 🙂 วันแรกก็ งมการใช้ cURL วันที่สองก็งม parse json หวังว่าท่านจะต่อยอดไปได้เร็วขึ้น
ทำไม ไม่ใช้ python, JavaScript?
อันนั้นมันมีตัวอย่างเยอะละ ที่สำคัญคือ กูเขียนไม่เป็น ha ha ผมมันคนโบราณ ใช้ C แบบถึกถึก นี่แหละ
ปล. code ตัวอย่าง จะมีที่ไม่เกี่ยวข้องอยู่พอสมควร ซึ่งผมใช้ในการ debug ตัว program และเป็นตัวอย่างการใช้งานต่าง ๆ