繼續上一篇~
ESP8266 一次就上手 / Google Script + Google試算表 =股票機 (二)
處理好 GoogleScript 以及Google試算表,接下來就是把程式寫到ESP8266
由於使用 GoogleScript 取資料必須走https的協議,所以Arduino IDE 要安裝HTTPSRedirect 這個程式庫(必須手動安裝)
可以從這下載最新的:HTTPSRedirect
手動將.ZIP加入程式庫內
好了這樣就可以正常使用了~~
完整的code 如下,很多是許多段加在一起的,沒有特別在整理過,所以有可能很多是贅述的,畢竟花了很多時間反覆在測試,請大家包涵一下~~
#include "ESP8266WiFi.h" #include "HTTPSRedirect.h" #include "DNSServer.h" //Local DNS Server used for redirecting all rs to the configuration portal #include "ESP8266WebServer.h" //Local WebServer used to serve the configuration portal #include "WiFiManager.h" #include "ArduinoJson.h" #include "DebugMacros.h" #include "string.h" #include "WiFiUdp.h" #include "Time.h" #include "TimeAlarms.h" char copy[512]; unsigned int localPort=2390; //local port to listen for UDP packets IPAddress timeServerIP; //time.nist.gov NTP server address const char* ntpServerName="time.nist.gov"; //NTP Server host name const int NTP_PACKET_SIZE=48; //NTP timestamp resides in the first 48 bytes of packets byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets WiFiUDP udp; //UDP instance to let us send and receive packets over UDP int count=0; int w2=0; const int pressPin = 0; String msg1="歡迎使用股票機"; static const unsigned char PROGMEM signal0[] = { 0x7F, 0x00, 0x00, 0x49, 0x00, 0x04, 0x2A, 0x00, 0x04, 0x9C, 0x64, 0x04, 0x88, 0x95, 0x04, 0x88, 0x96, 0x00, 0x88, 0x64, 0x04, 0x00, 0x00, 0x00, }; static const unsigned char PROGMEM signal1[] = { 0x7F, 0x00, 0x00, 0x49, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00, 0x00, 0x00, }; static const unsigned char PROGMEM signal2[] = { 0x7F, 0x00, 0x00, 0x49, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x08, 0x0C, 0x00, 0x08, 0x0C, 0x00, 0xC8, 0x0C, 0x00, 0x00, 0x00, 0x00, }; static const unsigned char PROGMEM signal3[] = { 0x7F, 0x00, 0x00, 0x49, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x1C, 0xC0, 0x00, 0x08, 0xCC, 0x00, 0x08, 0xCC, 0x00, 0xC8, 0xCC, 0x00, 0x00, 0x00, 0x00, }; static const unsigned char PROGMEM signal4[] = { 0x7F, 0x00, 0x00, 0x49, 0x00, 0x00, 0x2A, 0x00, 0x0C, 0x1C, 0xC0, 0x0C, 0x08, 0xCC, 0x0C, 0x08, 0xCC, 0x0C, 0xC8, 0xCC, 0x0C, 0x00, 0x00, 0x00, }; static const unsigned char PROGMEM signal5[] = { 0x7F, 0x00, 0x00, 0x49, 0x00, 0xC0, 0x2A, 0x00, 0xCC, 0x1C, 0xC0, 0xCC, 0x08, 0xCC, 0xCC, 0x08, 0xCC, 0xCC, 0xC8, 0xCC, 0xCC, 0x00, 0x00, 0x00, }; const char* host = "script.google.com"; const char *GScriptId = "xxxxxxxxxx"; // Google script ID const char *SheetId = "xxxxxxxxxx";// Google sheet ID const int httpsPort = 443; int stocknumber = 2; int totalnumber = 13;//設定幾檔股票 HTTPSRedirect* client = nullptr ; unsigned int free_heap_before = 0; unsigned int free_stack_before = 0; //U8g2 #include #ifdef U8X8_HAVE_HW_SPI #include #endif #ifdef U8X8_HAVE_HW_I2C #include #endif U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, /* reset=*/16, /* clock=*/ 5, /* data=*/ 4); // pin remapping with ESP8266 HW I2C void setup() { // initialize the Serial Serial.begin(115200); Serial.flush(); pinMode(pressPin, INPUT_PULLUP); free_heap_before = ESP.getFreeHeap(); free_stack_before = ESP.getFreeContStack(); Serial.printf("Free heap: %u\n", free_heap_before); Serial.printf("Free stack: %u\n", free_stack_before); Serial.flush(); //U8g2 u8g2.begin(); u8g2.enableUTF8Print(); u8g2.setFont(u8g2_font_unifont_t_chinese1); u8g2.setFontDirection(0); u8g2.clearBuffer(); u8g2.setCursor(0, 15); u8g2.print("Startup"); u8g2.setCursor(0, 31); u8g2.print("股票機啟動中..."); u8g2.sendBuffer(); //wifi連線 delay(1000); u8g2.clearBuffer(); u8g2.setCursor(0, 15); u8g2.print("WIFI連線中..."); u8g2.setCursor(0, 31); u8g2.print("請連結STOCK-BOT"); u8g2.sendBuffer(); WiFiManager wifiManager; wifiManager.autoConnect("STOCK-BOT"); Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); IPAddress ip = WiFi.localIP(); Serial.println(ip); // Use HTTPSRedirect class to create a new TLS connection client = new HTTPSRedirect(httpsPort); client->setInsecure(); client->setPrintResponseBody(true); client->setContentTypeHeader("application/json"); Serial.print("Connecting to "); Serial.println(host); //Start UDP Serial.println("Starting UDP"); udp.begin(localPort); Serial.print("Local port: "); Serial.println(udp.localPort()); sync_clock(); //以 NTP 時間初始設定內部時鐘 delete client; client = nullptr; } void loop() { String d=getDate(); String t=getTime(); String w=getWeek(); int Hm=hour()*100 + minute(); //時:分整數, 0~2359 Serial.print(d + " " + t + " " + w + " "); Serial.println(Hm); static bool flag = false; if (!flag){ free_heap_before = ESP.getFreeHeap(); free_stack_before = ESP.getFreeContStack(); client = new HTTPSRedirect(httpsPort); client->setInsecure(); flag = true; client->setPrintResponseBody(true); client->setContentTypeHeader("application/json"); } if (client != nullptr){ if (!client->connected()){ client->connect(host, httpsPort); } } getstock(); if(w2>128) longtext(); ++count; ++stocknumber; if (count >= 30) { //每 30 次迴圈與 NTP 同步一次 sync_clock(); count=0; } if (stocknumber > totalnumber) { //設定幾檔股票 stocknumber=2; } delay(1000); } void sync_clock() { unsigned long GMT=getUnixTime(); if (GMT != 0) { //有得到 NTP 回應才更新 ESP8266 內建 RTC setTime(GMT + 28800L); //以台灣時間更新內部時鐘 } } void longtext(){ String d=getDate(); String t=getTime(); int rssi = WiFi.RSSI(); for(int i=0 ; i>127-w2; --i){ u8g2.clearBuffer(); u8g2.setFont(u8g2_font_pxplusibmcgathin_8r); u8g2.setCursor(0, 8); u8g2.print(d);u8g2.print(" ");u8g2.println(t); u8g2.setCursor(i, 26); u8g2.setFont(u8g2_font_unifont_t_chinese1); u8g2.println(msg1); if(rssi<-20 && rssi>=-40) u8g2.drawXBMP(104, 0, 24, 8, signal5); else if(rssi<-40 && rssi>=-50) u8g2.drawXBMP(104, 0, 24, 8, signal4); else if(rssi<-50 && rssi>=-60) u8g2.drawXBMP(104, 0, 24, 8, signal3); else if(rssi<-60 && rssi>=-70) u8g2.drawXBMP(104, 0, 24, 8, signal2); else if(rssi<-70 && rssi>=-80) u8g2.drawXBMP(104, 0, 24, 8, signal1); else u8g2.drawXBMP(104, 0, 24, 8, signal0); u8g2.sendBuffer(); delay(10); } } void winfi_signal(){ int rssi = WiFi.RSSI(); if(rssi<-20 && rssi>=-40) u8g2.drawXBMP(104, 0, 24, 8, signal5); else if(rssi<-40 && rssi>=-50) u8g2.drawXBMP(104, 0, 24, 8, signal4); else if(rssi<-50 && rssi>=-60) u8g2.drawXBMP(104, 0, 24, 8, signal3); else if(rssi<-60 && rssi>=-70) u8g2.drawXBMP(104, 0, 24, 8, signal2); else if(rssi<-70 && rssi>=-80) u8g2.drawXBMP(104, 0, 24, 8, signal1); else u8g2.drawXBMP(104, 0, 24, 8, signal0); } String getDate() { String d=""; byte M=month(); if (M < 10) {d.concat('0');} d.concat(M); d.concat('/'); byte D=day(); if (D < 10) {d.concat('0');} d.concat(D); return d; } String getTime() { String t=""; byte h=hour(); if (h < 10) {t.concat('0');} t.concat(h); t.concat(':'); byte m=minute(); if (m < 10) {t.concat('0');} t.concat(m); //t.concat(':'); //byte s=second(); //if (s < 10) {t.concat('0');} //t.concat(s); return t; } String getWeek() { String w[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; return w[weekday()-1]; } String getDateTime() { //傳回日期時間 String dt=(String)year() + "-"; byte M=month(); if (M < 10) {dt.concat('0');} dt.concat(M); dt.concat('-'); byte d=day(); if (d < 10) {dt.concat('0');} dt.concat(d); dt.concat(' '); byte h=hour(); if (h < 10) {dt.concat('0');} dt.concat(h); dt.concat(':'); byte m=minute(); if (m < 10) {dt.concat('0');} dt.concat(m); dt.concat(':'); byte s=second(); if (s < 10) {dt.concat('0');} dt.concat(s); return dt; //傳回格式如 2016-07-16 16:09:23 的日期時間字串 } unsigned long getUnixTime() { WiFi.hostByName(ntpServerName, timeServerIP); //get a random server from the pool sendNTPpacket(timeServerIP); //send an NTP packet to a time server delay(1000); // wait to see if a reply is available int cb=udp.parsePacket(); //return bytes received unsigned long unix_time=0; //預設傳回 0, 表示未收到 NTP 回應 if (!cb) {Serial.println("no packet yet");} else { //received a packet, read the data from the buffer Serial.print("packet received, length="); Serial.println(cb); //=48 udp.read(packetBuffer, NTP_PACKET_SIZE); //read the packet into the buffer //the timestamp starts at byte 40 of the received packet and is four bytes, //or two words, long. First, esxtract the two words: unsigned long highWord=word(packetBuffer[40], packetBuffer[41]); unsigned long lowWord=word(packetBuffer[42], packetBuffer[43]); //combine the four bytes (two words) into a long integer //this is NTP time (seconds since Jan 1 1900): unsigned long secsSince1900=highWord << 16 | lowWord; Serial.print("Seconds since Jan 1 1900=" ); Serial.println(secsSince1900); Serial.print("Unix time="); //Unix time starts on Jan 1 1970. In seconds, that's 2208988800: unix_time=secsSince1900 - 2208988800UL; //更新 unix_time Serial.print(F("Unix time stamp (seconds since 1970-01-01)=")); Serial.println(unix_time); //print Unix time } return unix_time; //return seconds since 1970-01-01 } unsigned long sendNTPpacket(IPAddress& address) { Serial.println("sending NTP packet..."); // set all bytes in the buffer to 0 memset(packetBuffer, 0, NTP_PACKET_SIZE); //Initialize values needed to form NTP request //(see URL above for details on the packets) packetBuffer[0]=0b11100011; // LI, Version, Mode packetBuffer[1]=0; // Stratum, or type of clock packetBuffer[2]=6; // Polling Interval packetBuffer[3]=0xEC; // Peer Clock Precision //8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12]=49; packetBuffer[13]=0x4E; packetBuffer[14]=49; packetBuffer[15]=52; // all NTP fields have been given values, now // you can send a packet requesting a timestamp: udp.beginPacket(address, 123); //NTP requests are to port 123 udp.write(packetBuffer, NTP_PACKET_SIZE); udp.endPacket(); } void getstock() { String url3 = String("/macros/s/") + GScriptId + "/exec?id=" + SheetId + "&number=" + stocknumber; Serial.println(url3); Serial.println("GET Data"); if (client->GET(url3, host)){ Serial.println("GET SUCCESS"); } String aLine = client->getResponseBody(); Serial.print("aLine:");Serial.println(aLine); Serial.print("aLineLength:");Serial.println(aLine.length()); aLine.replace("records",""); aLine.replace("\"",""); aLine.replace("{",""); aLine.replace("}",""); aLine.replace("[",""); aLine.replace("]",""); aLine.replace(","," "); aLine.replace(":"," "); aLine.replace("TPE",""); msg1 = aLine; msg1.toCharArray(copy, 512); w2=u8g2.getUTF8Width(copy); Serial.print("w2:");Serial.println(w2); }
在程式碼第54~59行的位置
const char* host = "script.google.com";
const char *GScriptId = "xxxxxxxxxx"; // 輸入你的GoogleScript ID
const char *SheetId = "xxxxxxxxxx";// 輸入你的Google試算表 ID
const int httpsPort = 443;
int stocknumber = 2;
int totalnumber = 13;//設定幾檔股票
這3個地方要輸入你的資料,然後上傳就可以了~~
大功告成~
補充說明~
這邊用到幾個程式庫,其中 WiFimanager 是一個方便好用的 WiFi 連線程式庫,設定很簡單,好處是你不用常常在程式裡修改你的WiFi SSID及密碼,當裝置連不上WiFi時就會自動產生熱點,你在連到熱點選擇可以使用的WiFi 輸入密碼即可連上網,程式庫可以在Arduino IDE 管理程式庫搜尋即可安裝。
程式第61行,wifiManager.autoConnect("STOCK-BOT");
你可以修改自己喜歡的名稱,在搜尋裝置 WiFi 名稱時就可以輕易地辨識。
U8g2 程式庫自訂字褲的功能很強大,基本上UTF8碼找得到的字都可以用,只是要考量裝置 Flash 的大小是否能把字庫都裝進去。
自訂圖形部分U8g2 支援XBM的格式,你可以用小畫家等繪圖軟體畫好你要的圖形,存成BMP檔,利用線上轉圖程式:XBM線上轉圖
轉檔後下載的 *.XBM 用純文字編輯器打開,裡面的程式碼貼到程式內就可以用了~~
程式碼332~355行,是透過HTTPS協議取值,取回的值是JSON格式,但是我不會使用JSON取值方式,所以就把回傳的資料當作字串處理,把一些標點符號,括號用空白字取代了,如果有高手也請幫忙教學一下吧~~~