├── Readme.md ├── twilio.cpp ├── twilio.hpp ├── twilio_esp8266_arduino_example.ino ├── url_coding.cpp └── url_coding.hpp /Readme.md: -------------------------------------------------------------------------------- 1 | # Twilio Messaging Example on the ESP8266 (C++, Arduino IDE) 2 | 3 | An example application that demonstrates how to send SMS or MMS messages with an ESP8266 connected to a friendly WiFi network, as well as hosts a basic server to receive and respond to SMS and MMS messages. 4 | 5 | Please read [Send SMS and MMS Messages From the ESP8266 in C++](https://www.twilio.com/blog/how-to-send-sms-messages-esp8266-cpp) for more information. 6 | 7 | Once this application is loaded to the ESP8266 it will: 8 | 9 | 1) Send an SMS (or MMS if you choose) to a number 10 | 2) Start a webserver with the route '/message' 11 | 3) Listen for requests to '/message' and respond with a canned response or by reacting to a command from the master number. 12 | 13 | ## Build example: 14 | 15 | In Arduino IDE, first ensure your ESP8266 is burned with the Arduino bootloader and connected. Then, merely adjust the globals at the top of the .ino file and hit the 'Upload' button. 16 | 17 | You'll then want to edit the following variables in twilio_esp8266_arduino_example.ino: 18 | * ssid: Your network SSID 19 | * password: Network's password 20 | * fingerprint (maybe): The SHA1 Fingerprint of api.twilio.com, you 21 | can check with a web browser 22 | * account_sid: from twilio.com/console 23 | * auth_token: from twilio.com/console 24 | * to_number: A number to send the test SMS (or MMS) to 25 | * from_number: An number from twilio.com/console authorized to send SMS and/or MMSes 26 | * message_body: Message body to send 27 | * master_number: 'Authorized' number for this ESP8266 example 28 | * media_url (optional): Url to an image to send a test MMS 29 | 30 | ## Run example: 31 | 32 | Will run automatically once uploaded to the board. If you do not get a message, enable serial debugging. 33 | 34 | Either serial debugging or perhaps a peek at your router screen will reveal the assigned IP address of the ESP8266. :8000/message is the webhook of interest; use ngrok or another means to espose it to Twilio. Note that you'll need to provide a https endpoint! 35 | 36 | ### Fingerprint failure 37 | 38 | The most common reason the sketch fails (assumign you correctly copied credentials) is the `api.twilio.com` certificate changes. See [this section](https://www.twilio.com/en-us/blog/how-to-send-sms-messages-esp8266-cpp#Dealing-with-message-failure) of the Twilio Blog to update the fingerprint in your code. 39 | 40 | ## Motivations 41 | 42 | Hopefully you can use this as the base of your ESP8266 project with Twilio! 43 | 44 | ## Meta & Licensing 45 | 46 | * [MIT License](http://www.opensource.org/licenses/mit-license.html) 47 | * Lovingly crafted by Twilio Developer Education. 48 | -------------------------------------------------------------------------------- /twilio.cpp: -------------------------------------------------------------------------------- 1 | #include "twilio.hpp" 2 | 3 | /* 4 | * Send a SMS or MMS with the Twilio REST API 5 | * 6 | * Inputs: 7 | * - to_number : Number to send the message to 8 | * - from_number : Number to send the message from 9 | * - message_body : Text to send in the message (max 1600 characters) 10 | * - picture_url : (Optional) URL to an image 11 | * 12 | * Outputs: 13 | * - response : Connection messages and Twilio responses returned to caller 14 | * - bool (method) : Whether the message send was successful 15 | */ 16 | bool Twilio::send_message( 17 | const String& to_number, 18 | const String& from_number, 19 | const String& message_body, 20 | String& response, 21 | const String& picture_url) 22 | { 23 | // Check the body is less than 1600 characters in length. see: 24 | // https://support.twilio.com/hc/en-us/articles/223181508-Does-Twilio-support-concatenated-SMS-messages-or-messages-over-160-characters- 25 | // Note this is only checking ASCII length, not UCS-2 encoding; your 26 | // application may need to enhance this. 27 | if (message_body.length() > 1600) { 28 | response += "Message body must be 1600 or fewer characters."; 29 | response += " You are attempting to send "; 30 | response += message_body.length(); 31 | response += ".\r\n"; 32 | return false; 33 | } 34 | 35 | // URL encode our message body & picture URL to escape special chars 36 | // such as '&' and '=' 37 | String encoded_body = urlencode(message_body); 38 | 39 | // Use WiFiClientSecure class to create TLS 1.2 connection 40 | WiFiClientSecure client; 41 | client.setFingerprint(fingerprint.c_str()); 42 | const char* host = "api.twilio.com"; 43 | const int httpsPort = 443; 44 | 45 | // Use WiFiClientSecure class to create TLS connection 46 | Serial.print("connecting to "); 47 | Serial.println(host); 48 | 49 | Serial.printf("Using fingerprint '%s'\n", fingerprint.c_str()); 50 | 51 | // Connect to Twilio's REST API 52 | response += ("Connecting to host "); 53 | response += host; 54 | response += "\r\n"; 55 | if (!client.connect(host, httpsPort)) { 56 | response += ("Connection failed!\r\n"); 57 | return false; 58 | } 59 | 60 | // Check the SHA1 Fingerprint (We will watch for CA verification) 61 | if (client.verify(fingerprint.c_str(), host)) { 62 | response += ("Certificate fingerprints match.\r\n"); 63 | } else { 64 | response += ("Certificate fingerprints don't match.\r\n"); 65 | return false; 66 | } 67 | 68 | // Attempt to send an SMS or MMS, depending on picture URL 69 | String post_data = "To=" + urlencode(to_number) + "&From=" + urlencode(from_number) + \ 70 | "&Body=" + encoded_body; 71 | if (picture_url.length() > 0) { 72 | String encoded_image = urlencode(picture_url); 73 | post_data += "&MediaUrl=" + encoded_image; 74 | } 75 | 76 | // Construct headers and post body manually 77 | String auth_header = _get_auth_header(account_sid, auth_token); 78 | String http_request = "POST /2010-04-01/Accounts/" + 79 | String(account_sid) + "/Messages HTTP/1.1\r\n" + 80 | auth_header + "\r\n" + "Host: " + host + "\r\n" + 81 | "Cache-control: no-cache\r\n" + 82 | "User-Agent: ESP8266 Twilio Example\r\n" + 83 | "Content-Type: " + 84 | "application/x-www-form-urlencoded\r\n" + 85 | "Content-Length: " + post_data.length() +"\r\n" + 86 | "Connection: close\r\n" + 87 | "\r\n" + post_data + "\r\n"; 88 | 89 | response += ("Sending http POST: \r\n"+http_request); 90 | client.println(http_request); 91 | 92 | // Read the response into the 'response' string 93 | response += ("request sent"); 94 | while (client.connected()) { 95 | String line = client.readStringUntil('\n'); 96 | response += (line); 97 | response += ("\r\n"); 98 | } 99 | response += ("closing connection"); 100 | return true; 101 | } 102 | 103 | /* Private function to create a Basic Auth field and parameter */ 104 | String Twilio::_get_auth_header(const String& user, const String& password) { 105 | size_t toencodeLen = user.length() + password.length() + 2; 106 | char toencode[toencodeLen]; 107 | memset(toencode, 0, toencodeLen); 108 | snprintf( 109 | toencode, 110 | toencodeLen, 111 | "%s:%s", 112 | user.c_str(), 113 | password.c_str() 114 | ); 115 | 116 | String encoded = base64::encode((uint8_t*)toencode, toencodeLen-1); 117 | String encoded_string = String(encoded); 118 | std::string::size_type i = 0; 119 | 120 | // Strip newlines (after every 72 characters in spec) 121 | while (i < encoded_string.length()) { 122 | i = encoded_string.indexOf('\n', i); 123 | if (i == -1) { 124 | break; 125 | } 126 | encoded_string.remove(i, 1); 127 | } 128 | return "Authorization: Basic " + encoded_string; 129 | } 130 | -------------------------------------------------------------------------------- /twilio.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "base64.h" 6 | #include "url_coding.hpp" 7 | 8 | class Twilio { 9 | public: 10 | Twilio( 11 | const char* account_sid_in, 12 | const char* auth_token_in, 13 | const char* fingerprint_in 14 | ) 15 | : account_sid(account_sid_in) 16 | , auth_token(auth_token_in) 17 | , fingerprint(fingerprint_in) 18 | {} 19 | // Empty destructor 20 | ~Twilio() = default; 21 | 22 | bool send_message( 23 | const String& to_number, 24 | const String& from_number, 25 | const String& message_body, 26 | String& response, 27 | const String& picture_url = "" 28 | ); 29 | 30 | private: 31 | // Account SID and Auth Token come from the Twilio console. 32 | // See: https://twilio.com/console for more. 33 | 34 | // Used for the username of the auth header 35 | String account_sid; 36 | // Used for the password of the auth header 37 | String auth_token; 38 | // To store the Twilio API SHA1 Fingerprint (get it from a browser) 39 | String fingerprint; 40 | 41 | // Utilities 42 | static String _get_auth_header( 43 | const String& user, 44 | const String& password 45 | ); 46 | 47 | }; 48 | -------------------------------------------------------------------------------- /twilio_esp8266_arduino_example.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Twilio SMS and MMS on ESP8266 Example. 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "twilio.hpp" 10 | 11 | // Use software serial for debugging? 12 | #define USE_SOFTWARE_SERIAL 0 13 | 14 | // Print debug messages over serial? 15 | #define USE_SERIAL 1 16 | 17 | // Your network SSID and password 18 | const char* ssid = "The_Sailboat"; 19 | const char* password = "club848!"; 20 | 21 | // Find the api.twilio.com SHA1 fingerprint, this one was valid as 22 | // of July 2020. This will change, please see 23 | // https://www.twilio.com/docs/sms/tutorials/how-to-send-sms-messages-esp8266-cpp 24 | // to see how to update the fingerprint. 25 | const char fingerprint[] = "BC B0 1A 32 80 5D E6 E4 A2 29 66 2B 08 C8 E0 4C 45 29 3F D0"; 26 | 27 | // Twilio account specific details, from https://twilio.com/console 28 | // Please see the article: 29 | // https://www.twilio.com/docs/guides/receive-and-reply-sms-and-mms-messages-esp8266-c-and-ngrok 30 | 31 | // If this device is deployed in the field you should only deploy a revocable 32 | // key. This code is only suitable for prototyping or if you retain physical 33 | // control of the installation. 34 | const char* account_sid = "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; 35 | const char* auth_token = "Your AUTH TOKEN"; 36 | 37 | // Details for the SMS we'll send with Twilio. Should be a number you own 38 | // (check the console, link above). 39 | String to_number = "+18005551212"; 40 | String from_number = "+18005551212"; 41 | String message_body = "Hello from Twilio and the ESP8266!"; 42 | 43 | // The 'authorized number' to text the ESP8266 for our example 44 | String master_number = "+18005551212"; 45 | 46 | // Optional - a url to an image. See 'MediaUrl' here: 47 | // https://www.twilio.com/docs/api/rest/sending-messages 48 | String media_url = ""; 49 | 50 | // Global twilio objects 51 | Twilio *twilio; 52 | ESP8266WebServer twilio_server(8000); 53 | 54 | // Optional software serial debugging 55 | #if USE_SOFTWARE_SERIAL == 1 56 | #include 57 | // You'll need to set pin numbers to match your setup if you 58 | // do use Software Serial 59 | extern SoftwareSerial swSer(14, 4, false, 256); 60 | #else 61 | #define swSer Serial 62 | #endif 63 | 64 | /* 65 | * Callback function when we hit the /message route with a webhook. 66 | * Use the global 'twilio_server' object to respond. 67 | */ 68 | void handle_message() { 69 | #if USE_SERIAL == 1 70 | swSer.println("Incoming connection! Printing body:"); 71 | #endif 72 | bool authorized = false; 73 | char command = '\0'; 74 | 75 | // Parse Twilio's request to the ESP 76 | for (int i = 0; i < twilio_server.args(); ++i) { 77 | #if USE_SERIAL == 1 78 | swSer.print(twilio_server.argName(i)); 79 | swSer.print(": "); 80 | swSer.println(twilio_server.arg(i)); 81 | #endif 82 | 83 | if (twilio_server.argName(i) == "From" and 84 | twilio_server.arg(i) == master_number) { 85 | authorized = true; 86 | } else if (twilio_server.argName(i) == "Body") { 87 | if (twilio_server.arg(i) == "?" or 88 | twilio_server.arg(i) == "0" or 89 | twilio_server.arg(i) == "1") { 90 | command = twilio_server.arg(i)[0]; 91 | } 92 | } 93 | } // end for loop parsing Twilio's request 94 | 95 | // Logic to handle the incoming SMS 96 | // (Some board are active low so the light will have inverse logic) 97 | String response = ""; 98 | if (command != '\0') { 99 | if (authorized) { 100 | switch (command) { 101 | case '0': 102 | digitalWrite(LED_BUILTIN, LOW); 103 | response += "" 104 | "Turning light off!" 105 | ""; 106 | break; 107 | case '1': 108 | digitalWrite(LED_BUILTIN, HIGH); 109 | response += "" 110 | "Turning light on!" 111 | ""; 112 | break; 113 | case '?': 114 | default: 115 | response += "" 116 | "0 - Light off, 1 - Light On, " 117 | "? - Help\n" 118 | "The light is currently: "; 119 | response += digitalRead(LED_BUILTIN); 120 | response += ""; 121 | break; 122 | } 123 | } else { 124 | response += "" 125 | "Unauthorized!" 126 | ""; 127 | } 128 | 129 | } else { 130 | response += "" 131 | "Look: a SMS response from an ESP8266!" 132 | ""; 133 | } 134 | 135 | twilio_server.send(200, "application/xml", response); 136 | } 137 | 138 | /* 139 | * Setup function for ESP8266 Twilio Example. 140 | * 141 | * Here we connect to a friendly wireless network, set the time, instantiate 142 | * our twilio object, optionally set up software serial, then send a SMS 143 | * or MMS message. 144 | */ 145 | void setup() { 146 | WiFi.begin(ssid, password); 147 | twilio = new Twilio(account_sid, auth_token, fingerprint); 148 | 149 | #if USE_SERIAL == 1 150 | swSer.begin(115200); 151 | while (WiFi.status() != WL_CONNECTED) { 152 | delay(1000); 153 | swSer.print("."); 154 | } 155 | swSer.println(""); 156 | swSer.println("Connected to WiFi, IP address: "); 157 | swSer.println(WiFi.localIP()); 158 | #else 159 | while (WiFi.status() != WL_CONNECTED) delay(1000); 160 | #endif 161 | 162 | // Response will be filled with connection info and Twilio API responses 163 | // from this initial SMS send. 164 | String response; 165 | bool success = twilio->send_message( 166 | to_number, 167 | from_number, 168 | message_body, 169 | response, 170 | media_url 171 | ); 172 | 173 | // Set up a route to /message which will be the webhook url 174 | twilio_server.on("/message", handle_message); 175 | twilio_server.begin(); 176 | 177 | // Use LED_BUILTIN to find the LED pin and set the GPIO to output 178 | pinMode(LED_BUILTIN, OUTPUT); 179 | 180 | #if USE_SERIAL == 1 181 | swSer.println(response); 182 | #endif 183 | } 184 | 185 | 186 | /* 187 | * In our main loop, we listen for connections from Twilio in handleClient(). 188 | */ 189 | void loop() { 190 | twilio_server.handleClient(); 191 | } 192 | -------------------------------------------------------------------------------- /url_coding.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | From: https://github.com/zenmanenergy/ESP8266-Arduino-Examples/blob/master/helloWorld_urlencoded/urlencode.ino 3 | 4 | ESP8266 Hello World urlencode by Steve Nelson 5 | URLEncoding is used all the time with internet urls. This is how urls handle funny characters 6 | in a URL. For example a space is: %20 7 | 8 | These functions simplify the process of encoding and decoding the urlencoded format. 9 | 10 | It has been tested on an esp12e (NodeMCU development board) 11 | This example code is in the public domain, use it however you want. 12 | 13 | Prerequisite Examples: 14 | https://github.com/zenmanenergy/ESP8266-Arduino-Examples/tree/master/helloworld_serial 15 | 16 | */ 17 | #include "url_coding.hpp" 18 | 19 | unsigned char h2int(char c) 20 | { 21 | if (c >= '0' && c <='9'){ 22 | return((unsigned char)c - '0'); 23 | } 24 | if (c >= 'a' && c <='f'){ 25 | return((unsigned char)c - 'a' + 10); 26 | } 27 | if (c >= 'A' && c <='F'){ 28 | return((unsigned char)c - 'A' + 10); 29 | } 30 | return(0); 31 | } 32 | 33 | String urldecode(String str) 34 | { 35 | 36 | String encodedString=""; 37 | char c; 38 | char code0; 39 | char code1; 40 | for (int i =0; i < str.length(); i++){ 41 | c=str.charAt(i); 42 | if (c == '+'){ 43 | encodedString+=' '; 44 | }else if (c == '%') { 45 | i++; 46 | code0=str.charAt(i); 47 | i++; 48 | code1=str.charAt(i); 49 | c = (h2int(code0) << 4) | h2int(code1); 50 | encodedString+=c; 51 | } else{ 52 | 53 | encodedString+=c; 54 | } 55 | 56 | yield(); 57 | } 58 | 59 | return encodedString; 60 | } 61 | 62 | String urlencode(String str) 63 | { 64 | String encodedString=""; 65 | char c; 66 | char code0; 67 | char code1; 68 | char code2; 69 | for (int i =0; i < str.length(); i++){ 70 | c=str.charAt(i); 71 | if (c == ' '){ 72 | encodedString+= '+'; 73 | } else if (isalnum(c)){ 74 | encodedString+=c; 75 | } else{ 76 | code1=(c & 0xf)+'0'; 77 | if ((c & 0xf) >9){ 78 | code1=(c & 0xf) - 10 + 'A'; 79 | } 80 | c=(c>>4)&0xf; 81 | code0=c+'0'; 82 | if (c > 9){ 83 | code0=c - 10 + 'A'; 84 | } 85 | code2='\0'; 86 | encodedString+='%'; 87 | encodedString+=code0; 88 | encodedString+=code1; 89 | //encodedString+=code2; 90 | } 91 | yield(); 92 | } 93 | return encodedString; 94 | 95 | } 96 | -------------------------------------------------------------------------------- /url_coding.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | 5 | From: https://github.com/zenmanenergy/ESP8266-Arduino-Examples/blob/master/helloWorld_urlencoded/urlencode.ino 6 | 7 | */ 8 | #include 9 | 10 | unsigned char h2int(char c); 11 | String urldecode(String str); 12 | String urlencode(String str); 13 | --------------------------------------------------------------------------------