Month: November 2017
WeMos Smart Car demo
WeMos (d2) Home Automation
TinyWebDB-APIを利用した、Home Automationの例。
ハードウェア
“IoT-Cloud-Mobile Study Kit”を利用
データ送信
下記のは操作中、数分起き温度、気圧センサーのデータをTinyWebDB-APIテストサーバ(http://tinydb.ml/api/)へ送信する。
送信したデータは、http://tinydb.ml/status/で確認できる。
データ受信
スマートフォンからLED On/Off の指令は受信すると、ESP8266内蔵LEDは点/滅可能になった。
ソースコード
// Sample Arduino Json Web Client // Downloads and parse http://jsonplaceholder.typicode.com/users/1 // // Copyright Benoit Blanchon 2014-2017 // MIT License // // Arduino JSON library // https://bblanchon.github.io/ArduinoJson/ // If you like this project, please add a star! #include <ArduinoJson.h> #include <Arduino.h> #include <Adafruit_BMP280.h> #define BMP_SCK 13 #define BMP_MISO 12 #define BMP_MOSI 11 #define BMP_CS 10 Adafruit_BMP280 bmp; // I2C //Adafruit_BMP280 bmp(BMP_CS); // hardware SPI //Adafruit_BMP280 bmp(BMP_CS, BMP_MOSI, BMP_MISO, BMP_SCK); #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define OLED_RESET 0 // GPIO0 Adafruit_SSD1306 OLED(OLED_RESET); #include <ESP8266WiFi.h> #include <ESP8266HTTPClient.h> #define USE_SERIAL Serial WiFiClient client; const char* resource = "http://tinywebdb.cf/api/"; // http resource const unsigned long BAUD_RATE = 9600; // serial connection speed const unsigned long HTTP_TIMEOUT = 10000; // max respone time from server const size_t MAX_CONTENT_SIZE = 512; // max size of the HTTP response #include "WiFiManager.h" //https://github.com/tzapu/WiFiManager void configModeCallback (WiFiManager *myWiFiManager) { OLED.println("Entered config mode"); OLED.println(WiFi.softAPIP()); //if you used auto generated SSID, print it OLED.println(myWiFiManager->getConfigPortalSSID()); OLED.display(); //output 'display buffer' to screen } HTTPClient http; void setup() { OLED.begin(); OLED.clearDisplay(); //Add stuff into the 'display buffer' OLED.setTextWrap(false); OLED.setTextSize(1); OLED.setTextColor(WHITE); OLED.setCursor(0,0); delay(10); USE_SERIAL.begin(115200); // USE_SERIAL.setDebugOutput(true); USE_SERIAL.println(); USE_SERIAL.println(); USE_SERIAL.println(); OLED.println("wifiManager autoConnect..."); OLED.display(); //output 'display buffer' to screen //WiFiManager //Local intialization. Once its business is done, there is no need to keep it around WiFiManager wifiManager; //reset settings - for testing //wifiManager.resetSettings(); //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode wifiManager.setAPCallback(configModeCallback); //fetches ssid and pass and tries to connect //if it does not connect it starts an access point with the specified name //here "AutoConnectAP" //and goes into a blocking loop awaiting configuration if(!wifiManager.autoConnect()) { Serial.println("failed to connect and hit timeout"); //reset and try again, or maybe put it to deep sleep ESP.reset(); delay(1000); } //if you get here you have connected to the WiFi Serial.println("connected...yeey :)"); if (!bmp.begin(0x76)) { OLED.println("Could not find BMP280"); OLED.display(); //output 'display buffer' to screen while (1) {} } } void OLED_show() { OLED.clearDisplay(); OLED.setCursor(0,0); // Print the IP address OLED.print("http://"); OLED.print(WiFi.localIP()); OLED.println("/"); OLED.setCursor(0,8); OLED.print("Temp = "); OLED.print(bmp.readTemperature()); OLED.println(" Celsius"); // set the cursor to column 0, line 1 // (note: line 1 is the second row, since counting begins with 0): OLED.setCursor(0,16); OLED.print("Pres = "); OLED.print(bmp.readPressure()); OLED.println(" Pascal "); OLED.display(); //output 'display buffer' to screen } void loop() { OLED_show(); get_TinyWebDB("Led1"); delay(10000); sensor_TinyWebDB(); delay(10000); } void sensor_TinyWebDB() { int httpCode; char tag[32]; char value[128]; // read values from the sensor float pressure = bmp.readPressure(); float temperature = bmp.readTemperature(); const size_t bufferSize = JSON_ARRAY_SIZE(2) + JSON_OBJECT_SIZE(3); DynamicJsonBuffer jsonBuffer(bufferSize); JsonObject& root = jsonBuffer.createObject(); root["sensor"] = "bmp280"; root["temperature"] = String(temperature); root["pressure_hpa"] = String(pressure); root.printTo(value); USE_SERIAL.printf("[TinyWebDB] %s\n", value); USE_SERIAL.printf("ESP8266 Chip id = %08X\n", ESP.getChipId()); sprintf(tag, "esp8266-%06x", ESP.getChipId()); httpCode = TinyWebDBStoreValue(tag, value); // httpCode will be negative on error if(httpCode > 0) { // HTTP header has been send and Server response header has been handled USE_SERIAL.printf("[HTTP] POST... code: %d\n", httpCode); if(httpCode == HTTP_CODE_OK) { TinyWebDBValueStored(); } } else { USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); TinyWebDBWebServiceError(http.errorToString(httpCode).c_str()); } http.end(); delay(10000); } void get_TinyWebDB(const char* tag0) { int httpCode; char tag[32]; char value[128]; httpCode = TinyWebDBGetValue(tag0); // httpCode will be negative on error if(httpCode > 0) { // HTTP header has been send and Server response header has been handled USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode); if(httpCode == HTTP_CODE_OK) { String payload = http.getString(); const char * msg = payload.c_str(); USE_SERIAL.println(payload); if (TinyWebDBreadReponseContent(tag, value, msg)){ TinyWebDBGotValue(tag, value); } } } else { USE_SERIAL.printf("[HTTP] POST... failed, error: %s\n", http.errorToString(httpCode).c_str()); TinyWebDBWebServiceError(http.errorToString(httpCode).c_str()); } http.end(); delay(10000); } int TinyWebDBWebServiceError(const char* message) { } // ---------------------------------------------------------------------------------------- // Wp TinyWebDB API // Action URL Post Parameters Response // Get Value {ServiceURL}/getvalue tag JSON: ["VALUE","{tag}", {value}] // ---------------------------------------------------------------------------------------- int TinyWebDBGetValue(const char* tag) { char url[64]; sprintf(url, "%s%s?tag=%s", resource, "getvalue/", tag); USE_SERIAL.printf("[HTTP] %s\n", url); // configure targed server and url http.begin(url); USE_SERIAL.print("[HTTP] GET...\n"); // start connection and send HTTP header int httpCode = http.GET(); return httpCode; } int TinyWebDBGotValue(const char* tag, const char* value) { USE_SERIAL.printf("[TinyWebDB] %s\n", tag); USE_SERIAL.printf("[TinyWebDB] %s\n", value); OLED.printf("[TinyWebDB] %s:%s\n", tag, value); OLED.display(); //output 'display buffer' to screen return 0; } // ---------------------------------------------------------------------------------------- // Wp TinyWebDB API // Action URL Post Parameters Response // Store A Value {ServiceURL}/storeavalue tag,value JSON: ["STORED", "{tag}", {value}] // ---------------------------------------------------------------------------------------- int TinyWebDBStoreValue(const char* tag, const char* value) { char url[64]; sprintf(url, "%s%s", resource, "storeavalue"); USE_SERIAL.printf("[HTTP] %s\n", url); // POST パラメータ作る char params[128]; sprintf(params, "tag=%s&value=%s", tag, value); USE_SERIAL.printf("[HTTP] POST %s\n", params); // configure targed server and url http.begin(url); // start connection and send HTTP header http.addHeader("Content-Type", "application/x-www-form-urlencoded"); int httpCode = http.POST(params); String payload = http.getString(); //Get the response payload Serial.println(payload); //Print request response payload http.end(); return httpCode; } int TinyWebDBValueStored() { return 0; } // Parse the JSON from the input string and extract the interesting values // Here is the JSON we need to parse // [ // "VALUE", // "LED1", // "on", // ] bool TinyWebDBreadReponseContent(char* tag, char* value, const char* payload) { // Compute optimal size of the JSON buffer according to what we need to parse. // See https://bblanchon.github.io/ArduinoJson/assistant/ const size_t BUFFER_SIZE = JSON_OBJECT_SIZE(3) // the root object has 3 elements + MAX_CONTENT_SIZE; // additional space for strings // Allocate a temporary memory pool DynamicJsonBuffer jsonBuffer(BUFFER_SIZE); // JsonObject& root = jsonBuffer.parseObject(payload); JsonArray& root = jsonBuffer.parseArray(payload); JsonArray& root_ = root; if (!root.success()) { Serial.println("JSON parsing failed!"); return false; } // Here were copy the strings we're interested in strcpy(tag, root_[1]); // "led1" strcpy(value, root_[2]); // "on" return true; } // Pause for a 1 minute void wait() { Serial.println("Wait 60 seconds"); delay(60000); }
参考:
- TinyWebDB-API : https://wordpress.org/plugins/tinywebdb-api/
- https://techtutorialsx.com/2016/07/21/esp8266-post-requests/
WeMos (d1) RESTful API
JSONデータの扱いできて、今度RESTful APIに取り込む。まずRESTful APIのGETとPOSTを実現する。自作のTinyWebDB-APIプラグインでテストすることにした。
GETは簡単にできたが、POSTがなかなかうまくいかない。受信したデータは空になっている。いろいろGoogleして、ソースを書き換えてもうまくいかない、今度サーバ側様子をみると思い、Cloud9でデバッグしようとしても、なんとCloud9で弾けられ。普通のVPSでデバッグを続けてるうちに、自信喪失、TinyWebDB-APIに問題があると懐疑的に、cURLで検証しても、受信したデータは空。困った。
Postmanで試したら、なんとうまくいく。何か違うかな?簡単のHTMLフォームもうまくいく。やはり送信側ESP8266プログラムの問題。さらにGoogleして、ソースを書き換えていく。
一週間苦労して、ついに成功。
簡単に利用するために、TinyWebDB-APIライブラリを自作すると考える。
WeMos Smart Car Motor Shield
学園祭のため、IoTぼいのスマート車を作る。
しかし、注文したMotor Shield とは、違うもの(base only)が来たので、急遽自作する。
そのためのコントロールするAppも作りたいが、時間がなくって、ネットからWebページでコントロールするものを探して、沢山手直して、動くようになった。
車数台用意して、各車のIPは固定にしたいので、WiFiManager使わない方法をとった。
Webページでコントロールするから、遅延は目たつ。
時間があったら、ちゃんとアクセルペダルなど追加して、アプリの形にしたい。
// include libraries #include <ESP8266WiFi.h> #include <ESP8266WebServer.h> #define motor_lf D3 #define motor_lb D4 #define motor_rf D6 #define motor_rb D5 // configure server ESP8266WebServer server(80); const char *form = "<!DOCTYPE HTML>" "<meta name='viewport' content='width=device-width'>" "<html>" "<center><form action='/'>" "<button name='dir' type='submit' value='4'>Forward</button><p>" "<button name='dir' type='submit' value='1'>Left</button> " "<button name='dir' type='submit' value='2'>Right</button><p>" "<button name='dir' type='submit' value='3'>Reverse</button><p><p>" "<button name='dir' type='submit' value='5'>Stop</button>" "</form></center>" "</html>"; void stop(void) { analogWrite(motor_lf, 0); analogWrite(motor_lb, 0); analogWrite(motor_rf, 0); analogWrite(motor_rb, 0); } void forward(void) { analogWrite(motor_lf, 1023); analogWrite(motor_lb, 0); analogWrite(motor_rf, 1023); analogWrite(motor_rb, 0); } void backward(void) { analogWrite(motor_lf, 0); analogWrite(motor_lb, 1023); analogWrite(motor_rf, 0); analogWrite(motor_rb, 1023); } void left(void) { analogWrite(motor_lf, 0); analogWrite(motor_lb, 0); analogWrite(motor_rf, 1023); analogWrite(motor_rb, 0); } void right(void) { analogWrite(motor_lf, 1023); analogWrite(motor_lb, 0); analogWrite(motor_rf, 0); analogWrite(motor_rb, 0); } void handle_form() { // only move if we submitted the form if (server.arg("dir")) { // get the value of request argument "dir" int direction = server.arg("dir").toInt(); // chose direction switch (direction) { case 1: left(); break; case 2: right(); break; case 3: backward(); break; case 4: forward(); break; case 5: stop(); break; } // move for 300ms, gives chip time to update wifi also delay(300); } // in all cases send the response server.send(200, "text/html", form); } void setup() { // connect to wifi network WiFi.begin("uislab003", "**password**"); // static ip, gateway, netmask WiFi.config(IPAddress(192,168,11,10), IPAddress(192,168,11,1), IPAddress(255,255,255,0)); // connect while (WiFi.status() != WL_CONNECTED) { delay(200); } // set up the callback for http server server.on("/", handle_form); // start the webserver server.begin(); pinMode(motor_lf, OUTPUT); // pinMode(motor_lb, OUTPUT); // pinMode(motor_rf, OUTPUT); // pinMode(motor_rb, OUTPUT); // } void loop() { // check for client connections server.handleClient(); }
WeMos Smart Car G1
WeMos (c6) Thingspeak
数回JSON関連の実験をしたが、いざTinyWebDBのAPIの実験を始まると、また引っかかるところが多い。
色々と検索してところ、ThingspeakのAPIサンプルが見つかったので、ちょっと曲がり道して試すことに。
センサーの温度と気圧をThingspeakにアップして、動きを見て見る。
まずThingspeakのアカウントを申請して、THINGSPEAK_API_KEYを取得する。
https://thingspeak.com/users/sign_up
次はサンプルを見ながら、プログラミング。
#include <Adafruit_BMP280.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define OLED_RESET 0 // GPIO0 Adafruit_SSD1306 OLED(OLED_RESET); #define BMP_SCK 13 #define BMP_MISO 12 #define BMP_MOSI 11 #define BMP_CS 10 Adafruit_BMP280 bmp; // I2C //Adafruit_BMP280 bmp(BMP_CS); // hardware SPI //Adafruit_BMP280 bmp(BMP_CS, BMP_MOSI, BMP_MISO, BMP_SCK); #include <ESP8266WiFi.h> #include <ESP8266HTTPClient.h> /*************************** * Begin Settings **************************/ const char* host = "http://api.thingspeak.com"; const char* THINGSPEAK_API_KEY = "***********"; // Update every 600 seconds = 10 minutes. Min with Thingspeak is ~20 seconds const int UPDATE_INTERVAL_SECONDS = 600; //needed for library #include <DNSServer.h> #include "WiFiManager.h" //https://github.com/tzapu/WiFiManager void configModeCallback (WiFiManager *myWiFiManager) { Serial.println("Entered config mode"); Serial.println(WiFi.softAPIP()); //if you used auto generated SSID, print it Serial.println(myWiFiManager->getConfigPortalSSID()); } /*************************** * End Settings **************************/ void setup() { OLED.begin(); OLED.clearDisplay(); //Add stuff into the 'display buffer' OLED.setTextWrap(false); OLED.setTextSize(1); OLED.setTextColor(WHITE); OLED.setCursor(0,0); delay(10); Serial.begin(115200); delay(10); // We start by connecting to a WiFi network // Connect to WiFi network OLED.println("wifiManager autoConnect..."); OLED.display(); //output 'display buffer' to screen //WiFiManager //Local intialization. Once its business is done, there is no need to keep it around WiFiManager wifiManager; //reset settings - for testing //wifiManager.resetSettings(); //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode wifiManager.setAPCallback(configModeCallback); //fetches ssid and pass and tries to connect //if it does not connect it starts an access point with the specified name //here "AutoConnectAP" //and goes into a blocking loop awaiting configuration if(!wifiManager.autoConnect()) { Serial.println("failed to connect and hit timeout"); //reset and try again, or maybe put it to deep sleep ESP.reset(); delay(1000); } // Print the IP address OLED.print("http://"); OLED.print(WiFi.localIP()); OLED.println("/"); OLED.println("WiFi connected"); OLED.display(); //output 'display buffer' to screen //if you get here you have connected to the WiFi Serial.println("connected...yeey :)"); if (!bmp.begin(0x76)) { OLED.println("Could not find BMP180 or BMP085 sensor at 0x77"); OLED.display(); //output 'display buffer' to screen while (1) {} } } void loop() { // read values from the sensor float pressure = bmp.readPressure(); float temperature = bmp.readTemperature(); // We now create a URI for the request String url = host; url += "/update?api_key="; url += THINGSPEAK_API_KEY; url += "&field1="; url += String(temperature); url += "&field2="; url += String(pressure); Serial.print("Requesting URL: "); Serial.println(url); HTTPClient http; Serial.print("[HTTP] begin...\n"); // configure targed server and url http.begin(url); Serial.print("[HTTP] GET...\n"); // start connection and send HTTP header int httpCode = http.GET(); if(httpCode == HTTP_CODE_OK) { String buffer = http.getString(); Serial.println(buffer); } Serial.println("closing connection"); // Go back to sleep. If your sensor is battery powered you might // want to use deep sleep here delay(1000 * UPDATE_INTERVAL_SECONDS); }
こちら問題なくデータの蓄積ができた。
WeMos (c5) JSON exchange rate
WeMosをRESPのクライアントとして機能するため、JSONと、HTTPClientを検証する。
前回ArduinoJsonというライブラリを使用したので、ESP8266ならではのWiFi機能を使い、インターネットから情報を取得して表示させてみる。
JSON データ形式
為替レートの情報は、
Foreign exchange rates and currency conversion JSON API
から取得する。
ドル円レートの情報を取得する場合、URLはhttp://api.fixer.io/latest?base=USD&symbols=JPY
応答は下記の通り
HTTP/1.1 200 OK Server: nginx/1.13.6 Date: Sat, 04 Nov 2017 13:40:46 GMT Content-Type: application/json Content-Length: 57 Connection: close Cache-Control: public, must-revalidate, max-age=900 Last-Modified: Fri, 03 Nov 2017 00:00:00 GMT Vary: Origin X-Content-Type-Options: nosniff {"base":"USD","date":"2017-11-03","rates":{"JPY":113.94}} closing connection
プログラム
下記の処理をする
- WiFiManagerでWiFi自動接続
- get_exchange_rate処理
- api.fixer.io JSONデータ取得
- parse json data
- OLEDへ表示
- 60秒待ち
- 2へ続き
#include <ESP8266WiFi.h> #include <ESP8266HTTPClient.h> #include <ArduinoJson.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define OLED_RESET 0 // GPIO0 Adafruit_SSD1306 OLED(OLED_RESET); const char* URL = "http://api.fixer.io/latest?base=USD&symbols=JPY"; // http resource //sample json data used in this sketch // {"base":"USD","date":"2017-11-03","rates":{"JPY":113.94}} //needed for library #include <DNSServer.h> #include <ESP8266WebServer.h> #include "WiFiManager.h" //https://github.com/tzapu/WiFiManager void configModeCallback (WiFiManager *myWiFiManager) { Serial.println("Entered config mode"); Serial.println(WiFi.softAPIP()); //if you used auto generated SSID, print it Serial.println(myWiFiManager->getConfigPortalSSID()); } void setup() { OLED.begin(); OLED.clearDisplay(); //Add stuff into the 'display buffer' OLED.setTextWrap(false); OLED.setTextSize(1); OLED.setTextColor(WHITE); OLED.setCursor(0,0); delay(10); Serial.begin(115200); Serial.println(""); delay(10); // Connect to WiFi network OLED.println("wifiManager autoConnect..."); OLED.display(); //output 'display buffer' to screen //WiFiManager //Local intialization. Once its business is done, there is no need to keep it around WiFiManager wifiManager; //reset settings - for testing //wifiManager.resetSettings(); //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode wifiManager.setAPCallback(configModeCallback); //fetches ssid and pass and tries to connect //if it does not connect it starts an access point with the specified name //here "AutoConnectAP" //and goes into a blocking loop awaiting configuration if(!wifiManager.autoConnect()) { Serial.println("failed to connect and hit timeout"); //reset and try again, or maybe put it to deep sleep ESP.reset(); delay(1000); } // Print the IP address OLED.print("http://"); OLED.print(WiFi.localIP()); OLED.println("/"); OLED.println("WiFi connected"); OLED.display(); //output 'display buffer' to screen //if you get here you have connected to the WiFi Serial.println("connected...yeey :)"); } void get_exchange_rate() { const int BUFFER_SIZE = JSON_OBJECT_SIZE(4) + JSON_ARRAY_SIZE(1); StaticJsonBuffer<BUFFER_SIZE> jsonBuffer; HTTPClient http; Serial.print("[HTTP] begin...\n"); // configure targed server and url http.begin(URL); Serial.print("[HTTP] GET...\n"); // start connection and send HTTP header int httpCode = http.GET(); if(httpCode != HTTP_CODE_OK) { return; } String buffer = http.getString(); Serial.println(buffer); //parse json data char json[buffer.length() + 1]; buffer.toCharArray(json, sizeof(json)); Serial.println(json); JsonObject& root = jsonBuffer.parseObject(json); if (!root.success()) { Serial.println("parseObject() failed"); return; } const char* date = root["date"]; Serial.println(date); const char* base = root["base"]; Serial.println(base); JsonObject& rates = root["rates"]; rates.printTo(Serial); Serial.println(); const char* rate = rates["JPY"]; Serial.println(rate); Serial.println(); Serial.println("closing connection"); OLED.clearDisplay(); OLED.setCursor(0,0); // Print the IP address OLED.print("http://"); OLED.print(WiFi.localIP()); OLED.println("/"); OLED.setCursor(0, 16); OLED.print(date); OLED.setCursor(0, 24); OLED.print("JPY/"); OLED.print(base); OLED.print(": "); OLED.print(rate); OLED.display(); //output 'display buffer' to screen } void loop() { get_exchange_rate(); delay(60000); }
参考
- http://blog.boochow.com/article/425016458.html — ESP8266版Arduinoでネットから情報を取ってきてLCDに表示する