├── .github └── FUNDING.yml ├── AWSWebSocketClient.cpp ├── AWSWebSocketClient.h ├── CircularByteBuffer.h ├── LICENSE ├── README.md └── examples ├── aws-DHT-publish-example └── DHT-publish.ino ├── aws-mqtt-websocket-example-paho └── aws-mqtt-websocket-example-paho.ino └── aws-mqtt-websocket-example-pubsubclient └── aws-mqtt-websocket-example-pubsubclient.ino /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | buy_me_a_coffee: odelot 4 | -------------------------------------------------------------------------------- /AWSWebSocketClient.cpp: -------------------------------------------------------------------------------- 1 | #include "AWSWebSocketClient.h" 2 | 3 | //work as a singleton to be used by websocket layer message callback 4 | AWSWebSocketClient* AWSWebSocketClient::instance = NULL; 5 | 6 | String AWSWebSocketClient::ntpFixNumber (int number) { 7 | String ret = ""; 8 | if (number < 10) { 9 | ret = "0"; 10 | ret += number; 11 | return ret; 12 | } 13 | else{ 14 | ret += number; 15 | return ret; 16 | } 17 | } 18 | 19 | //assuming you have already configure time with a ntp server (also needed by validate CA) 20 | String AWSWebSocketClient::getCurrentTimeNTP (void) { 21 | time_t now = time(nullptr); 22 | struct tm timeinfo; 23 | gmtime_r(&now, &timeinfo); 24 | String date = ""; 25 | date += (1900+timeinfo.tm_year); 26 | date += ntpFixNumber(timeinfo.tm_mon+1); 27 | date += ntpFixNumber(timeinfo.tm_mday); 28 | date += ntpFixNumber(timeinfo.tm_hour); 29 | date += ntpFixNumber(timeinfo.tm_min); 30 | date += ntpFixNumber(timeinfo.tm_sec); 31 | return date; 32 | } 33 | 34 | //callback to handle messages coming from the websocket layer 35 | void AWSWebSocketClient::webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { 36 | 37 | switch(type) { 38 | case WStype_DISCONNECTED: 39 | DEBUG_WEBSOCKET_MQTT("[AWSc] Disconnected!\n"); 40 | AWSWebSocketClient::instance->stop (30000); 41 | break; 42 | case WStype_CONNECTED: 43 | DEBUG_WEBSOCKET_MQTT("[AWSc] Connected to url: %d %s\n", length, payload); 44 | AWSWebSocketClient::instance->_connected = true; 45 | break; 46 | case WStype_TEXT: 47 | DEBUG_WEBSOCKET_MQTT("[WSc] get text: %s\n", payload); 48 | AWSWebSocketClient::instance->putMessage (payload, length); 49 | break; 50 | case WStype_BIN: 51 | DEBUG_WEBSOCKET_MQTT("[WSc] get binary length: %u\n", length); 52 | //hexdump(payload, length); 53 | AWSWebSocketClient::instance->putMessage (payload, length); 54 | break; 55 | } 56 | } 57 | 58 | //constructor 59 | AWSWebSocketClient::AWSWebSocketClient (unsigned int bufferSize, unsigned long connectionTimeout) { 60 | useSSL = true; 61 | _connectionTimeout = connectionTimeout; 62 | AWSWebSocketClient:instance = this; 63 | onEvent(AWSWebSocketClient::webSocketEvent); 64 | awsRegion = NULL; 65 | awsSecKey = NULL; 66 | awsKeyID = NULL; 67 | awsDomain = NULL; 68 | awsToken = NULL; 69 | path = NULL; 70 | _connected = false; 71 | bb.init (bufferSize); 72 | lastTimeUpdate = 0; 73 | _useAmazonTimestamp = true; 74 | begin("", 0); 75 | } 76 | 77 | //destructor 78 | AWSWebSocketClient::~AWSWebSocketClient(void) { 79 | if (awsRegion != NULL) 80 | delete[] awsRegion; 81 | if (awsDomain != NULL) 82 | delete[] awsDomain; 83 | if (awsSecKey != NULL) 84 | delete[] awsSecKey; 85 | if (awsKeyID != NULL) 86 | delete[] awsKeyID; 87 | if (path != NULL) 88 | delete[] path; 89 | if (awsToken != NULL) 90 | delete[] awsToken; 91 | } 92 | 93 | // convert month to digits 94 | String AWSWebSocketClient::getMonth(String sM) { 95 | if(sM=="Jan") return "01"; 96 | if(sM=="Feb") return "02"; 97 | if(sM=="Mar") return "03"; 98 | if(sM=="Apr") return "04"; 99 | if(sM=="May") return "05"; 100 | if(sM=="Jun") return "06"; 101 | if(sM=="Jul") return "07"; 102 | if(sM=="Aug") return "08"; 103 | if(sM=="Sep") return "09"; 104 | if(sM=="Oct") return "10"; 105 | if(sM=="Nov") return "11"; 106 | if(sM=="Dec") return "12"; 107 | return "01"; 108 | } 109 | 110 | //get current time (UTC) from aws service (used to sign) 111 | String AWSWebSocketClient::getCurrentTimeAmazon (void) { 112 | 113 | int timeout_busy = 0; 114 | 115 | String GmtDate; 116 | String dateStamp; 117 | dateStamp ="19860618123600"; 118 | 119 | if (timeClient.connect(("aws.amazon.com"), 80)) { 120 | 121 | // send a bad header on purpose, so we get a 400 with a DATE: timestamp 122 | //Send Request 123 | timeClient.println(F("GET example.com/ HTTP/1.1")); 124 | timeClient.println(F("Connection: close")); 125 | timeClient.println(); 126 | 127 | while((!timeClient.available()) && (timeout_busy++ < 5000)){ 128 | // Wait until the client sends some data 129 | delay(1); 130 | } 131 | 132 | // kill client if timeout 133 | if(timeout_busy >= 5000) { 134 | timeClient.flush(); 135 | timeClient.stop(); 136 | DEBUG_WEBSOCKET_MQTT("timeout receiving timeserver data"); 137 | 138 | return dateStamp; 139 | } 140 | 141 | // read the http GET Response 142 | String req2 = timeClient.readString(); 143 | 144 | // close connection 145 | delay(1); 146 | timeClient.flush(); 147 | timeClient.stop(); 148 | int ipos = req2.indexOf("Date:"); 149 | if(ipos > 0) { 150 | String gmtDate = req2.substring(ipos, ipos + 35); 151 | String utctime = gmtDate.substring(18,22) + getMonth(gmtDate.substring(14,17)) + gmtDate.substring(11,13) + gmtDate.substring(23,25) + gmtDate.substring(26,28) + gmtDate.substring(29,31); 152 | dateStamp = utctime.substring(0, 14); 153 | } 154 | } 155 | else { 156 | DEBUG_WEBSOCKET_MQTT("did not connect to timeserver"); 157 | } 158 | 159 | timeout_busy=0; // reset timeout 160 | 161 | return dateStamp; // Return latest or last good dateStamp 162 | } 163 | 164 | /* Converts an integer value to its hex character*/ 165 | char to_hex(char code) { 166 | static char hex[] = "0123456789ABCDEF"; 167 | return hex[code & 15]; 168 | } 169 | 170 | /* Returns a url-encoded version of str */ 171 | /* IMPORTANT: be sure to free() the returned string after use */ 172 | char* url_encode(const char* str) { 173 | const char* pstr = str; 174 | char *buf = (char*) malloc(strlen(str) * 3 + 1), *pbuf = buf; 175 | while (*pstr) { 176 | if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~') 177 | *pbuf++ = *pstr; 178 | else if (*pstr == ' ') 179 | *pbuf++ = '+'; 180 | else 181 | *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15); 182 | pstr++; 183 | } 184 | *pbuf = '\0'; 185 | return buf; 186 | } 187 | 188 | //generate AWS url path, signed using url parameters 189 | char* AWSWebSocketClient::generateAWSPath (uint16_t port) { 190 | 191 | String dateTime; 192 | if (_useAmazonTimestamp) { 193 | dateTime = getCurrentTimeAmazon (); 194 | } else { 195 | dateTime = getCurrentTimeNTP (); 196 | } 197 | DEBUG_WEBSOCKET_MQTT (dateTime.c_str()); 198 | String awsService = F("iotdevicegateway"); 199 | char awsDate[9]; 200 | strncpy(awsDate, dateTime.c_str(), 8); 201 | awsDate[8] = '\0'; 202 | char awsTime[7]; 203 | strncpy(awsTime, dateTime.c_str() + 8, 6); 204 | awsTime[6] = '\0'; 205 | 206 | char credentialScope[strlen(awsDate)+strlen(awsRegion)+strlen(awsService.c_str())+16]; 207 | sprintf(credentialScope, "%s/%s/%s/aws4_request",awsDate,awsRegion,awsService.c_str()); 208 | String key_credential (awsKeyID); 209 | key_credential+="/"; 210 | key_credential+=credentialScope; 211 | //FIX tried a lot of escape functions, but no one was equal to escapeURL from javascript 212 | char* ekey_credential = url_encode (key_credential.c_str ()); 213 | key_credential = ekey_credential; 214 | free (ekey_credential); 215 | 216 | String method = F("GET"); 217 | String canonicalUri = F("/mqtt"); 218 | String algorithm = F("AWS4-HMAC-SHA256"); 219 | String token = ""; 220 | //adding AWS STS security token for temporary AIM credentials 221 | if (awsToken != NULL) { 222 | char* eToken = url_encode (awsToken); 223 | token = eToken; 224 | free (eToken); 225 | 226 | } 227 | 228 | char canonicalQuerystring [strlen(algorithm.c_str())+strlen(key_credential.c_str())+strlen(awsDate)+strlen(awsTime)+strlen (token.c_str())+225]; 229 | sprintf(canonicalQuerystring, "X-Amz-Algorithm=%s", algorithm.c_str()); 230 | sprintf(canonicalQuerystring, "%s&X-Amz-Credential=%s", canonicalQuerystring,key_credential.c_str()); 231 | sprintf(canonicalQuerystring, "%s&X-Amz-Date=%sT%sZ", canonicalQuerystring, awsDate,awsTime); 232 | sprintf(canonicalQuerystring, "%s&X-Amz-Expires=86400", canonicalQuerystring); //sign will last one day 233 | sprintf(canonicalQuerystring, "%s&X-Amz-SignedHeaders=host", canonicalQuerystring); 234 | 235 | String portString = String(port); 236 | char canonicalHeaders [strlen (awsDomain)+ strlen (portString.c_str())+ 8]; 237 | 238 | sprintf(canonicalHeaders, "host:%s:%s\n", awsDomain,portString.c_str()); 239 | SHA256* sha256 = new SHA256(); 240 | char* payloadHash = (*sha256)("", 0); 241 | delete sha256; 242 | char canonicalRequest [strlen (method.c_str())+ strlen (canonicalUri.c_str())+strlen(canonicalQuerystring)+strlen(canonicalHeaders)+strlen(payloadHash)+10]; 243 | 244 | sprintf(canonicalRequest, "%s\n%s\n%s\n%s\nhost\n%s", method.c_str(),canonicalUri.c_str(),canonicalQuerystring,canonicalHeaders,payloadHash); 245 | delete[] payloadHash; 246 | 247 | sha256 = new SHA256(); 248 | char* requestHash = (*sha256)(canonicalRequest, strlen (canonicalRequest)); 249 | delete sha256; 250 | char stringToSign[strlen (algorithm.c_str())+ strlen(awsDate)+strlen (awsTime)+strlen(credentialScope)+strlen(requestHash)+6]; 251 | sprintf(stringToSign, "%s\n%sT%sZ\n%s\n%s", algorithm.c_str(),awsDate,awsTime,credentialScope,requestHash); 252 | delete[] requestHash; 253 | 254 | /* Allocate memory for the signature */ 255 | char signature [HASH_HEX_LEN2 + 1]; 256 | 257 | /* Create the signature key */ 258 | /* + 4 for "AWS4" */ 259 | int keyLen = strlen(awsSecKey) + 4; 260 | char key[keyLen + 1]; 261 | sprintf(key, "AWS4%s", awsSecKey); 262 | 263 | char* k1 = hmacSha256(key, keyLen, awsDate, strlen(awsDate)); 264 | 265 | char* k2 = hmacSha256(k1, SHA256_DEC_HASH_LEN, awsRegion, 266 | strlen(awsRegion)); 267 | 268 | delete[] k1; 269 | char* k3 = hmacSha256(k2, SHA256_DEC_HASH_LEN, awsService.c_str(), 270 | strlen(awsService.c_str())); 271 | 272 | delete[] k2; 273 | char* k4 = hmacSha256(k3, SHA256_DEC_HASH_LEN, "aws4_request", 12); 274 | 275 | delete[] k3; 276 | char* k5 = hmacSha256(k4, SHA256_DEC_HASH_LEN, stringToSign, strlen(stringToSign)); 277 | 278 | delete[] k4; 279 | // Convert the chars in hash to hex for signature. 280 | for (int i = 0; i < SHA256_DEC_HASH_LEN; ++i) { 281 | sprintf(signature + 2 * i, "%02lx", 0xff & (unsigned long) k5[i]); 282 | } 283 | delete[] k5; 284 | 285 | sprintf(canonicalQuerystring, "%s&X-Amz-Signature=%s", canonicalQuerystring, signature); 286 | 287 | //adding AWS STS security token for temporary AIM credentials 288 | if (awsToken != NULL) { 289 | sprintf(canonicalQuerystring, "%s&X-Amz-Security-Token=%s", canonicalQuerystring,token.c_str()); 290 | } 291 | 292 | char* requestUri = new char[strlen(canonicalUri.c_str())+strlen(canonicalQuerystring)+2](); 293 | sprintf(requestUri, "%s?%s", canonicalUri.c_str(),canonicalQuerystring); 294 | 295 | return requestUri; 296 | 297 | } 298 | 299 | AWSWebSocketClient& AWSWebSocketClient::setAWSRegion(const char * awsRegion) { 300 | if (this->awsRegion != NULL) 301 | delete[] this->awsRegion; 302 | int len = strlen(awsRegion) + 1; 303 | this->awsRegion = new char[len](); 304 | strcpy(this->awsRegion, awsRegion); 305 | return *this; 306 | } 307 | 308 | AWSWebSocketClient& AWSWebSocketClient::setAWSDomain(const char * awsDomain) { 309 | if (this->awsDomain != NULL) 310 | delete[] this->awsDomain; 311 | int len = strlen(awsDomain) + 1; 312 | this->awsDomain = new char[len](); 313 | strcpy(this->awsDomain, awsDomain); 314 | return *this; 315 | } 316 | 317 | AWSWebSocketClient& AWSWebSocketClient::setAWSSecretKey(const char * awsSecKey) { 318 | if (this->awsSecKey != NULL) 319 | { 320 | if (strlen (awsSecKey) == strlen(this->awsSecKey)){ 321 | strcpy(this->awsSecKey, awsSecKey); 322 | return *this; 323 | } else { 324 | delete[] this->awsSecKey; 325 | } 326 | 327 | } 328 | int len = strlen(awsSecKey) + 1; 329 | this->awsSecKey = new char[len](); 330 | strcpy(this->awsSecKey, awsSecKey); 331 | return *this; 332 | } 333 | AWSWebSocketClient& AWSWebSocketClient::setAWSKeyID(const char * awsKeyID) { 334 | if (this->awsKeyID != NULL) { 335 | if (strlen (awsKeyID) == strlen(this->awsKeyID)){ 336 | strcpy(this->awsKeyID, awsKeyID); 337 | return *this; 338 | } else { 339 | delete[] this->awsKeyID; 340 | } 341 | } 342 | 343 | int len = strlen(awsKeyID) + 1; 344 | this->awsKeyID = new char[len](); 345 | strcpy(this->awsKeyID, awsKeyID); 346 | return *this; 347 | } 348 | 349 | AWSWebSocketClient& AWSWebSocketClient::setPath(const char * path) { 350 | if (this->path != NULL) 351 | delete[] this->path; 352 | int len = strlen(path) + 1; 353 | this->path = new char[len](); 354 | strcpy(this->path, path); 355 | return *this; 356 | } 357 | 358 | AWSWebSocketClient& AWSWebSocketClient::setCA(const char * ca) { 359 | this->ca = ca; 360 | return *this; 361 | } 362 | 363 | AWSWebSocketClient& AWSWebSocketClient::setUseAmazonTimestamp(bool useAmazonTimestamp) { 364 | this->_useAmazonTimestamp = useAmazonTimestamp; 365 | return *this; 366 | } 367 | 368 | AWSWebSocketClient& AWSWebSocketClient::setAWSToken(const char * awsToken) { 369 | if (this->awsToken != NULL) 370 | { 371 | if (strlen (awsToken) == strlen(this->awsToken)){ 372 | strcpy(this->awsToken, awsToken); 373 | return *this; 374 | } else { 375 | delete[] this->awsToken; 376 | } 377 | } 378 | int len = strlen(awsToken) + 1; 379 | this->awsToken = new char[len](); 380 | strcpy(this->awsToken, awsToken); 381 | return *this; 382 | } 383 | 384 | int AWSWebSocketClient::connect(IPAddress ip, uint16_t port){ 385 | return connect (awsDomain,port); 386 | } 387 | 388 | int AWSWebSocketClient::connect(const String& host, uint16_t port){ 389 | return connect (awsDomain,port); 390 | } 391 | 392 | int AWSWebSocketClient::connect(const char *host, uint16_t port) { 393 | //disconnect first 394 | stop (_connectionTimeout); 395 | char* path = this->path; 396 | //just need to free path if it was generated to connect to AWS 397 | bool freePath = false; 398 | if (this->path == NULL) { 399 | //just generate AWS Path if user does not inform its own (to support the lib usage out of aws) 400 | path = generateAWSPath (port); 401 | freePath = true; 402 | } 403 | if (useSSL == true) 404 | beginSslWithCA (host,port,(const char*)path,(const char*)ca,"mqtt"); 405 | else 406 | begin (host,port,path,F("mqtt")); 407 | long now = millis (); 408 | while ( (millis ()-now) < _connectionTimeout) { 409 | loop (); 410 | if (connected () == 1) { 411 | if (freePath == true) 412 | delete[] path; 413 | return 1; 414 | } 415 | delay (10); 416 | } 417 | if (freePath == true) 418 | delete[] path; 419 | return 0; 420 | } 421 | 422 | //store messages arrived by websocket layer to be consumed by mqtt layer through the read funcion 423 | void AWSWebSocketClient::putMessage (byte* buffer, int length) { 424 | bb.push (buffer,length); 425 | } 426 | 427 | size_t AWSWebSocketClient::write(uint8_t b) { 428 | if (_connected == false) 429 | return -1; 430 | return write (&b,1); 431 | } 432 | 433 | //write through websocket layer 434 | size_t AWSWebSocketClient::write(const uint8_t *buf, size_t size) { 435 | if (_connected == false) 436 | return -1; 437 | if (sendBIN (buf,size)) 438 | return size; 439 | return 0; 440 | } 441 | 442 | //return with there is bytes to consume from the circular buffer (used by mqtt layer) 443 | int AWSWebSocketClient::available(){ 444 | //force websocket to handle it messages 445 | if (_connected == false) 446 | return false; 447 | loop (); 448 | return bb.getSize (); 449 | } 450 | 451 | //read from circular buffer (used by mqtt layer) 452 | int AWSWebSocketClient::read() { 453 | if (_connected == false) 454 | return -1; 455 | return bb.pop (); 456 | } 457 | 458 | //read from circular buffer (used by mqtt layer) 459 | int AWSWebSocketClient::read(uint8_t *buf, size_t size) { 460 | if (_connected == false) 461 | return -1; 462 | int s = size; 463 | if (bb.getSize() 5 | #include 6 | #include "Client.h" 7 | #include "WebSocketsClient.h" 8 | #include "CircularByteBuffer.h" 9 | #include "sha256.h" 10 | #include "Utils.h" 11 | #include 12 | 13 | static const int HASH_HEX_LEN2 = 64; 14 | 15 | //#define DEBUG_WEBSOCKET_MQTT(...) os_printf( __VA_ARGS__ ) 16 | 17 | #ifndef DEBUG_WEBSOCKET_MQTT 18 | #define DEBUG_WEBSOCKET_MQTT(...) 19 | #define NODEBUG_WEBSOCKET_MQTT 20 | #endif 21 | 22 | class AWSWebSocketClient : public Client, private WebSocketsClient { 23 | public: 24 | 25 | //bufferSize defines the size of the circular byte buffer that provides the interface between messages arrived in websocket layer and byte reads from mqtt layer 26 | AWSWebSocketClient (unsigned int bufferSize = 1000, unsigned long connectionTimeout = 50000); 27 | ~AWSWebSocketClient(); 28 | 29 | virtual int connect(IPAddress ip, uint16_t port) override; 30 | virtual int connect(const char *host, uint16_t port) override; 31 | virtual int connect(const String& host, uint16_t port); 32 | 33 | void putMessage (byte* buffer, int length); 34 | size_t write(uint8_t b); 35 | size_t write(const uint8_t *buf, size_t size); 36 | int available(); 37 | int read(); 38 | int read(uint8_t *buf, size_t size); 39 | 40 | int peek(); 41 | bool flush(unsigned int maxWaitMs); 42 | bool stop(unsigned int maxWaitMs); 43 | virtual void flush() override { (void)flush(0); } 44 | virtual void stop() override { (void)stop(0); } 45 | uint8_t connected() ; 46 | operator bool(); 47 | 48 | bool getUseSSL (); 49 | 50 | AWSWebSocketClient& setUseSSL (bool value); 51 | AWSWebSocketClient& setAWSRegion(const char * awsRegion); 52 | AWSWebSocketClient& setAWSDomain(const char * awsDomain); 53 | AWSWebSocketClient& setAWSSecretKey(const char * awsSecKey); 54 | AWSWebSocketClient& setAWSKeyID(const char * awsKeyID); 55 | AWSWebSocketClient& setPath(const char * path); 56 | AWSWebSocketClient& setAWSToken(const char * awsToken); 57 | AWSWebSocketClient& setFingerprint(const char * fingerprint); 58 | AWSWebSocketClient& setCA(const char * ca); 59 | AWSWebSocketClient& setUseAmazonTimestamp(bool useAmazonTimestamp); 60 | 61 | protected: 62 | //generate AWS signed path 63 | char* generateAWSPath (uint16_t port); 64 | 65 | //convert the month info 66 | String getMonth(String sM); 67 | //get current time (UTC) from aws service (used to sign) 68 | String getCurrentTimeAmazon(void); 69 | 70 | String getCurrentTimeNTP(void); 71 | 72 | String ntpFixNumber (int number); 73 | 74 | //static instance of aws websocket client 75 | static AWSWebSocketClient* instance; 76 | //keep the connection state 77 | bool _connected; 78 | //websocket callback 79 | static void webSocketEvent(WStype_t type, uint8_t * payload, size_t length); 80 | 81 | private: 82 | 83 | //enable ssl... if your using mqtt over websockets at AWS IoT service, it must be enabled 84 | bool useSSL; 85 | 86 | //connection timeout 87 | unsigned long _connectionTimeout; 88 | 89 | //useAmazonTimestamp 90 | bool _useAmazonTimestamp; 91 | 92 | char* path; 93 | /* Name of region, eg. "us-east-1" in "kinesis.us-east-1.amazonaws.com". */ 94 | char* awsRegion; 95 | /* Domain, optional, eg. "A2MBBEONHC9LUG.iot.us-east-1.amazonaws.com". */ 96 | char* awsDomain; 97 | /* The user's AWS Secret Key for accessing the AWS Resource. */ 98 | char* awsSecKey; 99 | /* The user's AWS Access Key ID for accessing the AWS Resource. */ 100 | char* awsKeyID; 101 | /* The user's AWS Security Token for temporary credentials (just use with AWS STS). */ 102 | char* awsToken; 103 | /* root certificate */ 104 | const char* ca; 105 | 106 | unsigned long lastTimeUpdate; 107 | //circular buffer to keep incoming messages from websocket 108 | CircularByteBuffer bb; 109 | 110 | //connection to get current time 111 | WiFiClient timeClient; 112 | }; 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /CircularByteBuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef __CIRCULARBYTEBUFFER_H_ 2 | #define __CIRCULARBYTEBUFFER_H_ 3 | 4 | //#define DEBUG_CBB(...) os_printf( __VA_ARGS__ ) 5 | 6 | #ifndef DEBUG_CBB 7 | #define DEBUG_CBB(...) 8 | #define NODEBUG_CBB 9 | #endif 10 | 11 | class CircularByteBuffer { 12 | public: 13 | 14 | CircularByteBuffer () { 15 | data = NULL; 16 | size = 0; 17 | capacity = 0; 18 | } 19 | ~CircularByteBuffer(){ 20 | if (data!=NULL) 21 | free (data); 22 | } 23 | 24 | void clear () 25 | { 26 | memset(data,0,capacity); 27 | size = 0; 28 | begin = 0; 29 | end = 0; 30 | } 31 | 32 | void deallocate () { 33 | if (data!=NULL) { 34 | free (data); 35 | data = NULL; 36 | } 37 | } 38 | 39 | void init (long capacity) { 40 | if (data!=NULL) 41 | free (data); 42 | data = (byte*) malloc (capacity); 43 | size = 0; 44 | begin = 0; 45 | end = 0; 46 | this->capacity = capacity; 47 | } 48 | 49 | long getSize () { 50 | return size; 51 | } 52 | 53 | byte peek () { 54 | return data[begin]; 55 | } 56 | void push (byte b) { 57 | if (size+1 == capacity) { 58 | DEBUG_CBB ("buffer full"); 59 | return; 60 | } 61 | data[end] = b; 62 | end = (end+1) % capacity; 63 | size += 1; 64 | 65 | } 66 | 67 | byte pop () { 68 | if (size == 0){ 69 | DEBUG_CBB ("buffer empty"); 70 | return 0; 71 | } 72 | byte ret = data[begin]; 73 | begin = (begin+1) % capacity; 74 | size -= 1; 75 | return ret; 76 | 77 | } 78 | 79 | void push (byte* b, long len){ 80 | if (size+len >= capacity) { 81 | DEBUG_CBB ("buffer full"); 82 | return; 83 | } 84 | if ( (end + len) <= capacity ) { 85 | memcpy ((void*)&data[end],b,len); 86 | end += len; 87 | } else { 88 | long endSide = capacity - end; 89 | memcpy ((void*)&data[end],b,endSide); 90 | end = 0; 91 | memcpy ((void*)&data[end],b+endSide,len-endSide); 92 | end += len-endSide; 93 | } 94 | size += len; 95 | 96 | } 97 | 98 | byte* pop (byte* b, long len){ 99 | if ( (size - len) < 0 ) { 100 | DEBUG_CBB ("buffer empty"); 101 | return NULL; 102 | } 103 | if ( (begin + len) <= capacity ) { 104 | memcpy (b,(void*)&data[begin],len); 105 | begin += len; 106 | } else { 107 | long endSide = capacity - begin; 108 | memcpy (b,(void*)&data[begin],endSide); 109 | begin = 0; 110 | memcpy (b+endSide,(void*)&data[begin],len-endSide); 111 | begin += len-endSide; 112 | } 113 | size -= len; 114 | } 115 | 116 | private: 117 | byte* data; 118 | long capacity; 119 | long size; 120 | long begin; 121 | long end; 122 | }; 123 | 124 | 125 | 126 | #endif 127 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aws-mqtt-websockets 2 | Implementation of a middleware to use AWS MQTT service through websockets. Aiming the esp8266 platform 3 | 4 | ## ChangeLog 5 | * **1.3.0** - update lib and pubsubclient example to use the newest versions of arduino/esp (2.7.3), arduinoWebSocket (2.2.0) and pubsubclient (2.8.0) - now you need to use a certificate to connect to AWS - you can use the certificate alternatives in the pubsubclient example that should be valid up to 2036/2037 6 | * 1.2.0 - using pubsubclient there isn't the "many reconnection issue" (see pubsubclient example to migrate from paho) - get time from pool.ntp.org - tested with arduinoWebSockets v.2.1.0, arduino/esp sdk 2.4.1 and pubsubclient version v2.6.0 7 | * 1.1.0 - can use AWS STS temporary credentials - change some dynamic to static memory allocation to avoid memory fragmentation 8 | * 1.0.1 - works with arduinoWebSockets v.2.0.5 and arduino/esp sdk 2.3.0 9 | * 1.0.alpha - stable - works with arduinoWebSockets v.2.0.2 and arduino/esp sdk 2.1.0 10 | * 0.3 - own impl of circular buffer 11 | * 0.2 - auto reconnection 12 | * 0.1 - has known limitation and it was not extensively tested 13 | 14 | ## Motivation 15 | 16 | As we cannot use AWS MQTT service directly because of the lack of support for TLS 1.2*, we need to use the websocket communication as a transport layer for MQTT through SSL (supported by esp8266) 17 | 18 | This way we can change the state of your esp8266 devices in realtime, without using the AWS Restful API and busy-waiting inefficient approach. 19 | 20 | * now you can use TLS 1.2 and use the certificates generated by amazon. Here is it an example https://github.com/copercini/esp8266-aws_iot if you wanna try 21 | 22 | ## Donate 23 | 24 | if you fell like thank me, you can buy me a coffe (or a beer) https://www.buymeacoffee.com/odelot cheers! 25 | 26 | ## Dependencies 27 | 28 | | Library | Link | Use | 29 | |---------------------------|-----------------------------------------------------------------|---------------------| 30 | |aws-sdk-arduino |https://github.com/odelot/aws-sdk-arduino |aws signing functions| 31 | |arduinoWebSockets |https://github.com/Links2004/arduinoWebSockets |websocket comm impl | 32 | 33 | **Works with these MQTT clients** - Use one or another - see examples 34 | 35 | | Library | Link | Use | 36 | |-------------------------------|-----------------------------------------------------------------|---------------------| 37 | |PubSubClient (recommended) |https://github.com/knolleary/pubsubclient |mqtt comm impl | 38 | |Paho MQTT for Arduino |https://projects.eclipse.org/projects/technology.paho/downloads |mqtt comm impl | 39 | 40 | ## Installation 41 | 42 | 1. Configure your arduino ide to compile and upload programs to ESP8266 (Arduino core for ESP8266 - details https://github.com/esp8266/Arduino )\*\* 43 | 2. Install all the dependencies as Arduino Libraries 44 | 3. Install aws-mqtt-websockets as Arduino Library as well 45 | 4. Configure the example file with your AWS credencials and endpoints (**remember to grant iot permissions for your user**) 46 | 5. Compile, upload and run! 47 | 48 | \** The library was tested with 2.7.3 version of Arduino core for ESP8266 49 | 50 | ## Usage 51 | 52 | It is transparent. It is the same as the usage of Paho. There is just some changes in the connection step. See the example for details. Things you should edit in the example: 53 | * ssid and password to connect to wifi 54 | * domain/endpoint for your aws iot service 55 | * region of your aws iot service 56 | * aws user key \*\* 57 | * aws user secret key 58 | 59 | \*\* It is a good practice creating a new user (and grant just **iot services permission**). Avoid use the key/secret key of your main aws console user 60 | 61 | ``` 62 | //AWS IOT config, change these: 63 | char wifi_ssid[] = "your-ssid"; 64 | char wifi_password[] = "your-password"; 65 | char aws_endpoint[] = "your-endpoint.iot.eu-west-1.amazonaws.com"; 66 | char aws_key[] = "your-iam-key"; 67 | char aws_secret[] = "your-iam-secret-key"; 68 | char aws_region[] = "eu-west-1"; 69 | const char* aws_topic = "$aws/things/your-device/shadow/update"; 70 | int port = 443; 71 | 72 | //MQTT config 73 | const int maxMQTTpackageSize = 128; 74 | const int maxMQTTMessageHandlers = 1; 75 | ``` 76 | 77 | ## Grant IoT Permission in AWS Console 78 | 79 | * Go to https://console.aws.amazon.com/ 80 | * Then click IAM 81 | * Then click policy. find your policy or create a new policy 82 | * set service to IOT 83 | * set action to iot:* 84 | * set resouce to all resources 85 | 86 | ## AWS STS Temporary Credential 87 | 88 | To avoid having a long term credential hardcoded in our device, you can create temporary credentials that will last up to 36 hours using the AWS STS service (learn more here http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html). 89 | 90 | Using STS you will get a AWS key, AWS secret and AWS token. To inform the AWS token, use the following method 91 | 92 | ``` 93 | //it is just an example. As the credential last up to 36 hours, you will need to get temporary credential every 36 hours 94 | //you won't use it hard coded 95 | char token[] = 'FQoDYXdzEHgaDN7ZQSxqszH+LgBTXCKsAeU5dsW/g3BK01wyYoBk0vnCfz+D19w2kslSC5drDXyN9Nxx14WcgrOOWNxHsLRDPkcrYhw6DIkW1Nvv1mKu3i86riq19qhBose7v1XngRLBQwgfU/HnlIzJegNEEGgeMAkX0ErF77WfV2pxCzF6ZMRv7kn+a6yE2LURLg/M8eq3lYoyQcJFq55JfVPVUIpx/avEsjgCR/MvlHXlhtJqviClB3mRlvwBcz4vpq4ogpKnzAU='; 96 | awsWSclient.setAWSToken (token); 97 | ``` 98 | -------------------------------------------------------------------------------- /examples/aws-DHT-publish-example/DHT-publish.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * Example working with library v1.2 3 | * 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // DHT22 - https://learn.adafruit.com/dht, https://github.com/adafruit/DHT-sensor-library 12 | #include "DHT.h" 13 | 14 | //AWS 15 | #include "sha256.h" 16 | #include "Utils.h" 17 | 18 | //WEBSockets 19 | #include 20 | #include 21 | 22 | //MQTT PAHO 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | //AWS MQTT Websocket 29 | #include "Client.h" 30 | #include "AWSWebSocketClient.h" 31 | #include "CircularByteBuffer.h" 32 | 33 | // --------- Config ---------- // 34 | //AWS IOT config, change these: 35 | char wifi_ssid[] = "your-ssid"; 36 | char wifi_password[] = "your-password"; 37 | char aws_endpoint[] = "your-endpoint.iot.eu-west-1.amazonaws.com"; 38 | char aws_key[] = "your-iam-key"; 39 | char aws_secret[] = "your-iam-secret-key"; 40 | char aws_region[] = "eu-west-1"; 41 | const char* aws_topic = "$aws/things/your-device/shadow/update"; 42 | int port = 443; 43 | 44 | // If stuff isn't working right, watch the console: 45 | #define DEBUG_PRINT 1 46 | 47 | #define DHTPIN 5 48 | #define DHTTYPE DHT22 //Set for 11, 21, or 22 49 | 50 | //MQTT config 51 | const int maxMQTTpackageSize = 512; 52 | const int maxMQTTMessageHandlers = 1; 53 | // ---------- /Config ----------// 54 | 55 | DHT dht(DHTPIN, DHTTYPE); 56 | ESP8266WiFiMulti WiFiMulti; 57 | 58 | AWSWebSocketClient awsWSclient(1000); 59 | 60 | IPStack ipstack(awsWSclient); 61 | MQTT::Client *client = NULL; 62 | 63 | //# of connections 64 | long connection = 0; 65 | 66 | //generate random mqtt clientID 67 | char* generateClientID () { 68 | char* cID = new char[23](); 69 | for (int i=0; i<22; i+=1) 70 | cID[i]=(char)random(1, 256); 71 | return cID; 72 | } 73 | 74 | //count messages arrived 75 | int arrivedcount = 0; 76 | 77 | //callback to handle mqtt messages 78 | void messageArrived(MQTT::MessageData& md) 79 | { 80 | MQTT::Message &message = md.message; 81 | 82 | if (DEBUG_PRINT) { 83 | Serial.print("Message "); 84 | Serial.print(++arrivedcount); 85 | Serial.print(" arrived: qos "); 86 | Serial.print(message.qos); 87 | Serial.print(", retained "); 88 | Serial.print(message.retained); 89 | Serial.print(", dup "); 90 | Serial.print(message.dup); 91 | Serial.print(", packetid "); 92 | Serial.println(message.id); 93 | Serial.print("Payload "); 94 | char* msg = new char[message.payloadlen+1](); 95 | memcpy (msg,message.payload,message.payloadlen); 96 | Serial.println(msg); 97 | delete msg; 98 | } 99 | } 100 | 101 | //connects to websocket layer and mqtt layer 102 | bool connect () { 103 | 104 | if (client == NULL) { 105 | client = new MQTT::Client(ipstack); 106 | } else { 107 | 108 | if (client->isConnected ()) { 109 | client->disconnect (); 110 | } 111 | delete client; 112 | client = new MQTT::Client(ipstack); 113 | } 114 | 115 | 116 | //delay is not necessary... it just help us to get a "trustful" heap space value 117 | delay (1000); 118 | if (DEBUG_PRINT) { 119 | Serial.print (millis ()); 120 | Serial.print (" - conn: "); 121 | Serial.print (++connection); 122 | Serial.print (" - ("); 123 | Serial.print (ESP.getFreeHeap ()); 124 | Serial.println (")"); 125 | } 126 | 127 | int rc = ipstack.connect(aws_endpoint, port); 128 | if (rc != 1) 129 | { 130 | if (DEBUG_PRINT) { 131 | Serial.println("error connection to the websocket server"); 132 | } 133 | return false; 134 | } else { 135 | if (DEBUG_PRINT) { 136 | Serial.println("websocket layer connected"); 137 | } 138 | } 139 | 140 | if (DEBUG_PRINT) { 141 | Serial.println("MQTT connecting"); 142 | } 143 | 144 | MQTTPacket_connectData data = MQTTPacket_connectData_initializer; 145 | data.MQTTVersion = 3; 146 | char* clientID = generateClientID (); 147 | data.clientID.cstring = clientID; 148 | rc = client->connect(data); 149 | delete[] clientID; 150 | if (rc != 0) 151 | { 152 | if (DEBUG_PRINT) { 153 | Serial.print("error connection to MQTT server"); 154 | Serial.println(rc); 155 | return false; 156 | } 157 | } 158 | if (DEBUG_PRINT) { 159 | Serial.println("MQTT connected"); 160 | } 161 | return true; 162 | } 163 | 164 | //subscribe to a mqtt topic 165 | void subscribe () { 166 | //subscrip to a topic 167 | int rc = client->subscribe(aws_topic, MQTT::QOS0, messageArrived); 168 | if (rc != 0) { 169 | if (DEBUG_PRINT) { 170 | Serial.print("rc from MQTT subscribe is "); 171 | Serial.println(rc); 172 | } 173 | return; 174 | } 175 | if (DEBUG_PRINT) { 176 | Serial.println("MQTT subscribed"); 177 | } 178 | } 179 | 180 | void setup() { 181 | Serial.begin (115200); 182 | WiFiMulti.addAP(wifi_ssid, wifi_password); 183 | 184 | while(WiFiMulti.run() != WL_CONNECTED) { 185 | delay(100); 186 | if (DEBUG_PRINT) { 187 | Serial.print (". "); 188 | } 189 | } 190 | if (DEBUG_PRINT) { 191 | Serial.println ("\nconnected to network " + String(wifi_ssid) + "\n"); 192 | } 193 | 194 | //fill AWS parameters 195 | awsWSclient.setAWSRegion(aws_region); 196 | awsWSclient.setAWSDomain(aws_endpoint); 197 | awsWSclient.setAWSKeyID(aws_key); 198 | awsWSclient.setAWSSecretKey(aws_secret); 199 | awsWSclient.setUseSSL(true); 200 | 201 | dht.begin(); 202 | } 203 | 204 | void loop() { 205 | // Reading temperature or humidity takes about 250 milliseconds! 206 | // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor) 207 | delay(10000); 208 | String h = String(dht.readHumidity()); // Read temperature as Fahrenheit (isFahrenheit = true) 209 | String f = String(dht.readTemperature(true)); 210 | 211 | if (isnan(dht.readHumidity()) || isnan(dht.readTemperature(true))) { 212 | Serial.println("Failed to read from DHT sensor!"); 213 | return; 214 | } else { 215 | if (DEBUG_PRINT) { 216 | Serial.print("Humidity: "); 217 | Serial.print(h); 218 | Serial.print(" %\t"); 219 | Serial.print("Temperature: "); 220 | Serial.print(f); 221 | Serial.print(" *F\t\n"); 222 | } 223 | }; 224 | 225 | String values = "{\"state\":{\"reported\":{\"temp\": " + f + ",\"humidity\": " + h + "}}}"; 226 | // http://stackoverflow.com/questions/31614364/arduino-joining-string-and-char 227 | const char *publish_message = values.c_str(); 228 | 229 | //keep the mqtt up and running 230 | if (awsWSclient.connected ()) { 231 | client->yield(); 232 | 233 | subscribe (); 234 | //publish 235 | MQTT::Message message; 236 | char buf[1000]; 237 | strcpy(buf, publish_message); 238 | message.qos = MQTT::QOS0; 239 | message.retained = false; 240 | message.dup = false; 241 | message.payload = (void*)buf; 242 | message.payloadlen = strlen(buf)+1; 243 | int rc = client->publish(aws_topic, message); 244 | } else { 245 | //handle reconnection 246 | connect (); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /examples/aws-mqtt-websocket-example-paho/aws-mqtt-websocket-example-paho.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * Example working with library v1.2 3 | * 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | //AWS 13 | #include "sha256.h" 14 | #include "Utils.h" 15 | 16 | 17 | //WEBSockets 18 | #include 19 | #include 20 | 21 | //MQTT PAHO 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | 28 | 29 | //AWS MQTT Websocket 30 | #include "Client.h" 31 | #include "AWSWebSocketClient.h" 32 | #include "CircularByteBuffer.h" 33 | 34 | extern "C" { 35 | #include "user_interface.h" 36 | } 37 | 38 | //AWS IOT config, change these: 39 | char wifi_ssid[] = "your-ssid"; 40 | char wifi_password[] = "your-password"; 41 | char aws_endpoint[] = "your-endpoint.iot.eu-west-1.amazonaws.com"; 42 | char aws_key[] = "your-iam-key"; 43 | char aws_secret[] = "your-iam-secret-key"; 44 | char aws_region[] = "eu-west-1"; 45 | const char* aws_topic = "$aws/things/your-device/shadow/update"; 46 | int port = 443; 47 | 48 | //MQTT config 49 | const int maxMQTTpackageSize = 512; 50 | const int maxMQTTMessageHandlers = 1; 51 | 52 | ESP8266WiFiMulti WiFiMulti; 53 | 54 | AWSWebSocketClient awsWSclient(1000); 55 | 56 | IPStack ipstack(awsWSclient); 57 | MQTT::Client client(ipstack); 58 | 59 | //# of connections 60 | long connection = 0; 61 | 62 | //generate random mqtt clientID 63 | char* generateClientID () { 64 | char* cID = new char[23](); 65 | for (int i=0; i<22; i+=1) 66 | cID[i]=(char)random(1, 256); 67 | return cID; 68 | } 69 | 70 | //count messages arrived 71 | int arrivedcount = 0; 72 | 73 | //callback to handle mqtt messages 74 | void messageArrived(MQTT::MessageData& md) 75 | { 76 | MQTT::Message &message = md.message; 77 | 78 | Serial.print("Message "); 79 | Serial.print(++arrivedcount); 80 | Serial.print(" arrived: qos "); 81 | Serial.print(message.qos); 82 | Serial.print(", retained "); 83 | Serial.print(message.retained); 84 | Serial.print(", dup "); 85 | Serial.print(message.dup); 86 | Serial.print(", packetid "); 87 | Serial.println(message.id); 88 | Serial.print("Payload "); 89 | char* msg = new char[message.payloadlen+1](); 90 | memcpy (msg,message.payload,message.payloadlen); 91 | Serial.println(msg); 92 | delete msg; 93 | } 94 | 95 | //connects to websocket layer and mqtt layer 96 | bool connect () { 97 | 98 | 99 | 100 | if (client.isConnected ()) { 101 | client.disconnect (); 102 | } 103 | //delay is not necessary... it just help us to get a "trustful" heap space value 104 | delay (1000); 105 | Serial.print (millis ()); 106 | Serial.print (" - conn: "); 107 | Serial.print (++connection); 108 | Serial.print (" - ("); 109 | Serial.print (ESP.getFreeHeap ()); 110 | Serial.println (")"); 111 | 112 | 113 | 114 | 115 | int rc = ipstack.connect(aws_endpoint, port); 116 | if (rc != 1) 117 | { 118 | Serial.println("error connection to the websocket server"); 119 | return false; 120 | } else { 121 | Serial.println("websocket layer connected"); 122 | } 123 | 124 | 125 | Serial.println("MQTT connecting"); 126 | MQTTPacket_connectData data = MQTTPacket_connectData_initializer; 127 | data.MQTTVersion = 4; 128 | char* clientID = generateClientID (); 129 | data.clientID.cstring = clientID; 130 | rc = client.connect(data); 131 | delete[] clientID; 132 | if (rc != 0) 133 | { 134 | Serial.print("error connection to MQTT server"); 135 | Serial.println(rc); 136 | return false; 137 | } 138 | Serial.println("MQTT connected"); 139 | return true; 140 | } 141 | 142 | //subscribe to a mqtt topic 143 | void subscribe () { 144 | //subscript to a topic 145 | int rc = client.subscribe(aws_topic, MQTT::QOS0, messageArrived); 146 | if (rc != 0) { 147 | Serial.print("rc from MQTT subscribe is "); 148 | Serial.println(rc); 149 | return; 150 | } 151 | Serial.println("MQTT subscribed"); 152 | } 153 | 154 | //send a message to a mqtt topic 155 | void sendmessage () { 156 | //send a message 157 | MQTT::Message message; 158 | char buf[100]; 159 | strcpy(buf, "{\"state\":{\"reported\":{\"on\": false}, \"desired\":{\"on\": false}}}"); 160 | message.qos = MQTT::QOS0; 161 | message.retained = false; 162 | message.dup = false; 163 | message.payload = (void*)buf; 164 | message.payloadlen = strlen(buf)+1; 165 | int rc = client.publish(aws_topic, message); 166 | } 167 | 168 | 169 | void setup() { 170 | wifi_set_sleep_type(NONE_SLEEP_T); 171 | Serial.begin (115200); 172 | delay (2000); 173 | Serial.setDebugOutput(1); 174 | 175 | //fill with ssid and wifi password 176 | WiFiMulti.addAP(wifi_ssid, wifi_password); 177 | Serial.println ("connecting to wifi"); 178 | while(WiFiMulti.run() != WL_CONNECTED) { 179 | delay(100); 180 | Serial.print ("."); 181 | } 182 | Serial.println ("\nconnected"); 183 | 184 | //fill AWS parameters 185 | awsWSclient.setAWSRegion(aws_region); 186 | awsWSclient.setAWSDomain(aws_endpoint); 187 | awsWSclient.setAWSKeyID(aws_key); 188 | awsWSclient.setAWSSecretKey(aws_secret); 189 | awsWSclient.setUseSSL(true); 190 | 191 | if (connect ()){ 192 | subscribe (); 193 | sendmessage (); 194 | } 195 | 196 | } 197 | 198 | void loop() { 199 | //keep the mqtt up and running 200 | if (awsWSclient.connected ()) { 201 | client.yield(50); 202 | } else { 203 | //handle reconnection 204 | if (connect ()){ 205 | subscribe (); 206 | } 207 | } 208 | 209 | } 210 | -------------------------------------------------------------------------------- /examples/aws-mqtt-websocket-example-pubsubclient/aws-mqtt-websocket-example-pubsubclient.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * Example working with library v1.3 3 | * 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | //AWS 12 | #include "sha256.h" 13 | #include "Utils.h" 14 | 15 | 16 | //WEBSockets 17 | #include 18 | #include 19 | 20 | //MQTT PUBSUBCLIENT LIB 21 | #include 22 | 23 | //AWS MQTT Websocket 24 | #include "Client.h" 25 | #include "AWSWebSocketClient.h" 26 | #include "CircularByteBuffer.h" 27 | 28 | extern "C" { 29 | #include "user_interface.h" 30 | } 31 | 32 | //AWS IOT config, change these: 33 | char wifi_ssid[] = "your-ssid"; 34 | char wifi_password[] = "your-password"; 35 | char aws_endpoint[] = "your-endpoint.iot.eu-west-1.amazonaws.com"; 36 | char aws_key[] = "your-iam-key"; 37 | char aws_secret[] = "your-iam-secret-key"; 38 | char aws_region[] = "eu-west-1"; 39 | const char* aws_topic = "$aws/things/your-device/shadow/update"; 40 | int port = 443; 41 | 42 | /* uncomment the following line to use an alternate root CA if the first one does not work */ 43 | // #define ALTERNATE_ROOT_CA 44 | 45 | #ifndef ALTERNATE_ROOT_CA 46 | 47 | // AWS root certificate - expires 2037 48 | const char ca[] PROGMEM = R"EOF( 49 | -----BEGIN CERTIFICATE----- 50 | MIIEkjCCA3qgAwIBAgITBn+USionzfP6wq4rAfkI7rnExjANBgkqhkiG9w0BAQsF 51 | ADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNj 52 | b3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4x 53 | OzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1 54 | dGhvcml0eSAtIEcyMB4XDTE1MDUyNTEyMDAwMFoXDTM3MTIzMTAxMDAwMFowOTEL 55 | MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv 56 | b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj 57 | ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM 58 | 9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw 59 | IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 60 | VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L 61 | 93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm 62 | jgSubJrIqg0CAwEAAaOCATEwggEtMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ 63 | BAQDAgGGMB0GA1UdDgQWBBSEGMyFNOy8DJSULghZnMeyEE4KCDAfBgNVHSMEGDAW 64 | gBScXwDfqgHXMCs4iKK4bUqc8hGRgzB4BggrBgEFBQcBAQRsMGowLgYIKwYBBQUH 65 | MAGGImh0dHA6Ly9vY3NwLnJvb3RnMi5hbWF6b250cnVzdC5jb20wOAYIKwYBBQUH 66 | MAKGLGh0dHA6Ly9jcnQucm9vdGcyLmFtYXpvbnRydXN0LmNvbS9yb290ZzIuY2Vy 67 | MD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9jcmwucm9vdGcyLmFtYXpvbnRydXN0 68 | LmNvbS9yb290ZzIuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQsF 69 | AAOCAQEAYjdCXLwQtT6LLOkMm2xF4gcAevnFWAu5CIw+7bMlPLVvUOTNNWqnkzSW 70 | MiGpSESrnO09tKpzbeR/FoCJbM8oAxiDR3mjEH4wW6w7sGDgd9QIpuEdfF7Au/ma 71 | eyKdpwAJfqxGF4PcnCZXmTA5YpaP7dreqsXMGz7KQ2hsVxa81Q4gLv7/wmpdLqBK 72 | bRRYh5TmOTFffHPLkIhqhBGWJ6bt2YFGpn6jcgAKUj6DiAdjd4lpFw85hdKrCEVN 73 | 0FE6/V1dN2RMfjCyVSRCnTawXZwXgWHxyvkQAiSr6w10kY17RSlQOYiypok1JR4U 74 | akcjMS9cmvqtmg5iUaQqqcT5NJ0hGA== 75 | -----END CERTIFICATE----- 76 | )EOF"; 77 | 78 | #else 79 | 80 | // AWS root certificate (Verisign) - expires 2036 81 | const char ca[] PROGMEM = R"EOF( 82 | -----BEGIN CERTIFICATE----- 83 | MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB 84 | yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL 85 | ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp 86 | U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW 87 | ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 88 | aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL 89 | MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW 90 | ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln 91 | biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp 92 | U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y 93 | aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 94 | nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex 95 | t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz 96 | SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG 97 | BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ 98 | rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ 99 | NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E 100 | BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH 101 | BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy 102 | aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv 103 | MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE 104 | p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y 105 | 5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK 106 | WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ 107 | 4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N 108 | hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq 109 | -----END CERTIFICATE----- 110 | )EOF"; 111 | 112 | #endif 113 | 114 | 115 | 116 | //MQTT config 117 | const int maxMQTTpackageSize = 512; 118 | const int maxMQTTMessageHandlers = 1; 119 | 120 | ESP8266WiFiMulti WiFiMulti; 121 | 122 | AWSWebSocketClient awsWSclient(1000); 123 | 124 | PubSubClient client(awsWSclient); 125 | 126 | //# of connections 127 | long connection = 0; 128 | 129 | //generate random mqtt clientID 130 | char* generateClientID () { 131 | char* cID = new char[23](); 132 | for (int i=0; i<22; i+=1) 133 | cID[i]=(char)random(1, 256); 134 | return cID; 135 | } 136 | 137 | //count messages arrived 138 | int arrivedcount = 0; 139 | 140 | //callback to handle mqtt messages 141 | void callback(char* topic, byte* payload, unsigned int length) { 142 | Serial.print("Message arrived ["); 143 | Serial.print(topic); 144 | Serial.print("] "); 145 | for (int i = 0; i < length; i++) { 146 | Serial.print((char)payload[i]); 147 | } 148 | Serial.println(); 149 | } 150 | 151 | //connects to websocket layer and mqtt layer 152 | bool connect () { 153 | if (client.connected()) { 154 | client.disconnect (); 155 | } 156 | //delay is not necessary... it just help us to get a "trustful" heap space value 157 | delay (1000); 158 | Serial.print (millis ()); 159 | Serial.print (" - conn: "); 160 | Serial.print (++connection); 161 | Serial.print (" - ("); 162 | Serial.print (ESP.getFreeHeap ()); 163 | Serial.println (")"); 164 | 165 | 166 | //creating random client id 167 | char* clientID = generateClientID (); 168 | 169 | client.setServer(aws_endpoint, port); 170 | if (client.connect(clientID)) { 171 | Serial.println("connected"); 172 | return true; 173 | } else { 174 | Serial.print("failed, rc="); 175 | Serial.print(client.state()); 176 | return false; 177 | } 178 | 179 | } 180 | 181 | //subscribe to a mqtt topic 182 | void subscribe () { 183 | client.setCallback(callback); 184 | client.subscribe(aws_topic); 185 | //subscript to a topic 186 | Serial.println("MQTT subscribed"); 187 | } 188 | 189 | //send a message to a mqtt topic 190 | void sendmessage () { 191 | //send a message 192 | char buf[100]; 193 | strcpy(buf, "{\"state\":{\"reported\":{\"on\": false}, \"desired\":{\"on\": false}}}"); 194 | int rc = client.publish(aws_topic, buf); 195 | } 196 | 197 | 198 | void setClock() { 199 | configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov"); 200 | 201 | Serial.print("Waiting for NTP time sync: "); 202 | time_t now = time(nullptr); 203 | while (now < 8 * 3600 * 2) { 204 | delay(500); 205 | Serial.print("."); 206 | now = time(nullptr); 207 | } 208 | Serial.println(""); 209 | struct tm timeinfo; 210 | gmtime_r(&now, &timeinfo); 211 | Serial.print("Current time: "); 212 | Serial.print(asctime(&timeinfo)); 213 | } 214 | 215 | void setup() { 216 | wifi_set_sleep_type(NONE_SLEEP_T); 217 | Serial.begin (115200); 218 | delay (2000); 219 | Serial.setDebugOutput(1); 220 | 221 | //fill with ssid and wifi password 222 | WiFiMulti.addAP(wifi_ssid, wifi_password); 223 | Serial.println ("connecting to wifi"); 224 | while(WiFiMulti.run() != WL_CONNECTED) { 225 | delay(100); 226 | Serial.print ("."); 227 | } 228 | Serial.println ("\nconnected"); 229 | 230 | setClock(); // Required for X.509 certificate validation 231 | 232 | //fill AWS parameters 233 | awsWSclient.setAWSRegion(aws_region); 234 | awsWSclient.setAWSDomain(aws_endpoint); 235 | awsWSclient.setAWSKeyID(aws_key); 236 | awsWSclient.setAWSSecretKey(aws_secret); 237 | awsWSclient.setUseSSL(true); 238 | awsWSclient.setCA(ca); 239 | //as we had to configurate ntp time to validate the certificate, we can use it to validate aws connection as well 240 | awsWSclient.setUseAmazonTimestamp(false); 241 | 242 | 243 | if (connect ()){ 244 | subscribe (); 245 | sendmessage (); 246 | } 247 | 248 | } 249 | 250 | void loop() { 251 | //keep the mqtt up and running 252 | if (awsWSclient.connected ()) { 253 | client.loop (); 254 | } else { 255 | //handle reconnection 256 | if (connect ()){ 257 | subscribe (); 258 | } 259 | } 260 | 261 | } 262 | --------------------------------------------------------------------------------