ESP8266 OTA

อ้างอิงจาก https://www.bakke.online/index.php/2017/06/02/self-updating-ota-firmware-for-esp8266/
และ http://esp8266.github.io/Arduino/versions/2.1.0-rc2/doc/ota_updates/ota_updates.html

การเขียนโปรแกรมสั่งงานอุปกรณ์ IoT (Internet of Things) นั้นส่วนใหญ่จะทำการเขียนบนเครื่องมือพัฒนาที่เรียกว่า IDE (Integrated development environment) เช่น Arduino IDE จากนั้นก็จะทำการ upload ตัวโปรแกรม (binary compiled) ไปยังบอร์ดผ่านทาง serial port (USB) หรือสายอนุกรมที่ต่อระหว่างคอมพิวเตอร์กับบอร์ด เมื่อมีการแก้ไขโปรแกรมและทำการ compile ใหม่ก็จะทำแบบเดียวกันนี้อีกครั้ง เมื่อนำไปใช้งานแล้วถ้าหากจะต้องมีการแก้ไขก็จะต้องนำอุปกรณ์ IoT นั้นมาเชื่อมต่อกับเครื่องคอมพิวเตอร์ที่เขียนโปรแกรม เพื่อทำการ upload โปรแกรมที่แก้ไขแล้วลงไปที่อุปกรณ์ใหม่อีกครั้งผ่านสายอนุกรม จะเห็นได้ว่าในบางกรณีนั้นการทำแบบนี้เป็นไปได้ยากมาก เช่น ถ้าอุปกรณ์นั้นไม่ได้อยู่ในบ้านหรือในที่ทำงาน แต่อยู่ในสถานที่ห่างไกลออกไป เช่น อยู่กับลูกค้า อยู่ต่างจังหวัด อยู่ต่างประเทศ การทำ upload ผ่านทางสายอนุกรมนั้นแทบจะเป็นไปไม่ได้เลย การส่งอุปกรณ์กลับก็สร้างความยุ่งยากเช่นเดียวกัน จึงได้มีแนวคิดของการ upload โปรแกรมผ่านทางเครือข่ายเช่น WiFi หรือผ่านทางอินเทอร์เน็ต เพื่อที่อุปกรณ์ที่ใช้งานอยู่นั้นสามารถ download โปรแกรมอันใหม่ผ่านทางเครือข่ายไปปรับปรุงตัวเองได้ หรือสามารถให้ผู้ใช้ปลายทางสามารถที่จะ download โปรแกรมตัวใหม่ทำการ upload ด้วยตัวเองผ่าน wifi เพื่อ upload เข้าไปที่บอร์ด ซึ่งเมื่อบอร์ด upload โปรแกรมใหม่เสร็จก็จะทำการ reboot ตัวเองใหม่เพื่อทำงานตามโปรแกรมที่ได้รับการปรับปรุง

การ upload firmware ผ่านทาง WiFi

บอร์ด (esp8266) ทำการจำลองตัวเองเป็น web server พร้อมกับต่อ WiFi ที่กำหนดเพื่อให้ผู้ใช้ upload file (binary compiled) จากนั้นเราใช้คอมพิวเตอร์เชื่อมต่อ WiFi เดียวกันกับที่บอร์ดเชื่อมต่อแล้วใช้ browser เปิดเข้าไปที่ url ตามที่เรากำหนดใน code เช่น http://ogoswitch-7228204.local/firmware เพื่อทำการ upload binary file ที่เรา compiled แล้วไปยังบอร์ด

ตัวอย่าง code ได้แก่


#include "DNSServer.h"
#include "WiFiManager.h"  

#include "ESP8266HTTPUpdateServer"
#include "ESP8266WebServer.h"
#include "ESP8266mDNS"

const char* host = "ogosense-webupdate";
const char* update_path = "/firmware";
const char* update_username = "admin";
const char* update_password = "ogosense";
ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;

setup() 
{
   WiFiManager wifiManager;
   Serial.begin(115200);
   Serial.println();
   wifiManager.setTimeout(300);
   String APName = "OgoSense-"+String(ESP.getChipId());         
   if(!wifiManager.autoConnect(APName.c_str()) ) {
        Serial.println("failed to connect and hit timeout");
        delay(3000);
        // reset and try again, or maybe put it to deep sleep
        ESP.reset();
        delay(5000);
     }
 
   // if you get here you have connected to the WiFi
   Serial.println("connected...yeey :)");

   // web update OTA
   String host_update_name;
   host_update_name = "ogoswitch-"+String(ESP.getChipId());
   MDNS.begin(host_update_name.c_str());
   httpUpdater.setup(&httpServer, update_path, update_username, update_password);
   httpServer.begin();
   MDNS.addService("http", "tcp", 80);
   Serial.printf("HTTPUpdateServer ready! Open http://%s.local%s in your browser and login with username '%s' and password '%s'\n", host_update_name.c_str(), update_path, update_username, update_password);
}

loop()
{
   httpServer.handleClient();
}

การปรับปรุงโปรแกรมผ่านอินเทอร์เน็ตโดยอัตโนมัติเมื่อมีการปรับปรุง

การให้บอร์ด (IoT) ทำการตรวจสอบและทำการ update ผ่าน web server ที่เรากำหนดไว้ โดยเราจะนำไฟล์ที่ compiled แล้วไปเก็บไว้บน web server และสร้างไฟล์เพื่อระบุหมายเลขรุ่นใส่ไว้ โดยโปรแกรมบนบอร์ดจะทำการเปรียบเทียบเลขดังกล่าว ถ้าเลขในไฟล์บน web server มีค่ามากกว่าบนบอร์ด โปรแกรมบนบอร์ดก็จะทำการ download binary file อันใหม่ตามที่เราระบุชื่อ มาทำการ update บนบอร์ด เมื่อการปรับปรุงเสร็จตัวบอร์ดจะทำการ reboot เพื่อที่จะทำงานบนโปรแกรมอันใหม่ ดังนั้นโปรแกรมอันใหม่ที่เขียนปรับปรุงจะต้องมีการระบุหมายเลขในตัวแปร FW_VERSION ให้ตรงกับตัวเลขในไฟล์ที่ระบุหมายเลขรุ่นล่าสุด เมื่อเราต้องการปรับปรุงโปรแกรมก็เพิ่มค่าตัวเลขดังกล่าวขึ้นไปเช่น ถ้าของเดิมเป็น 1 ก็เปลี่ยนเป็น 2 โดยจะต้องเปลี่ยนทั้งในตัวโปรแกรม (source code) และค่าในไฟล์หมายเลขรุ่น

ตัวอย่าง


#include "DNSServer.h"
#include "ESP8266WebServer.h"
#include "WiFiManager.h"    

#include "Timer.h"

#include "WiFiClient.h"
#include "ESP8266mDNS.h"
#include "ESP8266HTTPUpdateServer.h"
#include "ESP8266HTTPClient.h"
#include "ESP8266httpUpdate.h"

const char* host = "ogosense-webupdate";
const char* update_path = "/firmware";
const char* update_username = "admin";
const char* update_password = "ogosense";
const int FW_VERSION = 1;
const char* firmwareUrlBase = "http://www.ogonan.com/ogoupdate/";
String firmware_name = "ogoswitch_temperature_humidity_nodisplay.ino.ino.d1_mini";
ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;
Timer checkFirmware;

void setup()
{
   WiFiManager wifiManager;

   Serial.begin(115200);
   Serial.println();
   wifiManager.setTimeout(300);
   String APName = "OgoSense-"+String(ESP.getChipId());
   if(!wifiManager.autoConnect(APName.c_str()) ) {
     Serial.println("failed to connect and hit timeout");
     delay(3000);
     //reset and try again, or maybe put it to deep sleep
     ESP.reset();
     delay(5000);
   }

  //if you get here you have connected to the WiFi
  Serial.println("connected...yeey :)");

  // web update OTA
  String host_update_name;
  host_update_name = "ogoswitch-"+String(ESP.getChipId());
  MDNS.begin(host_update_name.c_str());
  httpUpdater.setup(&httpServer, update_path, update_username, update_password);
  httpServer.begin();
  MDNS.addService("http", "tcp", 80);
  Serial.printf("HTTPUpdateServer ready! Open http://%s.local%s in your browser and login with username '%s' and password '%s'\n", host_update_name.c_str(), update_path, update_username, update_password);

  checkFirmware.every(86400000L, upintheair);
  upintheair();
}

void loop()
{
  checkFirmware.update();
}

void upintheair()
{
  String fwURL = String( firmwareUrlBase );
  fwURL.concat( firmware_name );
  String fwVersionURL = fwURL;
  fwVersionURL.concat( ".version" );

  Serial.println( "Checking for firmware updates." );
  // Serial.print( "MAC address: " );
  // Serial.println( mac );
  Serial.print( "Firmware version URL: " );
  Serial.println( fwVersionURL );

  HTTPClient httpClient;
  httpClient.begin( fwVersionURL );
  int httpCode = httpClient.GET();
  if( httpCode == 200 ) {
    String newFWVersion = httpClient.getString();

    Serial.print( "Current firmware version: " );
    Serial.println( FW_VERSION );
    Serial.print( "Available firmware version: " );
    Serial.println( newFWVersion );

    int newVersion = newFWVersion.toInt();

    if( newVersion > FW_VERSION ) {
      Serial.println( "Preparing to update" );

      String fwImageURL = fwURL;
      fwImageURL.concat( ".bin" );
      t_httpUpdate_return ret = ESPhttpUpdate.update( fwImageURL );

      switch(ret) {
        case HTTP_UPDATE_FAILED:
          Serial.printf("HTTP_UPDATE_FAILD Error (%d): %s", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
          break;

        case HTTP_UPDATE_NO_UPDATES:
          Serial.println("HTTP_UPDATE_NO_UPDATES");
          break;
      }
    }
    else {
      Serial.println( "Already on latest version" );
    }
  }
  else {
    Serial.print( "Firmware version check failed, got HTTP response code " );
    Serial.println( httpCode );
  }
  httpClient.end();
}

ในตัวอย่างจะมีการตรวจสอบ firmware ใหม่เมื่อเริ่มทำงาน และจะมีการตรวจสอบทุก 24 ชั่วโมง โดยใช้ Timer เรียกใช้ function upintheair()

   checkFirmware.every(86400000L, upintheair);

ogoswitch_temperature_humidity_nodisplay.ino.d1_mini.version

1

การนำโปรแกรมที่ compile แล้วไปไว้บน web server เราสามารถทำได้โดยการ compile โปรแกรมในเมนู Sketch เลือก Export Compiled Binary หรือกด Compile the sketch ด้วยปุ่ม Ctrl+R (windows) หรือ command+r (mac) และ export binary ด้วยปุ่ม Ctrl+Alt+S (windows) หรือ command+option+s (mac) จากนั้นนำ binary file upload ขึ้นไปยัง web server ในตำแหน่งที่กำหนด ตามตัวแปร firmwareUrlBase โดยอย่าลืมสร้างไฟล์เก็บเลขรุ่นแล้ว upload ขึ้นไปด้วยเช่น ogoswitch_temperature_humidity_nodisplay.ino.d1_mini.version โดยในไฟล์ให้ระบุหมายเลขรุ่นตามที่กำหนด เมื่อมีการปรับปรุงก็ให้เพิ่มเลขนี้ขึ้นไปให้มากกว่าเดิม

มีวิธีง่ายง่ายอีกหนึ่งวิธีคือ

   ESPhttpUpdate.update("192.168.0.2", 80, "/arduino.bin");

คำสั่งเดียว โดย 192.168.0.2 ก็เป็น ip address ของ web server, 80 คือหมายเลข port, /arduino.bin ก็เป็น directory และชื่อไฟล์ของโปรแกรมที่ต้องการ update เวลาที่ต้องการ update ก็ทำเป็น function แล้วเรียกใช้งาน อาจจะทำผ่านการกดปุ่มแล้วเรียก function ดังกล่าวก็ได้ สำหรับการใช้งานที่ซับซ้อนและละเอียดมากกว่านี้ก็สามารถดูได้จาก link ที่ให้ไว้ข้างบนสุด

ด้วยวิธีการแบบนี้ต่อให้อุปกรณ์ IoT เราไปอยู่ถึงญี่ปุ่น เราก็ยังสามารถปรับปรุงโปรแกรมเพิ่มความสามารถใหม่ หรือ upload โปรแกรมใหม่ ที่แก้ไข bug แล้วซึ่งอาจเกิดในโปรแกรมรุ่นแรกที่มี bug เพียบบบบบบ 😉 ไปยังอุปกรณ์ดังกล่าวได้โดยไม่ต้องเหาะเอาคอมพิวเตอร์ไปจิ้มที่เครื่อง หรือในกรณีที่คนใช้งานทั่วไปที่ไม่สามารถทำเองได้ วิธีการเหล่านี้ก็สามารถช่วยแก้ไขปัญหาดังกล่าวได้อย่างดี

หมายเหตุ กรุณาเพิ่มเติม code เกี่ยวกับเรื่องความปลอดภัยด้วย เพราะตัวอย่างนี้ไม่รวมเรื่องการตรวจสอบยืนยันเรื่องความปลอดภัย ถ้าไม่ทำท่านอาจถูก hacker ทำการ hack DNS หรือ redirect http request ไปยัง host ของ hacker แล้ว upload โปรแกรมของ hacker แทน เจอแบบนั้นตัวใครตัวมันนะครับท่าน 😛

Advertisements

ความตาย ความแน่นอน ความไม่แน่นอน

ความตายน่ากลัวไหม

ผมก็กลัว คนส่วนใหญ่ก็น่าจะกลัว เพียงแต่ผมรู้สึกว่าตอนนี้ตัวเองรู้สึกกลัวน้อยลง เราทุกคนโตขึ้นมาก็จะทราบดีว่าเราจะต้องตายและความตายเป็นเรื่องแน่นอนยิ่งกว่าแน่นอน

ถ้าลองพิจารณาดู ในประสบการณ์ที่เราเคยสัมผัส เรื่องอะไรก็ตามที่เราคิดว่ามันแน่นอนเราจะมั่นใจ เรื่องอะไรที่เรามั่นใจเราจะไม่กังวล เราจะไม่รู้สึกหวั่นไหวมาก เราสบายใจกับเรื่องที่เราคิดว่ามันแน่นอน แต่กับความตายซึ่งเป็นเรื่องแน่นอนทำไมเราจึงมักกังวลหรือกลัวมัน กลับกันเสียอีกเรื่องที่ไม่มีความแน่นอนต่าง ต่างในชีวิตของเรา เรามักไม่ค่อยจะกังวลกับมันและใช้เวลาไปกับมันอย่างเพลิดเพลินโดยไม่ได้ตะหนักด้วยซ้ำไปว่าเรื่องเหล่านั้นเป็นเรื่องที่ไม่แน่นอนทั้งสิ้น

เรื่องความตายเมื่อเป็นเรื่องที่แน่นอน เราควรที่จะครุ่นคิดถึงมันและเตรียมความพร้อมไว้ เมื่อพร้อมแล้วที่เหลือเราก็ใช้ชีวิตกับความไม่นอนที่เกิดขึ้นไปให้ดีที่สุด ใช้ชีวิตได้อย่างคุ้มค่า ทำประโยชน์ต่อตนเอง หรือโลกนี้ อย่าปล่อยให้ชีวิตที่เหลือนี้อยู่กับความไม่แน่นอน แล้วตายจากไปโดยที่ไม่ได้พบความจริงของชีวิตหรือแม้แต่หมดเวลาไปกับเรื่องไม่แน่นอนทั้งหลาย

//

แรงบันดาลใจจากการเสียชีวิตของคุณยาย ความเจ็บป่วยของเจ้านายที่ทำงาน การเห็นคนส่วนใหญ่ที่กลัวหรือเกรงเรื่องความตายโดยไม่กล้าจะพูดถึง รวมถึงเป็นเรื่องที่อยากเขียนมานานแล้ว — 2018 01 23 17:28

 

smart farm control

งานนี้ เป็นการประยุกต์ใช้

  • message passing (MQTT หรือ netpie.io) ในการรับส่งข้อมูลหรือคำสั่ง เช่น ข้อมูลความชื้น คำสั่งปิด หรือ เปิด โดยติดตั้งใช้งานบน Raspberry pi
  • smart switch or wifi switch ประกอบด้วย controller board (IoT board) เช่น ESP-8266, Relay เพื่อใช้เป็นสวิทช์ปิดเปิดอุปกรณ์ไฟฟ้า โดยการรับคำสั่งจาก MQTT Server
  • smart sensor or wifi sensor (temperature and relative humidity) เป็นการใช้ IoT Board กับ Electronic Sensor เช่น SHT30 ในการวัดค่าความชื้น อุณหภูมิ แล้วส่งข้อมูลไปยัง MQTT Server
  • web application (java script, angularjs, nodejs) เป็นส่วนติดต่อกับผู้ใช้งานและทำหน้าที่ในการรับค่าที่ส่งจาก smart sensor ผ่าน MQTT แล้วนำมาตัดสินใจในการทำงานตามเงื่อนไขที่ผู้ใช้ตั้งไว้ เช่น เมื่อค่าความชื้นต่ำกว่าที่กำหนด ให้สั่ง Smart Switch ทำงานเป็นต้น
  • wifi access point เป็นส่วนของระบบสื่อสัญญาณระหว่างอุปกรณ์ได้แก่ MQTT Server, smart switch, smart sensor

 

เมื่อนำมาทำงานประกอบร่วมกัน ก็จะได้ชุดควบคุมเพื่อใช้ควบคุมการรดน้ำ (พ่นหมอก) ในโรงเรือนเพาะปลูก โดยทำตามเงื่อนไขของ ความชื้น หรือ อุณหภูมิ หรือทั้งสองอย่าง

รายละเอียดของ source code

ส่วนของ User Interface (ui) และ smart sensor

ส่วนของ smart switch

ogofarm_ui

การทำงาน สามารถเลือกเงื่อนไข (Condition) ได้สามแบบคือ เลือกตรวจจับค่าความชื้น (RH) หรือ อุณหภูมิ (Temp) หรือ ทั้งความชื้นและอุณหภูมิ (RH&Temp) โดยความชื้นมีหน่วยเป็นร้อยละ (%) อุณหภูมิมีหน่วยเป็นองศาเซลเซียส (Degree Celsius) และเงื่อนไขการสั่งงาน smart switch เพื่อปิดหรือเปิด (ON/OFF)

การ ON/OFF นั้นจะสัมพันธ์โดยตรงกับค่า min ไม่ว่าจะเป็น min RH หรือ min Temp นั่นคือ เมื่อค่าที่วัดได้ต่ำกว่าค่า min smart switch จะทำงานตามที่ระบุใน ON/OFF เช่น ถ้าตำแหน่ง ON/OFF อยู่ ON โปรแกรมก็จะเป็นการสั่งให้ smart switch ทำงานเปิดไฟฟ้า relay contact จะอยู่ที่ตำแหน่ง NO (Normal Open) หรือเชื่อมระหว่าง Common กับ NO แต่ถ้าตำแหน่ง ON/OFF อยู่ที่ OFF เมื่อค่าที่วัดมาต่ำกว่าค่า min ก็จะเป็นการสั่งให้ smart switch ปิดการทำงาน relay contact จะอยู่ที่ตำแหน่ง NC (Normal Close)


if smart sensor data < min RH or min Temp

do action in ON/OFF

else if smart sensor data > max or min Temp

do toggle action in ON/OFF

ส่วนค่า max ของ RH หรือ Temperature นั่นจะทำงานตรงข้ามกับการเลือก ON/OFF นั่นคือเมื่อเลือกที่ ON เมื่อค่าที่วัดมาได้มากกว่าค่า max RH หรือ Temp. ก็จะเป็นการสั่งให้ smart switch ปิดการทำงานของอุปกรณ์ไฟฟ้า (เช่น ปั๊มน้ำ)

ในกรณีที่เลือกเงื่อนไข (condition) แบบ RH&Temp จะเป็นการทำงานด้วยสองเงื่อนไขของค่าที่วัดคือ ทั้งค่าความชื้นและอุณหภูมิ โดยค่า min จะทำงานสัมพันธ์กับ ON/OFF ด้านซ้าย ส่วนค่า max จะทำงานสัมพันธ์กับ ON/OFF ด้านขวา


if smart sensor data < min RH && min Temp

do action in Left ON/OFF

else if smart sensor data > max RH && max Temp

do action in Right ON/OFF

นอกจากนี้ ยังมีการตั้งค่าเวลา sensor timer (countdown) เพื่อตรวจสอบการส่งข้อมูลของ smart sensor ในกรณีที่มีข้อมูลส่งเข้ามา timer จะเริ่มนับใหม่ กรณีที่ไม่มีข้อมูลจาก smart sensor ส่งเข้ามาโปรแกรมจะทำการส่งคำสั่ง OFF ไปยัง smart switch เพื่อป้องกันการทำงานค้าง กรณีที่ไม่ได้รับข้อมูลจาก smart sensor

ในกรณีของการส่งข้อมูลไปเปิด smart switch จะมีการส่งค่าเวลาเพื่อให้ smart switch สามารถทำงานตามเวลาที่กำหนดแล้วปิดเอง (โดยไม่จำเป็นต้องดูเงื่อนไขของค่าที่ได้รับจาก smart sensor) เช่น เมื่อ ความชื้นต่ำกว่าที่ตั้งและกำหนดให้เปิด smart switch / smart switch จะถูกตั้งเวลาทำงาน 5 นาที (TIMEFACTOR) จากนั้นจะหยุดทำงาน แต่ถ้าในระหว่าง 5 นาทีนั้น เงื่อนไขของความชื้นมากกว่าที่กำหนด โปรแกรมสามารถส่งคำสั่งหยุดไปที่ smart switch ได้ทันที แต่ถ้าเงื่อนไขที่ได้รับจาก smart sensor ยังอยู่ในเงื่อนไข ON หลังจากที่ค่าเวลา ontimer ครบห้านาทีแล้ว โปรแกรมก็จะถูกบังคับให้ส่งการปิดไปยัง smart switch เพื่อป้องกันการทำงานเกินเวลาซึ่งอาจทำให้อุปกรณ์ เช่น ปั๊มน้ำเสียหายได้ และหลังจากส่งคำสั่งปิดไปที่ smart switch แล้ว โปรแกรมจะยังคงมีการตั้งค่าเวลาหน่วง delay (60 วินาที) เพื่อป้องกันการเปิดซ้ำในทันที ทั้งนี้ค่าการหน่วงเวลาเหล่านี้สามารถตั้งปรับเปลี่ยนได้ตามความเหมาะสม

ogofarm_diagram

ใน source code (version 1) เบื้องต้นนี้สามารถ ใช้งานกับ smart sensor 4 ชุด และ smart switch 4 ชุด โดยเงื่อนไขการทำงานผูกระหว่าง smart switch 1 ตัว สามารถทำงานจับคู่ได้กับ sensor หลายตัวได้ (1 to m) แต่ smart sensor จะจับคู่กับ smart switch ได้แค่ตัวใดตัวหนึ่ง (1 to 1)

smart sensor 1 ชุด จะทำงานทั้งวัดอุณหภูมิ วัดความชื้น สำหรับการสั่งงาน smart switch 1 ตัวเท่านั้นโปรแกรมยังไม่สามารถแยกการสั่ง smart switch ต่างกันได้ ด้วยเงื่อนไขของอุณหภูมิหรือความชื้น จาก smart sensor ตัวเดียวได้

ใน version 1.0

  • ยังไม่มีการเก็บข้อมูล เพื่อการดูย้อนหลังหรือนำไป plot graph (ว่าจะทำอยู่ รอก่อนนะ)
  • ยังไม่ได้ให้เชื่อมต่อออกอินเทอร์เน็ต เพราะใน farm ไม่มี internet  แต่ถ้าจะใช้ 3G 4G ก็ไม่มีปัญหาปรับแก้ไขเพิ่มเติมได้ เช่น นำค่าที่ได้ส่งออกไปเก็บไว้ที่ cloud เป็นต้น

Version 1.0 2017-11-21

Things Control

ThingsControl – control anything as you want.

ผมเริ่มหัดเขียน php, c เพื่อคุยกับ netpie เมื่อประมาณต้นเดือนมิถุนายน และหัดเขียน app inventor เมื่อราวราว 30 พฤษภาคม 2560

จนวันนี้ ใช้เวลาประมาณ 1 เดือนเต็มเต็ม งานที่ทำอยู่ก็สำเร็จในเบื้องต้น รวมเวลาสั่งของ (hardware) มาลองด้วย

แรงบันดาลใจก็มาจาก sonoff หรือ smart switch ที่บริษัท จีนทำขายไปทั่วโลกในราคาไม่แพง นอกเหนือจากนั้นอีกแรงขับหนึ่งก็คือ ความหวงวิชาของหน่วยงานรัฐ หรือแม้แต่คนในวงการเดียวกันเอง ถึงตอนนี้ ผมก็ยังคิดว่า ทำไมจะต้องมาเสียเวลาไป 1 เดือนเพื่อทำในสิ่งที่ คนอื่นก็ทำกันอยู่แล้ว และต้องมาทำซ้ำซ้ำกัน จริงอยู่นะ ว่า พอมาทำจริงจริงมันก็ไม่ได้ง่ายนัก ถ้าจะทำเพื่อให้ใช้งานได้จริง ไม่ใช่ทำแค่ ปิด เปิด หลอดไฟได้ แบบที่ ก็ทำกันมาหลายปี เวลาหัดทำ หัดใช้งาน ใหม่ใหม่ มันก็มีความยากอยู่พอสมควร แต่ถ้าจะคิดในมุม เรื่องพื้นฐานแบบนี้ ถ้าเผยแพร่ได้ มันจะช่วยให้วงการมันพัฒนาไปได้อีกมาก เพราะสาระของเรื่องนี้ มันไม่ได้อยู่ตรงนี้แล้ว มันต้องต่อยอดออกไปอีก ไม่ว่าจะเป็นเรื่อง big data หรือ machine learning ลำพังแค่จะทำตัวสร้างข้อมูลยังจะหวงกัน ก็ไม่ต้องไปถึงไหนกัน

ครับ ถ้าท่านหลงมาเจอ ก็ลองไปดูที่นี่ เผื่อจะสนใจ https://github.com/kaebmoo/thingscontrol

มันคืออุปกรณ์เปิด ปิด อุปกรณ์ไฟฟ้า ที่สั่งงานผ่าน smart phone (android) สั่งผ่าน internet โดยสามารถตั้งเวลาควบคุมการ เปิด ปิด หรือระยะเวลาที่ให้ทำงาน ได้

 

อ่าน/เขียน ข้อมูลไปยัง netpie ด้วย app inventor

screen_read_write_netpie_appinventor

 

blocks(1)

ใช้ผ่าน web component ที่อยู่ในหมวด connectivity ซึ่งใช้เรียก REST API ของ netpie โดยกำหนด uri คือ https://api.netpie.io/topic/ชื่อแอพที่สร้างในnetpie/ชื่อtopic?retain&authen=key:secret

เช่น https://api.netpie.io/topic/sControl/sensor/light?retain&auth=key:secret

ตรง key และ secret ใส่ key และ secret ที่ได้จากการสร้างแอพพลิเคชั่นจาก netpie

application_netpie

ลองส่งข้อมูลขึ้นด้วยการใส่ค่าใน textbox แล้วกดปุ่ม [post to netpie] โปรแกรมจะเรียก web method PUT Text เพื่อส่งค่า

ลองอ่านค่าจาก netpie ด้วยการกดปุ่ม [get value from netpie] โปรแกรมจะเรียก web method GET

ค่าที่ได้จาก netpie จะหน้าตาประมาณนี้

[{“topic”:”/sControl/sensor/light”,”payload”:”YES”,”lastUpdated”:1498810051,”retain”:true}]

จากนั้นจะมีการตรวจค่าใน Event GotText ก็ทำการตรวจสอบว่าสามารถอ่านค่าได้หรือไม่ (responseCode = 200) แล้วก็ทำการอ่านค่าโดยแปลงจาก json มาใส่ใน List (item) เพื่อใช้ค้นหา key ที่เราสนใจ ซึ่งในที่นี้จะอยู่ใน key ชื่อ payload

 

ตรง if else ที่ตรวจสอบค่าว่าเป็น PUT, หรือ GET เพื่อไม่ให้เกิด error เนื่องจากเวลา PUT netpie จะส่งค่า JSON กลับมาไม่เหมือนกับตอน GET จะได้แสดงผลถูก

ในตัวอย่างนี้มีการใช้ clock ทุก 5 วินาที (อย่าลืมตั้งค่า TimerInterval = 5000) เพื่ออ่านค่า ดังนั้นก็ไม่จำเป็นต้องกดปุ่ม [get value from netpie] ก็ได้ เพราะโปรแกรมจะอ่านค่าจาก netpie มาทุกทุก 5 วินาที เพื่อมาแสดงผล

ส่วนถ้าจะยัด microgear ลงไปใน component ของ app inventor นี่ อันนี้ขอผ่าน ฝีมือยังไม่ถึง รอเซียนทั้งหลายเขาทำมาละกัน 😛

download code

more information

ปล. app inventor มีออกชุดพัฒนาสำหรับ iot แล้วนะ ลองดูที่นี่ http://iot.appinventor.mit.edu

ใช้งาน bluetooth บน Raspberry Pi

ติดตั้งแบบ download source code และ compile เอง (ใช้เวลานานมากพอสมควรตอน compile)

อ่านที่ https://learn.adafruit.com/install-bluez-on-the-raspberry-pi/installation

download BlueZ Package http://www.bluez.org/download/

cd ~

wget http://www.kernel.org/pub/linux/bluetooth/bluez-5.45.tar.xz

tar xvf bluez-5.45.tar.xz

cd bluez-5.45

ติดตั้ง Lib ที่เกี่ยวข้อง


sudo apt-get update
sudo apt-get install -y libusb-dev libdbus-1-dev libglib2.0-dev libudev-dev libical-dev libreadline-dev

ทำการ compile


./configure

make

sudo make install

systemctl status bluetooth

sudo systemctl start bluetooth

sudo systemctl stop bluetooth

sudo systemctl enable bluetooth

sudo systemctl disable bluetooth

Enable Bluetooth Low Energy Features


sudo nano /lib/systemd/system/bluetooth.service

Enable the experimental features by adding –experimental to the ExecStart line.

sudo systemctl daemon-reload

sudo systemctl restart bluetooth

อันนี้อีกวิธี แบบไม่ต้อง compile เอง

https://github.com/EnableTech/raspberry-bluetooth-demo
sudo apt-get install pi-bluetooth
sudo apt-get install bluetooth bluez

sudo apt-get install python-bluez

sudo hciconfig hci0 piscan

sudo hciconfig hci0 name 'Device Name' [change your device name to something else you fancy]

sudo python /usr/share/doc/python-bluez/examples/simple/inquiry.py

Running the Bluetooth Server run the rfcomm-server example:

ก่อนใช้คำสั่งนี้ เพื่อ run rfcomm-server
อ่าน https://www.raspberrypi.org/forums/viewtopic.php?f=63&t=133263

run คำสั่ง sudo sdptool add SP แล้วเพิ่ม -C เข้าไปในไฟล์ /etc/systemd/system/dbus-org.bluez.service หลัง ‘bluetoothd’. จากนั้น Reboot. ไม่งั้นมันจะขึ้น error ประมาณนี้

Traceback (most recent call last):
File "/usr/share/doc/python-bluez/examples/simple/rfcomm-server.py", line 20, in <module>
profiles = [ SERIAL_PORT_PROFILE ],
File "/usr/lib/python2.7/dist-packages/bluetooth/bluez.py", line 176, in advertise_service
raise BluetoothError (str (e))
bluetooth.btcommon.BluetoothError: (2, 'No such file or directory')

sudo python /usr/share/doc/python-bluez/examples/simple/rfcomm-server.py

ลอง download โปรแกรม https://play.google.com/store/apps/details?id=es.pymasde.blueterm&hl=en

มาลองส่งข้อมูลจากโทรศัพท์ android ไปยัง raspberry pi ผ่าน bluetooth

เปิดโปรแกรมกด เรียกเมนู เพื่อค้นหาอุปกรณ์ตามที่เราตั้งชื่อ แล้วทำการเชื่อมต่อ ต่อได้ก็ลองพิมพ์ข้อความดูผลที่เกิดขึ้น

อ่านค่า / ส่งค่า netpie ด้วย C, cURL, json-c

การส่งค่า การอ่าน(รับ)ค่า 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 เยอะมาก ชอบตัวไหน ใช้ตัวไหนก็ตามลำบากครับท่าน ผม งมงม ตัวนี้แล้วใช้งานลองผิดไปหลายตลบแล้วมันได้ก็เลยใช้ใช้ไป)

ตัวอย่างที่ใช้งม

การติดตั้ง 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 และเป็นตัวอย่างการใช้งานต่าง ๆ