diff --git a/keywords.txt b/keywords.txt index 2980a99c..1411c860 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,5 +1,5 @@ ####################################### -# Syntax Coloring Map For LoRaLib +# Syntax Coloring Map For KiteLib ####################################### ####################################### @@ -21,9 +21,8 @@ SX1278 KEYWORD1 SX1279 KEYWORD1 XBee KEYWORD1 -Bandwidth KEYWORD1 -SpreadingFactor KEYWORD1 -CodingRate KEYWORD1 +MQTTClient KEYWORD1 +HTTPClient KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -66,26 +65,31 @@ setFrequencyDeviation KEYWORD2 # ESP8266 join KEYWORD2 reset KEYWORD2 -HttpGet KEYWORD2 -HttpPost KEYWORD2 -MqttConnect KEYWORD2 -MqttDisconnect KEYWORD2 -MqttPublish KEYWORD2 -MqttSubscribe KEYWORD2 -MqttUnsubscribe KEYWORD2 -MqttPing KEYWORD2 -MqttCheck KEYWORD2 -startTcp KEYWORD2 -closeTcp KEYWORD2 -startUdp KEYWORD2 -closeUdp KEYWORD2 -send KEYWORD2 -receive KEYWORD2 # XBee setDestinationAddress KEYWORD2 setPanId KEYWORD2 +# HTTP +get KEYWORD2 +post KEYWORD2 + +# MQTT +connect KEYWORD2 +disconnect KEYWORD2 +publish KEYWORD2 +subscribe KEYWORD2 +unsubscribe KEYWORD2 +ping KEYWORD2 +check KEYWORD2 + +# TransportLayer +openTransportConnection KEYWORD2 +closeTransportConnection KEYWORD2 +send KEYWORD2 +receive KEYWORD2 +getNumBytes KEYWORD2 + ####################################### # Constants (LITERAL1) ####################################### diff --git a/src/KiteLib.h b/src/KiteLib.h index 63fe30d7..b426bf26 100644 --- a/src/KiteLib.h +++ b/src/KiteLib.h @@ -15,6 +15,10 @@ #include "modules/SX1279.h" #include "modules/XBee.h" +#include "protocols/TransportLayer.h" +#include "protocols/HTTP.h" +#include "protocols/MQTT.h" + #define KITE_CS_A 10 #define KITE_TX_A 9 #define KITE_RX_A 8 diff --git a/src/modules/ESP8266.cpp b/src/modules/ESP8266.cpp index ad3ab6f6..54f34b56 100644 --- a/src/modules/ESP8266.cpp +++ b/src/modules/ESP8266.cpp @@ -1,10 +1,7 @@ #include "ESP8266.h" ESP8266::ESP8266(Module* module) { - portHttp = 80; - portMqtt = 1883; _mod = module; - _MqttPacketId = 1; } uint8_t ESP8266::begin(long speed) { @@ -83,587 +80,6 @@ uint8_t ESP8266::join(const char* ssid, const char* password) { return(ERR_NONE); } -uint16_t ESP8266::HttpGet(const char* url, String& response) { - // get the host address and endpoint - char* httpPrefix = strstr(url, "http://"); - char* endpoint; - char* host; - if(httpPrefix != NULL) { - // find the host string - char* hostStart = strchr(url, '/'); - hostStart = strchr(hostStart + 1, '/'); - char* hostEnd = strchr(hostStart + 1, '/'); - host = new char[hostEnd - hostStart]; - strncpy(host, hostStart + 1, hostEnd - hostStart - 1); - host[hostEnd - hostStart - 1] = 0x00; - - // find the endpoint string - endpoint = new char[url + strlen(url) - hostEnd + 1]; - strcpy(endpoint, hostEnd); - } else { - // find the host string - char* hostEnd = strchr(url, '/'); - host = new char[hostEnd - url + 1]; - strncpy(host, url, hostEnd - url); - host[hostEnd - url] = 0x00; - - // find the endpoint string - endpoint = new char[url + strlen(url) - hostEnd + 1]; - strcpy(endpoint, hostEnd); - } - - // build the GET request - char* request = new char[strlen(endpoint) + strlen(host) + 25]; - strcpy(request, "GET "); - strcat(request, endpoint); - strcat(request, " HTTP/1.1\r\nHost: "); - strcat(request, host); - strcat(request, "\r\n\r\n"); - - delete[] endpoint; - - // create TCP connection - uint8_t state = openTransportConnection(host, "TCP", portHttp); - delete[] host; - if(state != ERR_NONE) { - delete[] request; - return(state); - } - - // send the GET request - state = send(request); - delete[] request; - if(state != ERR_NONE) { - return(state); - } - - delay(1000); - - // get the response length - uint16_t numBytes = getNumBytes(); - if(numBytes == 0) { - return(ERR_RESPONSE_MALFORMED_AT); - } - - // read the response - char* raw = new char[numBytes]; - size_t rawLength = receive((uint8_t*)raw, numBytes); - if(rawLength == 0) { - delete[] raw; - return(ERR_RESPONSE_MALFORMED); - } - - // close the TCP connection - state = closeTransportConnection(); - if(state != ERR_NONE) { - delete[] raw; - return(state); - } - - // get the response body - char* responseStart = strstr(raw, "\r\n"); - if(responseStart == NULL) { - delete[] raw; - return(ERR_RESPONSE_MALFORMED); - } - char* responseStr = new char[raw + rawLength - responseStart - 1]; - strncpy(responseStr, responseStart + 2, raw + rawLength - responseStart - 1); - responseStr[raw + rawLength - responseStart - 2] = 0x00; - response = String(responseStr); - delete[] responseStr; - - // return the HTTP status code - char* statusStart = strchr(raw, ' '); - delete[] raw; - if(statusStart == NULL) { - return(ERR_RESPONSE_MALFORMED); - } - char statusStr[4]; - strncpy(statusStr, statusStart + 1, 3); - statusStr[3] = 0x00; - return(atoi(statusStr)); -} - -uint16_t ESP8266::HttpPost(const char* url, const char* content, String& response, const char* contentType) { - // get the host address and endpoint - char* httpPrefix = strstr(url, "http://"); - char* endpoint; - char* host; - if(httpPrefix != NULL) { - // find the host string - char* hostStart = strchr(url, '/'); - hostStart = strchr(hostStart + 1, '/'); - char* hostEnd = strchr(hostStart + 1, '/'); - host = new char[hostEnd - hostStart]; - strncpy(host, hostStart + 1, hostEnd - hostStart - 1); - host[hostEnd - hostStart - 1] = 0x00; - - // find the endpoint string - endpoint = new char[url + strlen(url) - hostEnd + 1]; - strcpy(endpoint, hostEnd); - } else { - // find the host string - char* hostEnd = strchr(url, '/'); - host = new char[hostEnd - url + 1]; - strncpy(host, url, hostEnd - url); - host[hostEnd - url] = 0x00; - - // find the endpoint string - endpoint = new char[url + strlen(url) - hostEnd + 1]; - strcpy(endpoint, hostEnd); - } - - // build the POST request - char contentLengthStr[8]; - itoa(strlen(content), contentLengthStr, 10); - char* request = new char[strlen(endpoint) + strlen(host) + strlen(contentType) + strlen(contentLengthStr) + strlen(content) + 64]; - strcpy(request, "POST "); - strcat(request, endpoint); - strcat(request, " HTTP/1.1\r\nHost: "); - strcat(request, host); - strcat(request, "\r\nContent-Type: "); - strcat(request, contentType); - strcat(request, "\r\nContent-length: "); - strcat(request, contentLengthStr); - strcat(request, "\r\n\r\n"); - strcat(request, content); - strcat(request, "\r\n\r\n"); - - delete[] endpoint; - - // create TCP connection - uint8_t state = openTransportConnection(host, "TCP", portHttp); - delete[] host; - if(state != ERR_NONE) { - return(state); - } - - // send the POST request - state = send(request); - delete[] request; - if(state != ERR_NONE) { - return(state); - } - - delay(2000); - - // get the response length - uint16_t numBytes = getNumBytes(); - if(numBytes == 0) { - return(ERR_RESPONSE_MALFORMED_AT); - } - - // read the response - char* raw = new char[numBytes]; - size_t rawLength = receive((uint8_t*)raw, numBytes); - if(rawLength == 0) { - delete[] raw; - return(ERR_RESPONSE_MALFORMED); - } - - // close the TCP connection - state = closeTransportConnection(); - if(state != ERR_NONE) { - delete[] raw; - return(state); - } - - // get the response body - char* responseStart = strstr(raw, "\r\n"); - if(responseStart == NULL) { - delete[] raw; - return(ERR_RESPONSE_MALFORMED); - } - char* responseStr = new char[raw + rawLength - responseStart - 1]; - strncpy(responseStr, responseStart + 2, raw + rawLength - responseStart - 1); - responseStr[raw + rawLength - responseStart - 2] = 0x00; - response = String(responseStr); - delete[] responseStr; - - // return the HTTP status code - char* statusStart = strchr(raw, ' '); - delete[] raw; - if(statusStart == NULL) { - return(ERR_RESPONSE_MALFORMED); - } - char statusStr[4]; - strncpy(statusStr, statusStart + 1, 3); - statusStr[3] = 0x00; - return(atoi(statusStr)); -} - -uint8_t ESP8266::MqttConnect(const char* host, const char* clientId, const char* userName, const char* password, uint16_t keepAlive, bool cleanSession, const char* willTopic, const char* willMessage) { - // encode packet length - size_t clientIdLen = strlen(clientId); - size_t userNameLen = strlen(userName); - size_t passwordLen = strlen(password); - size_t willTopicLen = strlen(willTopic); - size_t willMessageLen = strlen(willMessage); - uint32_t remainingLength = 10 + (2 + clientIdLen); - if(userNameLen > 0) { - remainingLength += (2 + userNameLen); - } - if(passwordLen > 0) { - remainingLength += (2 + passwordLen); - } - if((willTopicLen > 0) && (willMessageLen > 0)) { - remainingLength += (2 + willTopicLen) + (2 + willMessageLen); - } - uint8_t encoded[] = {0, 0, 0, 0}; - size_t encodedBytes = MqttEncodeLength(remainingLength, encoded); - - // build the CONNECT packet - uint8_t* packet = new uint8_t[1 + encodedBytes + remainingLength]; - - // fixed header - packet[0] = (MQTT_CONNECT << 4) | 0b0000; - memcpy(packet + 1, encoded, encodedBytes); - - // variable header - // protocol name - size_t pos = encodedBytes + 1; - packet[pos++] = 0x00; - packet[pos++] = 0x04; - memcpy(packet + pos, "MQTT", 4); - pos += 4; - - // protocol level - packet[pos++] = 0x04; - - // flags - packet[pos++] = 0x00; - if(cleanSession) { - packet[encodedBytes + 8] |= MQTT_CONNECT_CLEAN_SESSION; - } - - // keep alive interval in seconds - packet[pos++] = (keepAlive & 0xFF00) >> 8; - packet[pos++] = keepAlive & 0x00FF; - - // payload - // clientId - packet[pos++] = (clientIdLen & 0xFF00) >> 8; - packet[pos++] = clientIdLen & 0x00FF; - memcpy(packet + pos, clientId, clientIdLen); - pos += clientIdLen; - - // will topic and message - if((willTopicLen > 0) && (willMessageLen > 0)) { - packet[encodedBytes + 8] |= MQTT_CONNECT_WILL_FLAG; - - packet[pos++] = (willTopicLen & 0xFF00) >> 8; - packet[pos++] = willTopicLen & 0x00FF; - memcpy(packet + pos, willTopic, willTopicLen); - pos += willTopicLen; - - packet[pos++] = (willMessageLen & 0xFF00) >> 8; - packet[pos++] = willMessageLen & 0x00FF; - memcpy(packet + pos, willMessage, willMessageLen); - pos += willMessageLen; - } - - // user name - if(userNameLen > 0) { - packet[encodedBytes + 8] |= MQTT_CONNECT_USER_NAME_FLAG; - packet[pos++] = (userNameLen & 0xFF00) >> 8; - packet[pos++] = userNameLen & 0x00FF; - memcpy(packet + pos, userName, userNameLen); - pos += userNameLen; - } - - // password - if(passwordLen > 0) { - packet[encodedBytes + 8] |= MQTT_CONNECT_PASSWORD_FLAG; - packet[pos++] = (passwordLen & 0xFF00) >> 8;; - packet[pos++] = passwordLen & 0x00FF; - memcpy(packet + pos, password, passwordLen); - pos += passwordLen; - } - - // create TCP connection - uint8_t state = openTransportConnection(host, "TCP", portMqtt, keepAlive); - if(state != ERR_NONE) { - delete[] packet; - return(state); - } - - // send MQTT packet - state = send(packet, 1 + encodedBytes + remainingLength); - delete[] packet; - if(state != ERR_NONE) { - return(state); - } - - // get the response length (MQTT CONNACK response has to be 4 bytes long) - uint16_t numBytes = getNumBytes(); - if(numBytes != 4) { - return(ERR_RESPONSE_MALFORMED_AT); - } - - // read the response - uint8_t* response = new uint8_t[numBytes]; - receive(response, numBytes); - if((response[0] == MQTT_CONNACK << 4) && (response[1] == 2)) { - uint8_t returnCode = response[3]; - delete[] response; - return(returnCode); - } - - delete[] response; - return(ERR_RESPONSE_MALFORMED); -} - -uint8_t ESP8266::MqttDisconnect() { - // build the DISCONNECT packet - uint8_t packet[2]; - - // fixed header - packet[0] = (MQTT_DISCONNECT << 4) | 0b0000; - packet[1] = 0x00; - - // send MQTT packet - uint8_t state = send(packet, 2); - if(state != ERR_NONE) { - return(state); - } - - // close TCP connection - return(closeTransportConnection()); -} - -uint8_t ESP8266::MqttPublish(const char* topic, const char* message) { - // encode packet length - size_t topicLen = strlen(topic); - size_t messageLen = strlen(message); - uint32_t remainingLength = (2 + topicLen) + messageLen; - uint8_t encoded[] = {0, 0, 0, 0}; - size_t encodedBytes = MqttEncodeLength(remainingLength, encoded); - - // build the PUBLISH packet - uint8_t* packet = new uint8_t[1 + encodedBytes + remainingLength]; - - // fixed header - packet[0] = (MQTT_PUBLISH << 4) | 0b0000; - memcpy(packet + 1, encoded, encodedBytes); - - // variable header - // topic name - size_t pos = encodedBytes + 1; - packet[pos++] = (topicLen & 0xFF00) >> 8; - packet[pos++] = topicLen & 0x00FF; - memcpy(packet + pos, topic, topicLen); - pos += topicLen; - - // packet ID - - // payload - // message - memcpy(packet + pos, message, messageLen); - pos += messageLen; - - // send MQTT packet - uint8_t state = send(packet, 1 + encodedBytes + remainingLength); - delete[] packet; - return(state); - - //TODO: implement QoS > 0 and PUBACK response checking -} - -uint8_t ESP8266::MqttSubscribe(const char* topicFilter) { - // encode packet length - size_t topicFilterLen = strlen(topicFilter); - uint32_t remainingLength = 2 + (2 + topicFilterLen + 1); - uint8_t encoded[] = {0, 0, 0, 0}; - size_t encodedBytes = MqttEncodeLength(remainingLength, encoded); - - // build the SUBSCRIBE packet - uint8_t* packet = new uint8_t[1 + encodedBytes + remainingLength]; - - // fixed header - packet[0] = (MQTT_SUBSCRIBE << 4) | 0b0010; - memcpy(packet + 1, encoded, encodedBytes); - - // variable header - // packet ID - size_t pos = encodedBytes + 1; - uint16_t packetId = _MqttPacketId++; - packet[pos++] = (packetId & 0xFF00) >> 8; - packet[pos++] = packetId & 0x00FF; - - // payload - // topic filter - packet[pos++] = (topicFilterLen & 0xFF00) >> 8;; - packet[pos++] = topicFilterLen & 0x00FF; - memcpy(packet + pos, topicFilter, topicFilterLen); - pos += topicFilterLen; - packet[pos++] = 0x00; // QoS 0 - - // send MQTT packet - uint8_t state = send(packet, 1 + encodedBytes + remainingLength); - delete[] packet; - if(state != ERR_NONE) { - return(state); - } - - // get the response length (MQTT SUBACK response has to be 5 bytes long for single subscription) - uint16_t numBytes = getNumBytes(); - if(numBytes != 5) { - return(ERR_RESPONSE_MALFORMED_AT); - } - - // read the response - uint8_t* response = new uint8_t[numBytes]; - receive(response, numBytes); - if((response[0] == MQTT_SUBACK << 4) && (response[1] == 3)) { - // check packet ID - uint16_t receivedId = response[3] | response[2] << 8; - uint8_t returnCode = response[4]; - delete[] response; - if(receivedId != packetId) { - return(ERR_MQTT_UNEXPECTED_PACKET_ID); - } - return(returnCode); - } - - delete[] response; - return(ERR_RESPONSE_MALFORMED); -} - -uint8_t ESP8266::MqttUnsubscribe(const char* topicFilter) { - // encode packet length - size_t topicFilterLen = strlen(topicFilter); - uint32_t remainingLength = 2 + (2 + topicFilterLen); - uint8_t encoded[] = {0, 0, 0, 0}; - size_t encodedBytes = MqttEncodeLength(remainingLength, encoded); - - // build the UNSUBSCRIBE packet - uint8_t* packet = new uint8_t[1 + encodedBytes + remainingLength]; - - // fixed header - packet[0] = (MQTT_UNSUBSCRIBE << 4) | 0b0010; - memcpy(packet + 1, encoded, encodedBytes); - - // variable header - // packet ID - size_t pos = encodedBytes + 1; - uint16_t packetId = _MqttPacketId++; - packet[pos++] = (packetId & 0xFF00) >> 8; - packet[pos++] = packetId & 0x00FF; - - // payload - // topic filter - packet[pos++] = (topicFilterLen & 0xFF00) >> 8;; - packet[pos++] = topicFilterLen & 0x00FF; - memcpy(packet + pos, topicFilter, topicFilterLen); - pos += topicFilterLen; - - // send MQTT packet - uint8_t state = send(packet, 1 + encodedBytes + remainingLength); - delete[] packet; - if(state != ERR_NONE) { - return(state); - } - - // get the response length (MQTT UNSUBACK response has to be 4 bytes long) - uint16_t numBytes = getNumBytes(); - if(numBytes != 4) { - return(ERR_RESPONSE_MALFORMED_AT); - } - - // read the response - uint8_t* response = new uint8_t[numBytes]; - receive(response, numBytes); - if((response[0] == MQTT_UNSUBACK << 4) && (response[1] == 2)) { - // check packet ID - uint16_t receivedId = response[3] | response[2] << 8; - delete[] response; - if(receivedId != packetId) { - return(ERR_MQTT_UNEXPECTED_PACKET_ID); - } - return(ERR_NONE); - } - - delete[] response; - return(ERR_RESPONSE_MALFORMED); -} - -uint8_t ESP8266::MqttPing() { - // build the PINGREQ packet - uint8_t packet[2]; - - // fixed header - packet[0] = (MQTT_PINGREQ << 4) | 0b0000; - packet[1] = 0x00; - - // send MQTT packet - uint8_t state = send(packet, 2); - if(state != ERR_NONE) { - return(state); - } - - // get the response length (MQTT PINGRESP response has to be 2 bytes long) - uint16_t numBytes = getNumBytes(); - if(numBytes != 2) { - return(ERR_RESPONSE_MALFORMED_AT); - } - - // read the response - uint8_t* response = new uint8_t[numBytes]; - receive(response, numBytes); - if((response[0] == MQTT_PINGRESP << 4) && (response[1] == 0)) { - delete[] response; - return(ERR_NONE); - } - - delete[] response; - return(ERR_RESPONSE_MALFORMED); -} - -uint8_t ESP8266::MqttCheck(void (*func)(const char*, const char*)) { - // ping the server - uint8_t state = MqttPing(); - if(state != ERR_NONE) { - return(state); - } - - // check new data - uint16_t numBytes = getNumBytes(); - if(numBytes == 0) { - return(ERR_MQTT_NO_NEW_PACKET_AVAILABLE); - } - - // read the PUBLISH packet from server - uint8_t* dataIn = new uint8_t[numBytes]; - receive(dataIn, numBytes); - if(dataIn[0] == MQTT_PUBLISH << 4) { - // TODO: properly decode remaining length - uint8_t remainingLength = dataIn[1]; - - // get the topic - size_t topicLength = dataIn[3] | dataIn[2] << 8; - char* topic = new char[topicLength + 1]; - memcpy(topic, dataIn + 4, topicLength); - topic[topicLength] = 0x00; - - // get the message - size_t messageLength = remainingLength - topicLength - 2; - char* message = new char[messageLength + 1]; - memcpy(message, dataIn + 4 + topicLength, messageLength); - message[messageLength] = 0x00; - - // execute the callback function provided by user - func(topic, message); - - delete[] topic; - delete[] message; - delete[] dataIn; - return(ERR_NONE); - } - delete[] dataIn; - - return(ERR_MQTT_NO_NEW_PACKET_AVAILABLE); -} - uint8_t ESP8266::send(const char* data) { // build AT command char lenStr[8]; @@ -768,34 +184,6 @@ uint8_t ESP8266::closeTransportConnection() { return(ERR_NONE); } -size_t ESP8266::MqttEncodeLength(uint32_t len, uint8_t* encoded) { - size_t i = 0; - do { - encoded[i] = len % 128; - len /= 128; - if(len > 0) { - encoded[i] |= 128; - } - i++; - } while(len > 0); - return(i); -} - -uint32_t ESP8266::MqttDecodeLength(uint8_t* encoded) { - uint32_t mult = 1; - uint32_t len = 0; - uint8_t i = 0; - do { - len += (encoded[i] & 127) * mult; - mult *= 128; - if(mult > 2097152) { - // malformed remaining length - return(0); - } - } while((encoded[i] & 128) != 0); - return len; -} - uint16_t ESP8266::getNumBytes(uint32_t timeout, size_t minBytes) { // wait for available data uint32_t start = millis(); diff --git a/src/modules/ESP8266.h b/src/modules/ESP8266.h index 51c1a80c..473406f6 100644 --- a/src/modules/ESP8266.h +++ b/src/modules/ESP8266.h @@ -3,70 +3,27 @@ #include "Module.h" -// MQTT packet types -#define MQTT_CONNECT 0x01 -#define MQTT_CONNACK 0x02 -#define MQTT_PUBLISH 0x03 -#define MQTT_PUBACK 0x04 -#define MQTT_PUBREC 0x05 -#define MQTT_PUBREL 0x06 -#define MQTT_PUBCOMP 0x07 -#define MQTT_SUBSCRIBE 0x08 -#define MQTT_SUBACK 0x09 -#define MQTT_UNSUBSCRIBE 0x0A -#define MQTT_UNSUBACK 0x0B -#define MQTT_PINGREQ 0x0C -#define MQTT_PINGRESP 0x0D -#define MQTT_DISCONNECT 0x0E +#include "../protocols/TransportLayer.h" -// MQTT CONNECT flags -#define MQTT_CONNECT_USER_NAME_FLAG 0b10000000 -#define MQTT_CONNECT_PASSWORD_FLAG 0b01000000 -#define MQTT_CONNECT_WILL_RETAIN 0b00100000 -#define MQTT_CONNECT_WILL_FLAG 0b00000100 -#define MQTT_CONNECT_CLEAN_SESSION 0b00000010 - -class ESP8266 { +class ESP8266: public TransportLayer { public: ESP8266(Module* module); - // Port numbers - uint16_t portHttp, portMqtt; - // Basic methods uint8_t begin(long speed); uint8_t reset(); uint8_t join(const char* ssid, const char* password); - // HTTP methods - uint16_t HttpGet(const char* url, String& response); - uint16_t HttpPost(const char* url, const char* content, String& response, const char* contentType = "text/plain"); - - // MQTT methods - uint8_t MqttConnect(const char* host, const char* clientId, const char* userName = "", const char* password = "", uint16_t keepAlive = 60, bool cleanSession = true, const char* willTopic = "", const char* willMessage = ""); - uint8_t MqttDisconnect(); - uint8_t MqttPublish(const char* topic, const char* message); - uint8_t MqttSubscribe(const char* topicFilter); - uint8_t MqttUnsubscribe(const char* topicFilter); - uint8_t MqttPing(); - uint8_t MqttCheck(void (*func)(const char*, const char*)); - // Transport layer methods uint8_t openTransportConnection(const char* host, const char* protocol, uint16_t port, uint16_t tcpKeepAlive = 0); uint8_t closeTransportConnection(); uint8_t send(const char* data); uint8_t send(uint8_t* data, uint32_t len); size_t receive(uint8_t* data, size_t len, uint32_t timeout = 10000); + uint16_t getNumBytes(uint32_t timeout = 10000, size_t minBytes = 10); private: Module* _mod; - - uint16_t _MqttPacketId; - - size_t MqttEncodeLength(uint32_t len, uint8_t* encoded); - uint32_t MqttDecodeLength(uint8_t* encoded); - - uint16_t getNumBytes(uint32_t timeout = 10000, size_t minBytes = 10); }; #endif diff --git a/src/protocols/HTTP.cpp b/src/protocols/HTTP.cpp new file mode 100644 index 00000000..b8ac9f05 --- /dev/null +++ b/src/protocols/HTTP.cpp @@ -0,0 +1,213 @@ +#include "HTTP.h" + +HTTPClient::HTTPClient(TransportLayer* tl, uint16_t port) { + _tl = tl; + _port = port; +} + +uint16_t HTTPClient::get(const char* url, String& response) { + // get the host address and endpoint + char* httpPrefix = strstr(url, "http://"); + char* endpoint; + char* host; + if(httpPrefix != NULL) { + // find the host string + char* hostStart = strchr(url, '/'); + hostStart = strchr(hostStart + 1, '/'); + char* hostEnd = strchr(hostStart + 1, '/'); + host = new char[hostEnd - hostStart]; + strncpy(host, hostStart + 1, hostEnd - hostStart - 1); + host[hostEnd - hostStart - 1] = 0x00; + + // find the endpoint string + endpoint = new char[url + strlen(url) - hostEnd + 1]; + strcpy(endpoint, hostEnd); + } else { + // find the host string + char* hostEnd = strchr(url, '/'); + host = new char[hostEnd - url + 1]; + strncpy(host, url, hostEnd - url); + host[hostEnd - url] = 0x00; + + // find the endpoint string + endpoint = new char[url + strlen(url) - hostEnd + 1]; + strcpy(endpoint, hostEnd); + } + + // build the GET request + char* request = new char[strlen(endpoint) + strlen(host) + 25]; + strcpy(request, "GET "); + strcat(request, endpoint); + strcat(request, " HTTP/1.1\r\nHost: "); + strcat(request, host); + strcat(request, "\r\n\r\n"); + + delete[] endpoint; + + // create TCP connection + uint8_t state = _tl->openTransportConnection(host, "TCP", _port); + delete[] host; + if(state != ERR_NONE) { + delete[] request; + return(state); + } + + // send the GET request + state = _tl->send(request); + delete[] request; + if(state != ERR_NONE) { + return(state); + } + + //delay(1000); + + // get the response length + uint16_t numBytes = _tl->getNumBytes(); + if(numBytes == 0) { + return(ERR_RESPONSE_MALFORMED_AT); + } + + // read the response + char* raw = new char[numBytes]; + size_t rawLength = _tl->receive((uint8_t*)raw, numBytes); + if(rawLength == 0) { + delete[] raw; + return(ERR_RESPONSE_MALFORMED); + } + + // close the tl connection + state = _tl->closeTransportConnection(); + if(state != ERR_NONE) { + delete[] raw; + return(state); + } + + // get the response body + char* responseStart = strstr(raw, "\r\n"); + if(responseStart == NULL) { + delete[] raw; + return(ERR_RESPONSE_MALFORMED); + } + char* responseStr = new char[raw + rawLength - responseStart - 1]; + strncpy(responseStr, responseStart + 2, raw + rawLength - responseStart - 1); + responseStr[raw + rawLength - responseStart - 2] = 0x00; + response = String(responseStr); + delete[] responseStr; + + // return the HTTP status code + char* statusStart = strchr(raw, ' '); + delete[] raw; + if(statusStart == NULL) { + return(ERR_RESPONSE_MALFORMED); + } + char statusStr[4]; + strncpy(statusStr, statusStart + 1, 3); + statusStr[3] = 0x00; + return(atoi(statusStr)); +} + +uint16_t HTTPClient::post(const char* url, const char* content, String& response, const char* contentType) { + // get the host address and endpoint + char* httpPrefix = strstr(url, "http://"); + char* endpoint; + char* host; + if(httpPrefix != NULL) { + // find the host string + char* hostStart = strchr(url, '/'); + hostStart = strchr(hostStart + 1, '/'); + char* hostEnd = strchr(hostStart + 1, '/'); + host = new char[hostEnd - hostStart]; + strncpy(host, hostStart + 1, hostEnd - hostStart - 1); + host[hostEnd - hostStart - 1] = 0x00; + + // find the endpoint string + endpoint = new char[url + strlen(url) - hostEnd + 1]; + strcpy(endpoint, hostEnd); + } else { + // find the host string + char* hostEnd = strchr(url, '/'); + host = new char[hostEnd - url + 1]; + strncpy(host, url, hostEnd - url); + host[hostEnd - url] = 0x00; + + // find the endpoint string + endpoint = new char[url + strlen(url) - hostEnd + 1]; + strcpy(endpoint, hostEnd); + } + + // build the POST request + char contentLengthStr[8]; + itoa(strlen(content), contentLengthStr, 10); + char* request = new char[strlen(endpoint) + strlen(host) + strlen(contentType) + strlen(contentLengthStr) + strlen(content) + 64]; + strcpy(request, "POST "); + strcat(request, endpoint); + strcat(request, " HTTP/1.1\r\nHost: "); + strcat(request, host); + strcat(request, "\r\nContent-Type: "); + strcat(request, contentType); + strcat(request, "\r\nContent-length: "); + strcat(request, contentLengthStr); + strcat(request, "\r\n\r\n"); + strcat(request, content); + strcat(request, "\r\n\r\n"); + + delete[] endpoint; + + // create TCP connection + uint8_t state = _tl->openTransportConnection(host, "TCP", _port); + delete[] host; + if(state != ERR_NONE) { + return(state); + } + + // send the POST request + state = _tl->send(request); + delete[] request; + if(state != ERR_NONE) { + return(state); + } + + // get the response length + uint16_t numBytes = _tl->getNumBytes(); + if(numBytes == 0) { + return(ERR_RESPONSE_MALFORMED_AT); + } + + // read the response + char* raw = new char[numBytes]; + size_t rawLength = _tl->receive((uint8_t*)raw, numBytes); + if(rawLength == 0) { + delete[] raw; + return(ERR_RESPONSE_MALFORMED); + } + + // close the tl connection + state = _tl->closeTransportConnection(); + if(state != ERR_NONE) { + delete[] raw; + return(state); + } + + // get the response body + char* responseStart = strstr(raw, "\r\n"); + if(responseStart == NULL) { + delete[] raw; + return(ERR_RESPONSE_MALFORMED); + } + char* responseStr = new char[raw + rawLength - responseStart - 1]; + strncpy(responseStr, responseStart + 2, raw + rawLength - responseStart - 1); + responseStr[raw + rawLength - responseStart - 2] = 0x00; + response = String(responseStr); + delete[] responseStr; + + // return the HTTP status code + char* statusStart = strchr(raw, ' '); + delete[] raw; + if(statusStart == NULL) { + return(ERR_RESPONSE_MALFORMED); + } + char statusStr[4]; + strncpy(statusStr, statusStart + 1, 3); + statusStr[3] = 0x00; + return(atoi(statusStr)); +} diff --git a/src/protocols/HTTP.h b/src/protocols/HTTP.h new file mode 100644 index 00000000..18e4d277 --- /dev/null +++ b/src/protocols/HTTP.h @@ -0,0 +1,19 @@ +#ifndef _KITELIB_HTTP_H +#define _KITELIB_HTTP_H + +#include "TypeDef.h" +#include "TransportLayer.h" + +class HTTPClient { + public: + HTTPClient(TransportLayer* tl, uint16_t port = 80); + + uint16_t get(const char* url, String& response); + uint16_t post(const char* url, const char* content, String& response, const char* contentType = "text/plain"); + + private: + TransportLayer* _tl; + uint16_t _port; +}; + +#endif diff --git a/src/protocols/MQTT.cpp b/src/protocols/MQTT.cpp new file mode 100644 index 00000000..9ea6e889 --- /dev/null +++ b/src/protocols/MQTT.cpp @@ -0,0 +1,407 @@ +#include "MQTT.h" + +MQTTClient::MQTTClient(TransportLayer* tl, uint16_t port) { + _tl = tl; + _port = port; + _packetId = 1; +} + +uint8_t MQTTClient::connect(const char* host, const char* clientId, const char* userName, const char* password, uint16_t keepAlive, bool cleanSession, const char* willTopic, const char* willMessage) { + // encode packet length + size_t clientIdLen = strlen(clientId); + size_t userNameLen = strlen(userName); + size_t passwordLen = strlen(password); + size_t willTopicLen = strlen(willTopic); + size_t willMessageLen = strlen(willMessage); + uint32_t remainingLength = 10 + (2 + clientIdLen); + if(userNameLen > 0) { + remainingLength += (2 + userNameLen); + } + if(passwordLen > 0) { + remainingLength += (2 + passwordLen); + } + if((willTopicLen > 0) && (willMessageLen > 0)) { + remainingLength += (2 + willTopicLen) + (2 + willMessageLen); + } + uint8_t encoded[] = {0, 0, 0, 0}; + size_t encodedBytes = encodeLength(remainingLength, encoded); + + // build the CONNECT packet + uint8_t* packet = new uint8_t[1 + encodedBytes + remainingLength]; + + // fixed header + packet[0] = (MQTT_CONNECT << 4) | 0b0000; + memcpy(packet + 1, encoded, encodedBytes); + + // variable header + // protocol name + size_t pos = encodedBytes + 1; + packet[pos++] = 0x00; + packet[pos++] = 0x04; + memcpy(packet + pos, "MQTT", 4); + pos += 4; + + // protocol level + packet[pos++] = 0x04; + + // flags + packet[pos++] = 0x00; + if(cleanSession) { + packet[encodedBytes + 8] |= MQTT_CONNECT_CLEAN_SESSION; + } + + // keep alive interval in seconds + packet[pos++] = (keepAlive & 0xFF00) >> 8; + packet[pos++] = keepAlive & 0x00FF; + + // payload + // clientId + packet[pos++] = (clientIdLen & 0xFF00) >> 8; + packet[pos++] = clientIdLen & 0x00FF; + memcpy(packet + pos, clientId, clientIdLen); + pos += clientIdLen; + + // will topic and message + if((willTopicLen > 0) && (willMessageLen > 0)) { + packet[encodedBytes + 8] |= MQTT_CONNECT_WILL_FLAG; + + packet[pos++] = (willTopicLen & 0xFF00) >> 8; + packet[pos++] = willTopicLen & 0x00FF; + memcpy(packet + pos, willTopic, willTopicLen); + pos += willTopicLen; + + packet[pos++] = (willMessageLen & 0xFF00) >> 8; + packet[pos++] = willMessageLen & 0x00FF; + memcpy(packet + pos, willMessage, willMessageLen); + pos += willMessageLen; + } + + // user name + if(userNameLen > 0) { + packet[encodedBytes + 8] |= MQTT_CONNECT_USER_NAME_FLAG; + packet[pos++] = (userNameLen & 0xFF00) >> 8; + packet[pos++] = userNameLen & 0x00FF; + memcpy(packet + pos, userName, userNameLen); + pos += userNameLen; + } + + // password + if(passwordLen > 0) { + packet[encodedBytes + 8] |= MQTT_CONNECT_PASSWORD_FLAG; + packet[pos++] = (passwordLen & 0xFF00) >> 8;; + packet[pos++] = passwordLen & 0x00FF; + memcpy(packet + pos, password, passwordLen); + pos += passwordLen; + } + + // create TCP connection + uint8_t state = _tl->openTransportConnection(host, "TCP", _port, keepAlive); + if(state != ERR_NONE) { + delete[] packet; + return(state); + } + + // send MQTT packet + state = _tl->send(packet, 1 + encodedBytes + remainingLength); + delete[] packet; + if(state != ERR_NONE) { + return(state); + } + + // get the response length (MQTT CONNACK response has to be 4 bytes long) + uint16_t numBytes = _tl->getNumBytes(); + if(numBytes != 4) { + return(ERR_RESPONSE_MALFORMED_AT); + } + + // read the response + uint8_t* response = new uint8_t[numBytes]; + _tl->receive(response, numBytes); + if((response[0] == MQTT_CONNACK << 4) && (response[1] == 2)) { + uint8_t returnCode = response[3]; + delete[] response; + return(returnCode); + } + + delete[] response; + return(ERR_RESPONSE_MALFORMED); +} + +uint8_t MQTTClient::disconnect() { + // build the DISCONNECT packet + uint8_t packet[2]; + + // fixed header + packet[0] = (MQTT_DISCONNECT << 4) | 0b0000; + packet[1] = 0x00; + + // send MQTT packet + uint8_t state = _tl->send(packet, 2); + if(state != ERR_NONE) { + return(state); + } + + // close tl connection + return(_tl->closeTransportConnection()); +} + +uint8_t MQTTClient::publish(const char* topic, const char* message) { + // encode packet length + size_t topicLen = strlen(topic); + size_t messageLen = strlen(message); + uint32_t remainingLength = (2 + topicLen) + messageLen; + uint8_t encoded[] = {0, 0, 0, 0}; + size_t encodedBytes = encodeLength(remainingLength, encoded); + + // build the PUBLISH packet + uint8_t* packet = new uint8_t[1 + encodedBytes + remainingLength]; + + // fixed header + packet[0] = (MQTT_PUBLISH << 4) | 0b0000; + memcpy(packet + 1, encoded, encodedBytes); + + // variable header + // topic name + size_t pos = encodedBytes + 1; + packet[pos++] = (topicLen & 0xFF00) >> 8; + packet[pos++] = topicLen & 0x00FF; + memcpy(packet + pos, topic, topicLen); + pos += topicLen; + + // packet ID + + // payload + // message + memcpy(packet + pos, message, messageLen); + pos += messageLen; + + // send MQTT packet + uint8_t state = _tl->send(packet, 1 + encodedBytes + remainingLength); + delete[] packet; + return(state); + + //TODO: implement QoS > 0 and PUBACK response checking +} + +uint8_t MQTTClient::subscribe(const char* topicFilter) { + // encode packet length + size_t topicFilterLen = strlen(topicFilter); + uint32_t remainingLength = 2 + (2 + topicFilterLen + 1); + uint8_t encoded[] = {0, 0, 0, 0}; + size_t encodedBytes = encodeLength(remainingLength, encoded); + + // build the SUBSCRIBE packet + uint8_t* packet = new uint8_t[1 + encodedBytes + remainingLength]; + + // fixed header + packet[0] = (MQTT_SUBSCRIBE << 4) | 0b0010; + memcpy(packet + 1, encoded, encodedBytes); + + // variable header + // packet ID + size_t pos = encodedBytes + 1; + uint16_t packetId = _packetId++; + packet[pos++] = (packetId & 0xFF00) >> 8; + packet[pos++] = packetId & 0x00FF; + + // payload + // topic filter + packet[pos++] = (topicFilterLen & 0xFF00) >> 8;; + packet[pos++] = topicFilterLen & 0x00FF; + memcpy(packet + pos, topicFilter, topicFilterLen); + pos += topicFilterLen; + packet[pos++] = 0x00; // QoS 0 + + // send MQTT packet + uint8_t state = _tl->send(packet, 1 + encodedBytes + remainingLength); + delete[] packet; + if(state != ERR_NONE) { + return(state); + } + + // get the response length (MQTT SUBACK response has to be 5 bytes long for single subscription) + uint16_t numBytes = _tl->getNumBytes(); + if(numBytes != 5) { + return(ERR_RESPONSE_MALFORMED_AT); + } + + // read the response + uint8_t* response = new uint8_t[numBytes]; + _tl->receive(response, numBytes); + if((response[0] == MQTT_SUBACK << 4) && (response[1] == 3)) { + // check packet ID + uint16_t receivedId = response[3] | response[2] << 8; + uint8_t returnCode = response[4]; + delete[] response; + if(receivedId != packetId) { + return(ERR_MQTT_UNEXPECTED_PACKET_ID); + } + return(returnCode); + } + + delete[] response; + return(ERR_RESPONSE_MALFORMED); +} + +uint8_t MQTTClient::unsubscribe(const char* topicFilter) { + // encode packet length + size_t topicFilterLen = strlen(topicFilter); + uint32_t remainingLength = 2 + (2 + topicFilterLen); + uint8_t encoded[] = {0, 0, 0, 0}; + size_t encodedBytes = encodeLength(remainingLength, encoded); + + // build the UNSUBSCRIBE packet + uint8_t* packet = new uint8_t[1 + encodedBytes + remainingLength]; + + // fixed header + packet[0] = (MQTT_UNSUBSCRIBE << 4) | 0b0010; + memcpy(packet + 1, encoded, encodedBytes); + + // variable header + // packet ID + size_t pos = encodedBytes + 1; + uint16_t packetId = _packetId++; + packet[pos++] = (packetId & 0xFF00) >> 8; + packet[pos++] = packetId & 0x00FF; + + // payload + // topic filter + packet[pos++] = (topicFilterLen & 0xFF00) >> 8;; + packet[pos++] = topicFilterLen & 0x00FF; + memcpy(packet + pos, topicFilter, topicFilterLen); + pos += topicFilterLen; + + // send MQTT packet + uint8_t state = _tl->send(packet, 1 + encodedBytes + remainingLength); + delete[] packet; + if(state != ERR_NONE) { + return(state); + } + + // get the response length (MQTT UNSUBACK response has to be 4 bytes long) + uint16_t numBytes = _tl->getNumBytes(); + if(numBytes != 4) { + return(ERR_RESPONSE_MALFORMED_AT); + } + + // read the response + uint8_t* response = new uint8_t[numBytes]; + _tl->receive(response, numBytes); + if((response[0] == MQTT_UNSUBACK << 4) && (response[1] == 2)) { + // check packet ID + uint16_t receivedId = response[3] | response[2] << 8; + delete[] response; + if(receivedId != packetId) { + return(ERR_MQTT_UNEXPECTED_PACKET_ID); + } + return(ERR_NONE); + } + + delete[] response; + return(ERR_RESPONSE_MALFORMED); +} + +uint8_t MQTTClient::ping() { + // build the PINGREQ packet + uint8_t packet[2]; + + // fixed header + packet[0] = (MQTT_PINGREQ << 4) | 0b0000; + packet[1] = 0x00; + + // send MQTT packet + uint8_t state = _tl->send(packet, 2); + if(state != ERR_NONE) { + return(state); + } + + // get the response length (MQTT PINGRESP response has to be 2 bytes long) + uint16_t numBytes = _tl->getNumBytes(); + if(numBytes != 2) { + return(ERR_RESPONSE_MALFORMED_AT); + } + + // read the response + uint8_t* response = new uint8_t[numBytes]; + _tl->receive(response, numBytes); + if((response[0] == MQTT_PINGRESP << 4) && (response[1] == 0)) { + delete[] response; + return(ERR_NONE); + } + + delete[] response; + return(ERR_RESPONSE_MALFORMED); +} + +uint8_t MQTTClient::check(void (*func)(const char*, const char*)) { + // ping the server + uint8_t state = ping(); + if(state != ERR_NONE) { + return(state); + } + + // check new data + uint16_t numBytes = _tl->getNumBytes(); + if(numBytes == 0) { + return(ERR_MQTT_NO_NEW_PACKET_AVAILABLE); + } + + // read the PUBLISH packet from server + uint8_t* dataIn = new uint8_t[numBytes]; + _tl->receive(dataIn, numBytes); + if(dataIn[0] == MQTT_PUBLISH << 4) { + // TODO: properly decode remaining length + uint8_t remainingLength = dataIn[1]; + + // get the topic + size_t topicLength = dataIn[3] | dataIn[2] << 8; + char* topic = new char[topicLength + 1]; + memcpy(topic, dataIn + 4, topicLength); + topic[topicLength] = 0x00; + + // get the message + size_t messageLength = remainingLength - topicLength - 2; + char* message = new char[messageLength + 1]; + memcpy(message, dataIn + 4 + topicLength, messageLength); + message[messageLength] = 0x00; + + // execute the callback function provided by user + func(topic, message); + + delete[] topic; + delete[] message; + delete[] dataIn; + return(ERR_NONE); + } + delete[] dataIn; + + return(ERR_MQTT_NO_NEW_PACKET_AVAILABLE); +} + +size_t MQTTClient::encodeLength(uint32_t len, uint8_t* encoded) { + size_t i = 0; + do { + encoded[i] = len % 128; + len /= 128; + if(len > 0) { + encoded[i] |= 128; + } + i++; + } while(len > 0); + return(i); +} + +uint32_t MQTTClient::decodeLength(uint8_t* encoded) { + uint32_t mult = 1; + uint32_t len = 0; + uint8_t i = 0; + do { + len += (encoded[i] & 127) * mult; + mult *= 128; + if(mult > 2097152) { + // malformed remaining length + return(0); + } + } while((encoded[i] & 128) != 0); + return len; +} diff --git a/src/protocols/MQTT.h b/src/protocols/MQTT.h new file mode 100644 index 00000000..1f2e00ae --- /dev/null +++ b/src/protocols/MQTT.h @@ -0,0 +1,52 @@ +#ifndef _KITELIB_MQTT_H +#define _KITELIB_MQTT_H + +#include "TypeDef.h" +#include "TransportLayer.h" + +// MQTT packet types +#define MQTT_CONNECT 0x01 +#define MQTT_CONNACK 0x02 +#define MQTT_PUBLISH 0x03 +#define MQTT_PUBACK 0x04 +#define MQTT_PUBREC 0x05 +#define MQTT_PUBREL 0x06 +#define MQTT_PUBCOMP 0x07 +#define MQTT_SUBSCRIBE 0x08 +#define MQTT_SUBACK 0x09 +#define MQTT_UNSUBSCRIBE 0x0A +#define MQTT_UNSUBACK 0x0B +#define MQTT_PINGREQ 0x0C +#define MQTT_PINGRESP 0x0D +#define MQTT_DISCONNECT 0x0E + +// MQTT CONNECT flags +#define MQTT_CONNECT_USER_NAME_FLAG 0b10000000 +#define MQTT_CONNECT_PASSWORD_FLAG 0b01000000 +#define MQTT_CONNECT_WILL_RETAIN 0b00100000 +#define MQTT_CONNECT_WILL_FLAG 0b00000100 +#define MQTT_CONNECT_CLEAN_SESSION 0b00000010 + +class MQTTClient { + public: + MQTTClient(TransportLayer* tl, uint16_t port = 1883); + + uint8_t connect(const char* host, const char* clientId, const char* userName = "", const char* password = "", uint16_t keepAlive = 60, bool cleanSession = true, const char* willTopic = "", const char* willMessage = ""); + uint8_t disconnect(); + uint8_t publish(const char* topic, const char* message); + uint8_t subscribe(const char* topicFilter); + uint8_t unsubscribe(const char* topicFilter); + uint8_t ping(); + uint8_t check(void (*func)(const char*, const char*)); + + private: + TransportLayer* _tl; + + uint16_t _port; + uint16_t _packetId; + + size_t encodeLength(uint32_t len, uint8_t* encoded); + uint32_t decodeLength(uint8_t* encoded); +}; + +#endif diff --git a/src/protocols/TransportLayer.h b/src/protocols/TransportLayer.h new file mode 100644 index 00000000..70e5ae71 --- /dev/null +++ b/src/protocols/TransportLayer.h @@ -0,0 +1,16 @@ +#ifndef _KITELIB_TRANSPORT_LAYER_H +#define _KITELIB_TRANSPORT_LAYER_H + +#include "TypeDef.h" + +class TransportLayer { + public: + virtual uint8_t openTransportConnection(const char* host, const char* protocol, uint16_t port, uint16_t tcpKeepAlive = 0) = 0; + virtual uint8_t closeTransportConnection() = 0; + virtual uint8_t send(const char* data) = 0; + virtual uint8_t send(uint8_t* data, uint32_t len) = 0; + virtual size_t receive(uint8_t* data, size_t len, uint32_t timeout = 10000) = 0; + virtual uint16_t getNumBytes(uint32_t timeout = 10000, size_t minBytes = 10) = 0; +}; + +#endif