From 7b171acab2cae33410e2dc67f1dd87bd864202b1 Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 22 Aug 2021 14:34:04 -0700 Subject: [PATCH] Send an SMS from ESP32 (ft. Twilio) --- .../0_ESP32TTGO_FIRESTORE_SMS.ino | 270 ++++++++++++++++++ .../0_ESP32TTGO_FIRESTORE_SMS/Display.cpp | 66 +++++ .../0_ESP32TTGO_FIRESTORE_SMS/Display.h | 25 ++ .../0_ESP32TTGO_FIRESTORE_SMS/Messenger.cpp | 32 +++ .../0_ESP32TTGO_FIRESTORE_SMS/Messenger.h | 16 ++ .../0_ESP32TTGO_FIRESTORE_SMS/Network.cpp | 102 +++++++ .../0_ESP32TTGO_FIRESTORE_SMS/Network.h | 37 +++ .../data/icon_firebase_off.jpg | Bin 0 -> 12130 bytes .../data/icon_firebase_on.jpg | Bin 0 -> 11475 bytes .../data/icon_wifi_off.jpg | Bin 0 -> 10901 bytes .../data/icon_wifi_on.jpg | Bin 0 -> 10527 bytes 11 files changed, 548 insertions(+) create mode 100644 MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/0_ESP32TTGO_FIRESTORE_SMS.ino create mode 100644 MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Display.cpp create mode 100644 MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Display.h create mode 100644 MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Messenger.cpp create mode 100644 MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Messenger.h create mode 100644 MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Network.cpp create mode 100644 MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Network.h create mode 100644 MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/data/icon_firebase_off.jpg create mode 100644 MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/data/icon_firebase_on.jpg create mode 100644 MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/data/icon_wifi_off.jpg create mode 100644 MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/data/icon_wifi_on.jpg diff --git a/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/0_ESP32TTGO_FIRESTORE_SMS.ino b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/0_ESP32TTGO_FIRESTORE_SMS.ino new file mode 100644 index 0000000..1938b7c --- /dev/null +++ b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/0_ESP32TTGO_FIRESTORE_SMS.ino @@ -0,0 +1,270 @@ +///////////////////////////////////////////////////////////////// +/* + Send an SMS from ESP32 (ft. Twilio) + https://youtu.be/SP4pvYCQAfc + Created by Eric N. (ThatProject) +*/ +///////////////////////////////////////////////////////////////// + +#include "DHTesp.h" // Click here to get the library: http://librarymanager/All#DHTesp +#include +#include "Display.h" +#include "Network.h" +#include "Messenger.h" + +#ifndef ESP32 +#pragma message(THIS EXAMPLE IS FOR ESP32 ONLY!) +#error Select ESP32 board. +#endif + +/**************************************************************/ +/* Example how to read DHT sensors from an ESP32 using multi- */ +/* tasking. */ +/* This example depends on the Ticker library to wake up */ +/* the task every 20 seconds */ +/**************************************************************/ + +Display *display; +Network *network; +Messenger *messenger; +DHTesp dht; + +void tempTask(void *pvParameters); +bool getTemperature(); +void triggerGetTemp(); + +/** Task handle for the light value read task */ +TaskHandle_t tempTaskHandle = NULL; +/** Ticker for temperature reading */ +Ticker tempTicker; +/** Comfort profile */ +ComfortState cf; +/** Flag if task should run */ +bool tasksEnabled = false; +/** Pin number for DHT11 data pin */ +int dhtPin = 21; + +/** + * initTemp + * Setup DHT library + * Setup task and timer for repeated measurement + * @return bool + * true if task and timer are started + * false if task or timer couldn't be started + */ +bool initTemp() { + byte resultValue = 0; + // Initialize temperature sensor + dht.setup(dhtPin, DHTesp::DHT11); + Serial.println("DHT initiated"); + + // Start task to get temperature + xTaskCreatePinnedToCore( + tempTask, /* Function to implement the task */ + "tempTask ", /* Name of the task */ + 10000, /* Stack size in words */ + NULL, /* Task input parameter */ + 5, /* Priority of the task */ + &tempTaskHandle, /* Task handle. */ + 1); /* Core where the task should run */ + + if (tempTaskHandle == NULL) { + Serial.println("Failed to start task for temperature update"); + return false; + } else { + // Start update of environment data every 20 seconds + tempTicker.attach(20, triggerGetTemp); + } + return true; +} + +/** + * triggerGetTemp + * Sets flag dhtUpdated to true for handling in loop() + * called by Ticker getTempTimer + */ +void triggerGetTemp() { + if (tempTaskHandle != NULL) { + xTaskResumeFromISR(tempTaskHandle); + } +} + +/** + * Task to reads temperature from DHT11 sensor + * @param pvParameters + * pointer to task parameters + */ +void tempTask(void *pvParameters) { + Serial.println("tempTask loop started"); + while (1) // tempTask loop + { + if (tasksEnabled) { + // Get temperature values + getTemperature(); + } + // Got sleep again + vTaskSuspend(NULL); + } +} + +/** + * getTemperature + * Reads temperature from DHT11 sensor + * @return bool + * true if temperature could be aquired + * false if aquisition failed +*/ +bool getTemperature() { + // Reading temperature for humidity takes about 250 milliseconds! + // Sensor readings may also be up to 2 seconds 'old' (it's a very slow sensor) + TempAndHumidity newValues = dht.getTempAndHumidity(); + // Check if any reads failed and exit early (to try again). + if (dht.getStatus() != 0) { + Serial.println("DHT11 error status: " + String(dht.getStatusString())); + return false; + } + + float heatIndex = dht.computeHeatIndex(newValues.temperature, newValues.humidity); + float dewPoint = dht.computeDewPoint(newValues.temperature, newValues.humidity); + float cr = dht.getComfortRatio(cf, newValues.temperature, newValues.humidity); + + String comfortStatus; + switch(cf) { + case Comfort_OK: + comfortStatus = "OK"; + break; + case Comfort_TooHot: + comfortStatus = "TooHot"; + break; + case Comfort_TooCold: + comfortStatus = "TooCold"; + break; + case Comfort_TooDry: + comfortStatus = "TooDry"; + break; + case Comfort_TooHumid: + comfortStatus = "TooHumid"; + break; + case Comfort_HotAndHumid: + comfortStatus = "Hot&Humid"; + break; + case Comfort_HotAndDry: + comfortStatus = "Hot&Dry"; + break; + case Comfort_ColdAndHumid: + comfortStatus = "Cold&Humid"; + break; + case Comfort_ColdAndDry: + comfortStatus = "Cold&Dry"; + break; + default: + comfortStatus = "Unknown"; + break; + }; + + Serial.println(" T:" + String(newValues.temperature) + " H:" + String(newValues.humidity) + " I:" + String(heatIndex) + " D:" + String(dewPoint) + " " + comfortStatus); + + display->tempUpdates("Temp " + String(newValues.temperature, 1) +"'C", + "Humidity " + String(newValues.humidity,0) + "%", + comfortStatus); + + network->firestoreDataUpdate(newValues.temperature, newValues.humidity); + + String msgBody = ""; + if (newValues.temperature >28 || newValues.temperature <18){ + msgBody += "Temperature: "; + msgBody += String(newValues.temperature, 1) +"'C\n"; + } + + if(newValues.humidity > 60 || newValues.humidity < 20){ + msgBody += "Humidity: "; + msgBody += String(newValues.humidity, 0) +"%\n"; + } + + if(msgBody.length() >0){ + msgBody += "From the House>Room 1"; + messenger->sendMessage(msgBody); + } + + return true; +} + +void setup() +{ + Serial.begin(115200); + Serial.println(); + Serial.println("DHT ESP32 example with tasks"); + + initDisplay(); + initNetwork(); + initMessenger(); + initTemp(); + // Signal end of setup() to tasks + tasksEnabled = true; +} + +void loop() { + if (!tasksEnabled) { + // Wait 2 seconds to let system settle down + delay(2000); + // Enable task that will read values from the DHT sensor + tasksEnabled = true; + if (tempTaskHandle != NULL) { + vTaskResume(tempTaskHandle); + } + } + yield(); +} + +void initDisplay(){ + display = new Display(); + display->initTFT(); + display->showWiFiIcon(false); + display->showFirebaseIcon(false); + display->centerMsg("System Init..."); +} + +void initNetwork(){ + void (*ptr)(Network_State_t) = &networkEvent; + network = new Network(ptr); + network->initWiFi(); +} + +void networkEvent(Network_State_t event){ + switch(event){ + case NETWORK_CONNECTED: + display->showWiFiIcon(true); + break; + case NETWORK_DISCONNECTED: + display->showWiFiIcon(false); + break; + case FIREBASE_CONNECTED: + display->showFirebaseIcon(true); + break; + case FIREBASE_DISCONNECTED: + display->showFirebaseIcon(false); + break; + default: break; + } +} + +void initMessenger(){ + messenger = new Messenger(); +} + + + + + + + + + + + + + + + + + diff --git a/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Display.cpp b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Display.cpp new file mode 100644 index 0000000..4f5dfff --- /dev/null +++ b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Display.cpp @@ -0,0 +1,66 @@ +#include "Display.h" + +static Display* instance = NULL; + +Display::Display(){ + instance = this; + tft = new TFT_eSPI(); +} + +Display::~Display(){ + delete tft; +} + +bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t * bitmap){ + if (y >= instance->tft->height()) return 0; + instance->tft->pushImage(x, y, w, h, bitmap); + return 1; +} + +void Display::initTFT(){ + tft->init(); + tft->fillScreen(TFT_BLACK); + tft->setRotation(1); + tft->setTextColor(TFT_WHITE, TFT_BLACK); + tft->setTextDatum(MC_DATUM); + tft->setFreeFont(&Orbitron_Light_24); + + TJpgDec.setJpgScale(1); + TJpgDec.setSwapBytes(true); + TJpgDec.setCallback(tft_output); + + if(!SPIFFS.begin()){ + while(1) yield(); + } +} + +void Display::centerMsg(String text){ + tft->drawString(text, tft->width()/2, 60); +} + +void Display::tempUpdates(String temp, String hum, String status){ + tft->setTextPadding(tft->width()); + tft->drawString(temp, tft->width()/2, 40); + tft->drawString(hum, tft->width()/2, 70); + tft->drawString(status, tft->width()/2, 110); +} + +void Display::showWiFiIcon(bool isOn){ + tft->fillRect(tft->width() -30, 0, 30, 30, TFT_BLACK); + TJpgDec.drawFsJpg(tft->width() -30, 0, isOn ? "/icon_wifi_on.jpg" : "/icon_wifi_off.jpg"); +} + +void Display::showFirebaseIcon(bool isOn){ + tft->fillRect(tft->width() -60, 0, 30, 30, TFT_BLACK); + TJpgDec.drawFsJpg(tft->width() -60, 0, isOn ? "/icon_firebase_on.jpg" : "/icon_firebase_off.jpg"); +} + + + + + + + + + + diff --git a/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Display.h b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Display.h new file mode 100644 index 0000000..19572f6 --- /dev/null +++ b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Display.h @@ -0,0 +1,25 @@ +#ifndef Display_H_ +#define Display_H_ + +#include +#include +#include "SPIFFS.h" + +class Display { +private: + TFT_eSPI* tft; + friend bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t * bitmap); + +public: + Display(); + ~Display(); + + void initTFT(); + void centerMsg(String text); + void tempUpdates(String temp, String hum, String status); + void showWiFiIcon(bool isOn); + void showFirebaseIcon(bool isOn); +}; + + +#endif \ No newline at end of file diff --git a/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Messenger.cpp b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Messenger.cpp new file mode 100644 index 0000000..73d38a5 --- /dev/null +++ b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Messenger.cpp @@ -0,0 +1,32 @@ +#include "Messenger.h" + +// Values from Twilio (find them on the dashboard) +static const char *account_sid = ""; +static const char *auth_token = ""; +// Phone number should start with "+" +static const char *from_number = ""; + +// You choose! +// Phone number should start with "+" +static const char *to_number = ""; + +Messenger::Messenger() { + twilio = new Twilio(account_sid, auth_token); +} + +Messenger::~Messenger() { + delete twilio; +} + +void Messenger::sendMessage(String msg) { + + if (WiFi.status() != WL_CONNECTED) return; + + String response; + bool success = twilio->send_message(to_number, from_number, msg, response); + if (success) { + Serial.println("Sent message successfully!"); + } else { + Serial.println(response); + } +} \ No newline at end of file diff --git a/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Messenger.h b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Messenger.h new file mode 100644 index 0000000..0525a94 --- /dev/null +++ b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Messenger.h @@ -0,0 +1,16 @@ +#ifndef Messenger_H_ +#define Messenger_H_ + +#include "twilio.hpp" + +class Messenger { +private: + Twilio *twilio; + +public: + Messenger(); + ~Messenger(); + void sendMessage(String msg); +}; + +#endif \ No newline at end of file diff --git a/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Network.cpp b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Network.cpp new file mode 100644 index 0000000..ec69ec1 --- /dev/null +++ b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Network.cpp @@ -0,0 +1,102 @@ +#include "Network.h" +#include "addons/TokenHelper.h" + +#define WIFI_SSID "" +#define WIFI_PASSWORD "" + +#define API_KEY "" +#define FIREBASE_PROJECT_ID "" +#define USER_EMAIL "" +#define USER_PASSWORD "" + +static Network *instance = NULL; + +Network::Network(){} + +Network::Network(FuncPtrInt f){ + instance = this; + callBackEvent = f; +} + +void WiFiEventConnected(WiFiEvent_t event, WiFiEventInfo_t info){ + Serial.println("WIFI CONNECTED! BUT WAIT FOR THE LOCAL IP ADDR"); +} + +void WiFiEventGotIP(WiFiEvent_t event, WiFiEventInfo_t info){ + Serial.print("LOCAL IP ADDRESS: "); + Serial.println(WiFi.localIP()); + instance->callBackEvent(NETWORK_CONNECTED); + instance->firebaseInit(); +} + +void WiFiEventDisconnected(WiFiEvent_t event, WiFiEventInfo_t info){ + Serial.println("WIFI DISCONNECTED!"); + instance->callBackEvent(NETWORK_DISCONNECTED); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); +} + +void FirestoreTokenStatusCallback(TokenInfo info){ + Serial.printf("Token Info: type = %s, status = %s\n", getTokenType(info).c_str(), getTokenStatus(info).c_str()); + if(info.status == token_status_ready){ + instance->callBackEvent(FIREBASE_CONNECTED); + }else{ + instance->callBackEvent(FIREBASE_DISCONNECTED); + } +} + +void Network::initWiFi(){ + WiFi.disconnect(); + WiFi.onEvent(WiFiEventConnected, SYSTEM_EVENT_STA_CONNECTED); + WiFi.onEvent(WiFiEventGotIP, SYSTEM_EVENT_STA_GOT_IP); + WiFi.onEvent(WiFiEventDisconnected, SYSTEM_EVENT_STA_DISCONNECTED); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); +} + +void Network::firebaseInit(){ + config.api_key = API_KEY; + + auth.user.email = USER_EMAIL; + auth.user.password = USER_PASSWORD; + + config.token_status_callback = FirestoreTokenStatusCallback; + + Firebase.begin(&config, &auth); +} + +void Network::firestoreDataUpdate(double temp, double humi){ + if(WiFi.status() == WL_CONNECTED && Firebase.ready()){ + String documentPath = "House/Room_1"; + + FirebaseJson content; + + content.set("fields/temperature/doubleValue", String(temp).c_str()); + content.set("fields/humidity/doubleValue", String(humi).c_str()); + + if(Firebase.Firestore.patchDocument(&fbdo, FIREBASE_PROJECT_ID, "", documentPath.c_str(), content.raw(), "temperature,humidity")){ + Serial.printf("ok\n%s\n\n", fbdo.payload().c_str()); + return; + }else{ + Serial.println(fbdo.errorReason()); + } + + if(Firebase.Firestore.createDocument(&fbdo, FIREBASE_PROJECT_ID, "", documentPath.c_str(), content.raw())){ + Serial.printf("ok\n%s\n\n", fbdo.payload().c_str()); + return; + }else{ + Serial.println(fbdo.errorReason()); + } + } +} + + + + + + + + + + + + + diff --git a/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Network.h b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Network.h new file mode 100644 index 0000000..cd290b6 --- /dev/null +++ b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/Network.h @@ -0,0 +1,37 @@ +#ifndef Network_H_ +#define Network_H_ + +#include +#include + +typedef enum { + NETWORK_CONNECTED, + NETWORK_DISCONNECTED, + FIREBASE_CONNECTED, + FIREBASE_DISCONNECTED +} Network_State_t; + +class Network{ +private: + FirebaseData fbdo; + FirebaseAuth auth; + FirebaseConfig config; + + typedef void (*FuncPtrInt)(Network_State_t); + + void firebaseInit(); + friend void WiFiEventConnected(WiFiEvent_t event, WiFiEventInfo_t info); + friend void WiFiEventGotIP(WiFiEvent_t event, WiFiEventInfo_t info); + friend void WiFiEventDisconnected(WiFiEvent_t event, WiFiEventInfo_t info); + friend void FirestoreTokenStatusCallback(TokenInfo info); + +public: + FuncPtrInt callBackEvent; + Network(); + Network(FuncPtrInt f); + void initWiFi(); + void firestoreDataUpdate(double temp, double humi); +}; + + +#endif \ No newline at end of file diff --git a/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/data/icon_firebase_off.jpg b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/data/icon_firebase_off.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c4aeb65efe8694280a38230d29044bc23fab2c0b GIT binary patch literal 12130 zcmeG?cU)7+)|1eSAOe=vg@}q}g>)cML@6S!hz1bEnsO7OX(l()MAWq{yNVrgT|1y_ zdo~bRajmE;Dk>lfSg?SAfC>l*d2??H=(=zBd+&R{?~l*PFZa$VbLO0L&zvd0(cITO zF-ZuB6$=3f4hACt0Or7q&khEKag`Vt>)P&z?|Z=V!x8|7_SC<}3FJJ4G$mS&%2m;F1&Ky;XOM;kbNON!h8YfM z6dILH@nTb$Bv_%+*mNqCW3-E)?OUS(U>DKQhtn>jt~%tyiYdWJr>ALmTL>;)wh%^i zw}nX1%OKE*2HD2rwBo=7_=aB5zBTtXb-)_8Vvb)hAZT6#8yo_HB^aOPAy^Kq&CSg% z%&jdftnI8Ut?V3at*vbx``X((+S~WFv&M_=r3F5H5_(u!^|0yD)5fN!gN==i1NN|S z&^EFAI|4Mn0XuWx1e}ZrE`Vr9FtQ_P%At`&%_V{l)Y6227pO2+i~xaXY+`C=ZeeK! z1K?vK0T7M!#GZg)OfVuE6HUxa%}tD~>5yn=WIV7J)nwdMuD#2Gr8HBA^}CPx4jSyp zInSdrl*q_y9X3xL~QOv5;%PdnfPo{ibBc1o4TBu3RtivI0YiumA2M*P-RQan1_`Q2H! zg^{;9CZ*13JG}j7Ud4vtvN6I7(jNVzRgvCln^=cC`NMa<5VUPRboAh94T!Ee%~W~| zy;5AA!ymniJR9R_cQ!tYTdVzyf7+_H@3gmf@u_1z8W;M4wcEPz@*>tyivh~i zw~LZS4)Uq@P|T(0kK8nA;K0}BAIhB5hvkhdo0*zdOv}u9$Gn-dd*2r3w#fEDRm`2+ zm!Fkq^5!CF-hpqadA}FA_YS>R74vq@>0>uEAZyl#=eF{y+P0*Sd$T8`l5cL$_iDZ& zT2ex2O?P*Ge&K|xossRVD@o}cGm5+1W?X3=P<#c$n?7bk~i1=ZUFF<-ce^o%ha~Imxp8j?CP5gR9qljx7(2v#cq>ru54OgofRpCE2M?S4?PRzJoQD3M5`yN$4)qwO! z=jgWc8Zf3K=>)H{ynS;0@q#&`rpnf{6-P@uf*-Z;i`qjvN+T%zo2;3~w(p6%HGkO? za`B?$?dQ%UCAHPXU3Ypm!R*1)vw@i%A}dX)rp|`bmHz1N6yW6>6l{zoO#sXWz@&=h zRU9`2$MvjgocWe~zVGfm38KT71x06Y8XKpVuu>>t77bCO3N;ErEQfC_;fZ zu~ZniA&l$lD?w#CU9EX)u>=*%aFLL1fphx>Pybfyr%{*;q8og4`Jz`hUxX-7v0SEt z^gbA;p8}QX&4cFilm>t!QUx0TKc&pC3y`5*z$BhTWuQzJp$iQ_fK=jd0HBAvMEmmR zi*$~_%>#fF=<5fy!Dd~+5s*j%xtcdt{ul%C5~!5@0p~0EBcHEQ5Ed>&#}DF2u&-_? zz7hfHb9jzQg5vlov68VI1H0uygpbOV{#=xcS%51!MKndFj~A?tF~c{eHkgD6wPHT$ zHVjVcpX)Z1FVW(sDEVWjV-oPvldqI3BBBxaabfI=Wg@-%Y%l@`>f|d&QMptimx**u zto0O3g>LvBdOSxgitbLZ(i5O=27YW58P#cZ&`t3K{Tm-)t~kv`*C~AYEYXfjqc=Ft z5&#WebfdKgj>X1t6yCm{0d~UsdW*vr_!B+|c?DI6x*>`+FqG~%y%Ylxt8{136LhL# zpiC{nGAd@gDNnAJ2~?lt13rqabn%G*8$^akg~(4L9r$*Q2;6K=SHNSisY)W|BdTeV zN!XAjeAJH#PJ`%P@Nb3#LH^zPG7~A~YQ;w=Q@K(s5*xB{Kqw|X1jj)cJ-BLA9*D>g zB^N~m5COvC6#8k#Qp=2CFiMbAM1rsXw*5wGr9?Mb={^eP{EsogDv^Fd)AE>dC1@B| z^bwa0AA#+VSTsmAAuM>3J~5i>(H}8eM9Y=&<0N8{-cz<(<0t6R&~h^YBIK&^L~XGU zQKFw=pQcBD%x=XK`N<`6WjE*AY32I{8VIlnm@JoJzP3Q+3b+(dA>A{fr39wTKVYqS zc#8c4-U@!==ug@4aX1X?2+rs*oP8Sbx^~Rj;dO#Gw;5v_LTy0z!dqedz<3sXU>?_x zp_wohT!r{?pfB7$ec4`p*^X8FYl^7tCD&ZT) z!!6ZN&&Vi0bxoJif2;oipC5c+~F2#p4cTvqo{&S zCd*XrTg9nCGikAlY6_==bm#Ri3Q#+5zJspu)+_C z7Eg#*BBAkMZv6bgmp$#NHPd0ak^HuyId(EQ&}t)nL;DeXl{_hO%*3Yxv_3C)gZkDe?-Ms zit#NU36lu7Giub^1*)pGL8>r_{j9U}i~_!aPXX5xc;^B>8Id4TM24!MW2u<^dZEF= z2KLX2l}ZiVDl}jr)aYM*F%>q=lR1b=u2%AqfQ7KfLHf>AC@kG={50#)+WI|3>(c|4e-qci~?sPe*v3 zo(Rj0&Y(kYGZ+FlmX{aHjYj9PpvUPHFNCKz{twDy!AfN_>H1*(_mtPUmLJWPi4d5a z$u9rCIaCiJl|mIT+&ri-kQo#{-Hl5ZAZ{!yc=${YD&hfyz-K{~|(2RFRiGD44nYZI($SQU-YFJ8X9zj)w_2fldViwC}V;EMR+U4Ma#XG9#K3EP$m|Pe3FX!7D*Vm_|fX z3pD{=7P2$xWjfHz-UXOb9mdfHIa*Bh?adub@55QJv@dV{5Wn5W8eADl%YNqznM0q1 zdMs<)u*W}46)@g2kUuPZTEth!FWhpR z#q$=%#Ky(1Sh;HT_sN?!Z%N&nwryW}M&|wlznnakm3=zr%%#g$@~>XIey^yw|QEQ@*D8w0qT%oGle?PbN{=q$zJ6T%(W8pr+SW zE>o)0e6J^j44vJ6w4mM0{Y~#{1NwJ7Ka%El%3+1;$rZnf!^?iA-du07S{0rXH*o5{ zz+Zk|a5kbeZhKzaZ%?vJ8*d2DT(ruG8Ond!I3wnp4Ttal6eA43JT6z&p0y)+OPoii zy)!sdFY;Nqx9`$h z7uLGg9^L-PF>C*tjO8tpua|9Z*v}DOs-3w_zWvf*3O8fsw6e620mQ^h&aqD1#o^7P z$`7~oyA(4;+0n8*f62CQW0LJPfHiB_=$q_y-Umucl3M8NnpXu+Daf5+=G<^<^X!oK z7f!{uxwj1E+)kn#&dxd$DwRdD@4nE0%V$Qfta~5)X5ue9G7k1Joi1MQDbJTZ+mgH^ ze%tHKwe!3lU%q*~)i`u{EN8m7a_^xRspa2PES}vm^kHn?Yv)*tJ*OYEFx#Yi=C2A{ zfA2-bWkfYqZ9hC!n)H42KZxclW{f)9*(~3BHcNVmp+Ak+&;iaRksr5JA2+LDB-F>%Cmq*-xRkjpep#nr z%gcvjBFYz*+|Tf*Mipc|ob7kuXOliPdsa+)&@}t_ct^*e>{zFIf9%@POtYsv z&}+iQuvLAhIGD_#b+Y6YPYdSeE&32}*|fF&_NduE**@$DU9)Ah%(Hdr2k!ab9Ve>} zoiINZM2tC7knda6-;?=toBHrt_VCL685`0TlxH-DmQ{qbYe4(JoOYA?9yg{Y6rPGo zlhkju?JfMC;g|E;51k9OViY>K`}d_6EIOCdKIEoHS>5$Ux^nZ4-NNNg zMb`70U#rslyH;>)S3drR2uj`>wHK`Fym7A4_Vtlk4H)lSeYf5x$N6Hl2ArEy=2Nc$ zJKGO__@n&!Pu`av z`8i6Jak{3jx^(fI#$g(e`)mB2?;=(m`SqLNs6_`Swl-<*-2N^alXx~^e*3D9C!HUX zoG&G*&YkhObna|YMasF%PNR+oojp3AYe0$yI5Z~ie4qiF60f8xX~aVv)ym_`&%;17B^?z z--s4OS0l5G%<@2D70b3gFD^5ooSb;Dx=9mSyJ- zemS+cxE|p%6i$z))~0W}dWh#$cX!4ly6Rzh&7&S!Hy=2OpOt2ReR#uxL1i=i;Elo9E{m`K(%z>o{zy>$Ez; zk9ETH1HZ#0sdtr6^Gu(+8qhE!NuBfqycBxtU0_E|=a5?)+bcgLwAX0B@SPRz-c2co JkQz2LuIloJt#mNE1*8lp<q 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 Jm)EGN`Y$#wZomKl literal 0 HcmV?d00001 diff --git a/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/data/icon_wifi_off.jpg b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/data/icon_wifi_off.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ec251deca45b3281cbaa59c7c55d4b700d405bb4 GIT binary patch literal 10901 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 L%e^Bt)a8ExfRue4 literal 0 HcmV?d00001 diff --git a/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/data/icon_wifi_on.jpg b/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS/data/icon_wifi_on.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ef4b16e4ad4ab08b2abdc25bb37d5e8e95897807 GIT binary patch literal 10527 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