├── README.md ├── WebSocketClient.cpp ├── WebSocketClient.h ├── examples └── WebsocketSample │ └── WebsocketSample.ino └── library.properties /README.md: -------------------------------------------------------------------------------- 1 | # esp8266-websocketclient 2 | ESP8266 WebsocketClient 3 | 4 | A simple Websocket Client for the ESP8266. Supporting ws:// & wss://. 5 | 6 | Example: 7 | 8 | ``` 9 | #include 10 | #include "WebSocketClient.h" 11 | #include "ESP8266WiFi.h" 12 | 13 | WebSocketClient ws(true); 14 | 15 | void setup() { 16 | Serial.begin(115200); 17 | WiFi.begin("MyWifi", "secret"); 18 | 19 | Serial.print("Connecting"); 20 | while (WiFi.status() != WL_CONNECTED) { 21 | delay(500); 22 | Serial.print("."); 23 | } 24 | } 25 | 26 | void loop() { 27 | if (!ws.isConnected()) { 28 | ws.connect("echo.websocket.org", "/", 443); 29 | } else { 30 | ws.send("hello"); 31 | 32 | String msg; 33 | if (ws.getMessage(msg)) { 34 | Serial.println(msg); 35 | } 36 | } 37 | delay(500); 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /WebSocketClient.cpp: -------------------------------------------------------------------------------- 1 | //#define DEBUG 2 | 3 | #include "WebSocketClient.h" 4 | #include 5 | 6 | #define WS_FIN 0x80 7 | #define WS_OPCODE_TEXT 0x01 8 | #define WS_OPCODE_BINARY 0x02 9 | 10 | #define WS_MASK 0x80 11 | #define WS_SIZE16 126 12 | 13 | #ifdef DEBUG 14 | #define DEBUG_WS Serial.println 15 | #else 16 | #define DEBUG_WS(MSG) 17 | #endif 18 | 19 | WebSocketClient::WebSocketClient(bool secure) { 20 | if (secure) 21 | this->client = new WiFiClientSecure; 22 | else 23 | this->client = new WiFiClient; 24 | } 25 | 26 | WebSocketClient::~WebSocketClient() { 27 | delete this->client; 28 | } 29 | 30 | void WebSocketClient::setAuthorizationHeader(String header) { 31 | this->authorizationHeader = header; 32 | } 33 | 34 | String WebSocketClient::generateKey() { 35 | String key = ""; 36 | for (int i = 0; i < 22; ++i) { 37 | int r = random(0, 3); 38 | if (r == 0) 39 | key += (char) random(48, 57); 40 | else if (r == 1) 41 | key += (char) random(65, 90); 42 | else if (r == 2) 43 | key += (char) random(97, 122); 44 | } 45 | return key; 46 | } 47 | void WebSocketClient::write(uint8_t data) { 48 | if (client->connected()) 49 | client->write(data); 50 | } 51 | 52 | void WebSocketClient::write(const char *data) { 53 | if (client->connected()) 54 | client->write(data); 55 | } 56 | 57 | bool WebSocketClient::connect(String host, String path, int port) { 58 | if (!client->connect(host.c_str(), port)) 59 | return false; 60 | 61 | // send handshake 62 | String handshake = "GET " + path + " HTTP/1.1\r\n" 63 | "Host: " + host + "\r\n" 64 | "Connection: Upgrade\r\n" 65 | "Upgrade: websocket\r\n" 66 | "Sec-WebSocket-Version: 13\r\n" 67 | "Sec-WebSocket-Key: " + generateKey() + "==\r\n"; 68 | 69 | if (authorizationHeader != "") 70 | handshake += "Authorization: " + authorizationHeader + "\r\n"; 71 | 72 | handshake += "\r\n"; 73 | 74 | DEBUG_WS("[WS] sending handshake"); 75 | DEBUG_WS(handshake); 76 | 77 | write(handshake.c_str()); 78 | 79 | // success criteria 80 | bool hasCorrectStatus = false; 81 | bool isUpgrade = false; 82 | bool isWebsocket = false; 83 | bool hasAcceptedKey = false; 84 | 85 | bool endOfResponse = false; 86 | 87 | // handle response headers 88 | String s; 89 | while (!endOfResponse && (s = client->readStringUntil('\n')).length() > 0) { 90 | DEBUG_WS("[WS][RX] " + s); 91 | // HTTP Status 92 | if (s.indexOf("HTTP/") != -1) { 93 | auto status = s.substring(9, 12); 94 | if (status == "101") 95 | hasCorrectStatus = true; 96 | else { 97 | DEBUG_WS("[WS] wrong status: " + status); 98 | return false; 99 | } 100 | } 101 | // Headers 102 | else if (s.indexOf(":") != -1) { 103 | auto col = s.indexOf(":"); 104 | auto key = s.substring(0, col); 105 | auto value = s.substring(col + 2, s.length() - 1); 106 | 107 | if (key == "Connection" && (value == "Upgrade" || value == "upgrade")) 108 | isUpgrade = true; 109 | 110 | else if (key == "Sec-WebSocket-Accept") 111 | hasAcceptedKey = true; 112 | 113 | else if (key == "Upgrade" && value == "websocket") 114 | isWebsocket = true; 115 | } 116 | 117 | else if (s == "\r") 118 | endOfResponse = true; 119 | } 120 | 121 | bool success = hasCorrectStatus && isUpgrade && isWebsocket && hasAcceptedKey; 122 | 123 | if (success) { 124 | DEBUG_WS("[WS] sucessfully connected"); 125 | this->websocketEstablished = true; 126 | } 127 | else { 128 | DEBUG_WS("[WS] could not connect"); 129 | this->disconnect(); 130 | } 131 | 132 | return success; 133 | } 134 | 135 | bool WebSocketClient::isConnected() { 136 | return this->websocketEstablished && client->connected(); 137 | } 138 | 139 | void WebSocketClient::disconnect() { 140 | client->stop(); 141 | this->websocketEstablished = false; 142 | } 143 | 144 | void WebSocketClient::send(const String& str) { 145 | DEBUG_WS("[WS] sending: " + str); 146 | if (!client->connected()) { 147 | DEBUG_WS("[WS] not connected..."); 148 | return; 149 | } 150 | 151 | // 1. send fin and type text 152 | write(WS_FIN | WS_OPCODE_TEXT); 153 | 154 | // 2. send length 155 | int size = str.length(); 156 | if (size > 125) { 157 | write(WS_MASK | WS_SIZE16); 158 | write((uint8_t) (size >> 8)); 159 | write((uint8_t) (size & 0xFF)); 160 | } else { 161 | write(WS_MASK | (uint8_t) size); 162 | } 163 | 164 | // 3. send mask 165 | uint8_t mask[4]; 166 | mask[0] = random(0, 256); 167 | mask[1] = random(0, 256); 168 | mask[2] = random(0, 256); 169 | mask[3] = random(0, 256); 170 | 171 | write(mask[0]); 172 | write(mask[1]); 173 | write(mask[2]); 174 | write(mask[3]); 175 | 176 | //4. send masked data 177 | for (int i = 0; i < size; ++i) { 178 | write(str[i] ^ mask[i % 4]); 179 | } 180 | } 181 | 182 | int WebSocketClient::timedRead() { 183 | while (!client->available()) { 184 | delay(20); 185 | } 186 | return client->read(); 187 | } 188 | 189 | bool WebSocketClient::getMessage(String& message) { 190 | if (!client->connected()) { return false; } 191 | 192 | // 1. read type and fin 193 | unsigned int msgtype = timedRead(); 194 | if (!client->connected()) { 195 | DEBUG_WS("Step 1"); 196 | return false; 197 | } 198 | 199 | // 2. read length and check if masked 200 | int length = timedRead(); 201 | bool hasMask = false; 202 | if (length & WS_MASK) { 203 | hasMask = true; 204 | length = length & ~WS_MASK; 205 | } 206 | 207 | if (length == WS_SIZE16) { 208 | length = timedRead() << 8; 209 | length |= timedRead(); 210 | } 211 | 212 | // 3. read mask 213 | if (hasMask) { 214 | uint8_t mask[4]; 215 | mask[0] = timedRead(); 216 | mask[1] = timedRead(); 217 | mask[2] = timedRead(); 218 | mask[3] = timedRead(); 219 | 220 | // 4. read message (masked) 221 | message = ""; 222 | for (int i = 0; i < length; ++i) { 223 | message += (char) (timedRead() ^ mask[i % 4]); 224 | } 225 | } else { 226 | // 4. read message (unmasked) 227 | message = ""; 228 | for (int i = 0; i < length; ++i) { 229 | message += (char) timedRead(); 230 | } 231 | } 232 | 233 | return true; 234 | } 235 | -------------------------------------------------------------------------------- /WebSocketClient.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBSOCKETCLIENT_H 2 | #define WEBSOCKETCLIENT_H 3 | 4 | #include 5 | 6 | class WebSocketClient { 7 | public: 8 | 9 | WebSocketClient(bool secure = false); 10 | 11 | ~WebSocketClient(); 12 | 13 | bool connect(String host, String path, int port); 14 | 15 | bool isConnected(); 16 | 17 | void disconnect(); 18 | 19 | void send(const String& str); 20 | 21 | bool getMessage(String& message); 22 | 23 | void setAuthorizationHeader(String header); 24 | 25 | private: 26 | int timedRead(); 27 | 28 | void write(uint8_t data); 29 | 30 | void write(const char *str); 31 | 32 | String generateKey(); 33 | 34 | WiFiClient *client; 35 | 36 | String authorizationHeader = ""; 37 | 38 | bool websocketEstablished = false; 39 | 40 | }; 41 | 42 | #endif //WEBSOCKETCLIENT_H 43 | -------------------------------------------------------------------------------- /examples/WebsocketSample/WebsocketSample.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "WebSocketClient.h" 3 | #include "ESP8266WiFi.h" 4 | 5 | WebSocketClient ws(true); 6 | 7 | void setup() { 8 | Serial.begin(115200); 9 | WiFi.begin("MyWifi", "secret"); 10 | 11 | Serial.print("Connecting"); 12 | while (WiFi.status() != WL_CONNECTED) { 13 | delay(500); 14 | Serial.print("."); 15 | } 16 | } 17 | 18 | void loop() { 19 | if (!ws.isConnected()) { 20 | ws.connect("echo.websocket.org", "/", 443); 21 | } else { 22 | ws.send("hello"); 23 | 24 | String msg; 25 | if (ws.getMessage(msg)) { 26 | Serial.println(msg); 27 | } 28 | } 29 | delay(500); 30 | } 31 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ESP8266 Websocket Client 2 | version=1.0.2 3 | author=Christian Heller 4 | maintainer=mail@christian-heller.net 5 | sentence=Arduino library for consuming Websockets using an ESP8266 6 | paragraph=Arduino library for consuming Websockets using an ESP8266 7 | category=Display 8 | url=https://github.com/hellerchr/esp8266-websocketclient 9 | architectures= 10 | --------------------------------------------------------------------------------