本例標題看起來似乎不凡, 好像是能與目前引領潮流的Alexa語音助理兼容的AI高檔貨呢? 但事實上我們啥事都沒幹! 只不過延續前一個案例「以ESP8266存取IoT數據資料庫」, 即我們知道怎麼用ESP-01透過REST API把DHT11溫/濕度值上傳ThingSpeak資料庫(並做讀取確認)之後, 改良一下Arduino程式再加個LCD顯示器罷了! 而關於Alexa Skill部份, 請參考前一個案例「讓Alexa到ThingSpeak讀取溫度-發出HTTPS GET請求」.
基本概念
整個概念如下圖所示, 我們外加一個LCD顯示, 再透過ESP-01發出HTTPS/GET請求並將最新的溫/濕度測量值放上ThingSpeak IoT資料庫的某個Channel. 因此在Alexa Skill Kit底層只要透過AWS/Lambda程式對該Channel發出HTTPS/GET請求, 即可將最新的溫/濕度測量值透過Alexa語音助理(Echo Dot)「說出來」. 其中Echo設備已連接WAN, 因此並不侷限在自己家裡.
為系統增加一個 LCD 螢幕
所有硬體以前案「以ESP8266存取IoT數據資料庫」為基礎再增加一個LCM6102模組, 整個系統如下圖所示. 關於LCD的驅動程式請參閱前例「LiquidCrystal_I2C (LCM6102) 顯示器」的測試程式, 該驅動程式可以在Arduino IDE v1.8.2 版本運作良好.
如開頭的標題, 我們想要實作一個Alexa Compliant溫度計, 所以我們可以將醜醜的外觀稍為美化/包裝一下, 如下圖. 它是標榜著AI商品的高檔貨啊! 左下方透露的一些紅光即ESP-01模組. 筆者算了一下, 增加成本應該可以不超過2塊美金(含ESP-01與LCM6102模組).
細部接線
系統接線融合了「以ESP8266存取IoT數據資料庫」與「LiquidCrystal_I2C (LCM6102) 顯示器」兩者, 新增的LCM6102模組接線部份為: GND(接Arduino GND)、VCC(接Arduino 5V輸出)、SDA(接Arduino A4)與SCL(接Arduino A5). 因此整個系統接線看起來如下圖, 其中省略了液晶顯示面板部份.
Arduino程式
筆者的Arduino IDE開發環境是1.8.2 ZIP免安裝版. 程式部份我們基於前案「以ESP8266存取IoT數據資料庫」, 仍然是完全透過AT命令達成, 因此沒有引用其它WiFi函式庫. 但相較於前案, 又再次做了整體的改良(其實是修正了一些之前預留的BUG). 其中connectWAN函式中請填入自己家中AP的SSID與PASSWORD, 而該函式一旦連線失敗時, 我們仍然可在Serial監控窗透過AT命令訪問ESP-01以釐清問題.
我們也將訪問ThingSpeak資料庫所需的REST請求(HTTPS/GET)打包成queryThingSpeak與updateThingSpeak兩個函式, 其中包含了所需的ESP-01 A T命令與步驟以建立HTTPS/GET請求.
#include <SoftwareSerial.h>
#include "DHT.h" // package required from https://github.com/adafruit/DHT-sensor-library
// !NOTE! You may need to enlarge the serial RX buffer size to 128 for baud rate 115200bps, described in the
// include file arduino-
// #define _SS_MAX_RX_BUFF 128 // software serial RX buffer size (default is 64 bytes)
// This driver works well with Arduino v
#include <LiquidCrystal_I
#define DEBUG 0
DHT dht(5, DHT11); // digital pin5 connected to the DHT11
SoftwareSerial ESP(3, 2); // ESP8266 ESP-01: Tx, Rx
// Addr, En, Rw, Rs, d4,d5,d6,d7 backlight, polarity
LiquidCrystal_I
bool swState = 0; // switch to enable/disable hunidity and temperature update
bool wifiState = 0; // check wifi connection
int dataCnt = 0; // times to access ThingSpeak IoT database
void setup() {
Serial.begin(115200);
pinMode(7, INPUT_PULLUP); // set pin7 as the switch button
// initialize LCD
lcd.begin(16, 2); // init LCD : 2 rows x 16 columns
lcd.backlight();
lcd.clear();
lcd.print("Hello World!");
delay(2000);
lcd.noBacklight(); // turn off back light and wait for user input
// initialize DHT11
dht.begin();
delay(1000); // Sensor readings may be up to 2 seconds
Serial.println("DHT11 start ...");
// initialize ESP-01
ESP.begin(115200);
delay(5000);
Serial.println("ESP-01 start ...");
#if !DEBUG
Serial.println("try to connect ESP-01 to WAN (through home AP) ...");
if ( connectWAN(SSID, PASSWORD) ) { // join ESP-01 to WAN through your home AP
wifiState = 1;
Serial.println("press switch button to start ...");
} else {
Serial.println("fail to join WAN ...");
}
#endif
}
bool connectWAN(char* ssidName, char* passWord) {
int status = false;
String joinCmd = "AT+CWJAP=\"" + String(ssidName) + "\",\"" + String(passWord) + "\"\r\n";
sendATcmd("AT+RST\r\n", 2000, DEBUG); // reset ESP-01
sendATcmd("AT+CWMODE=1\r\n", 1000, DEBUG); // config ESP-01 as STA mode
sendATcmd(joinCmd, 1000, DEBUG); // wait the server to ack "OK"
for (int loop=0; loop<10; loop++ ) { // wait AP's response
Serial.print(".");
if ( ESP.find("OK") ) {
status = true;
Serial.println("join ESP-01 to WAN success ...");
break;
}
setDelay(1000);
}
return status;
}
String sendATcmd(String cmd, unsigned int msDelay, bool dbg) {
String espMsg = "";
unsigned long timeout = millis() + msDelay;
Serial.print(">>ESP-01: " + String(cmd)); // debug
ESP.print(cmd); // send AT command to ESP-01
// allow delay > msDelay if ESP-01 still receives message
while ( ESP.available() || millis()<timeout ) {
while ( ESP.available() ) {
char ch = ESP.read();
espMsg += ch;
if (dbg) Serial.write(ch); // debug only
}
}
return espMsg;
}
// query data from ThingSpeak
String queryThingSpeak(char* ipName, char* channelName, char* apiKey) {
String espResp; // receive response from ThingSpeak
String tcpCmd = "AT+CIPSTART=\"TCP\",\"" + String(ipName) + "\",80\r\n";
String getCmd = "GET /channels/" + String(channelName) +
"/feeds/last.json?key=" + String(apiKey) + "\r\n";
String cipCmd = "AT+CIPSEND=" + String(getCmd.length()) + "\r\n";
sendATcmd(tcpCmd.c_str(), 1000, DEBUG); // ask HTTP connection, and wait the server to ack "OK"
sendATcmd(cipCmd.c_str(), 2000, DEBUG); // start CIPSEND request, and wait the server to ack "OK"
espResp = sendATcmd(getCmd.c_str(), 2000, DEBUG); // send GET request and receive response
return espResp;
}
// sample message received through ESP-01
// +IPD,82:{"created_at":"
// update data to ThingSpeak
String updateThingSpeak(char* ipName, char* apiKey, float humidity, float temperature) {
String espResp; // receive response from ThingSpeak
String tcpCmd = "AT+CIPSTART=\"TCP\",\"" + String(ipName) + "\",80\r\n";
String getCmd = "GET /update?key=" + String(apiKey) +
"&field1=" + String(humidity) +
"&field2=" + String(temperature) + "\r\n";
String cipCmd = "AT+CIPSEND=" + String(getCmd.length()) + "\r\n";
sendATcmd(tcpCmd.c_str(), 1000, DEBUG); // ask HTTP connection, and wait the server to ack "OK"
sendATcmd(cipCmd.c_str(), 2000, DEBUG); // start CIPSEND request, and wait the server to ack "OK"
espResp = sendATcmd(getCmd.c_str(), 2000, DEBUG); // end GET request and receive response
return espResp;
}
void setDelay(unsigned int ms) {
unsigned long timeout = ms + millis();
while ( millis()<timeout ) {
checkSwitchButton();
}
}
bool checkSwitchButton() {
if ( digitalRead(7)==LOW ) {
swState = !swState;
Serial.println("\nInterrupt: Switch Button = " + String(swState) );
delay(300); // debounce delay (depending on your behavior)
}
return swState;
}
void loop() {
#if DEBUG // ESP-01 debug by using AT command through serial console
if ( Serial.available() ) {
ESP.write( Serial.read() );
}
if ( ESP.available() ) {
Serial.write( ESP.read() );
}
#else
if ( swState && wifiState && dataCnt < 100 ) {
// get DHT11 data
float h = dht.readHumidity(); // reading temperature or humidity takes about 250 milliseconds
float t = dht.readTemperature(); // read temperature as Celsius (the default)
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0); lcd.print(String(h) + "%"); // humidity
lcd.setCursor(0, 1); lcd.print(String(t) + "C, DHT11"); // temperature
// update humidity and temperature to ThingSpeak
Serial.println("update data to ThingSpeak: " + String(++dataCnt) );
updateThingSpeak(
"184.106.153.149", // ThingSpeak IP
Write_APIKEY, // write API-KEY (DHT11 channel)
h, // humidity
t // temperature
);
#if 0 // query data from ThingSpeak
Serial.println("query data from ThingSpeak channel...");
String msg = queryThingSpeak(
"184.106.153.149", // ThingSpeak IP
channelID, // channelId (DHT11 channel)
Read_APIKEY // read API-KEY (DHT11 channel)
);
#endif
Serial.println("press switch to stop (will start after one minute) ...");
if ( swState )
setDelay(10000); // update humidity and temperature 10 second
}
#endif
setDelay(500); // monitor hardware interrupt
}
|
每建立一次資料更新(updateThingSpeak)的請求筆者預設約5000m s時間延遲, 讀者可以自行調整時間, 也以可以將筆者註解掉的查詢(queryThingSpeak)請求在編譯時打開, 以確保所建立的資料完整性. 迴圈大約等待10秒之後才會再次重新建立ThingSpeak的訪問, 期間也可透過硬體中斷暫停, 並在上傳最多100筆資料之後結束.
測試影片
筆者: 根據目前DHT11的讀數, 溫度是31度, 濕度是58%.
(註: Arduino程式以每隔約15秒更新溫濕度值到ThingSpeak一次)
筆者: Alexa, ask information
(註: information是筆者寫的Alexa Skill以存取ThingSpeak)
Alexa: Welcome! I am ready to access ThingSpeak.
筆者: get the temperature
Alexa: The temperature is 31 degree, and the humidity is 58 %.
(註: 答案正確, 但… 天氣真的好熱啊! 汗…)