/*! \file ELClientRest.cpp \brief Constructor and functions for ELClientRest \author B. Runnels \author T. von Eicken \date 2016 */ // Copyright (c) 2016 by B. Runnels and T. von Eicken #include "ELClientRest.h" typedef enum { HEADER_GENERIC = 0, /**< Header is generic */ HEADER_CONTENT_TYPE, /**< Header is content type */ HEADER_USER_AGENT /**< Header is user agent */ } HEADER_TYPE; /**< Enum of header types */ /*! ELClientRest(ELClient *e) @brief Constructor for ELClientRest @param e Pointer to ELClient structure @par Example @code ELClientRest(ELClient *e); @endcode */ ELClientRest::ELClientRest(ELClient *e) { _elc = e; remote_instance = -1; } /*! restCallback(void *res) @brief Function called by esp-link when data is sent, received or an error occured. @details The function is called by esp-link when data is sent or received from the remote server. @note Internal library function @param res Pointer to ELClientResponse structure @warning The content of the response structure is overwritten when the next package arrives! */ void ELClientRest::restCallback(void *res) { if (!res) return; ELClientResponse *resp = (ELClientResponse *)res; resp->popArg(&_status, sizeof(_status)); if (_elc->_debugEn) { _elc->_debug->print("REST code "); _elc->_debug->println(_status); } _len = resp->popArgPtr(&_data); } /*! begin(const char* host, uint16_t port, boolean security) @brief Initialize communication to a REST server @details Initialize communication to a remote server, this communicates with esp-link but does not open a connection to the remote server. @param host Host to be connected. Can be a URL or an IP address in the format of xxx.xxx.xxx.xxx . @param port Port to be used to send/receive packets. Port MUST NOT be 80, 23 or 2323, as these ports are already used by EL-CLIENT on the ESP8266 @param security Flag if secure connection should be established @warning Port MUST NOT be 80, 23 or 2323, as these ports are already used by EL-CLIENT on the ESP8266. Max 4 connections are supported! @par Example @code int err = rest.begin("www.timeapi.org"); if (err != 0) { Serial.print("REST begin failed: "); Serial.println(err); while(1) ; } @endcode */ int ELClientRest::begin(const char* host, uint16_t port, boolean security) { uint8_t sec = !!security; restCb.attach(this, &ELClientRest::restCallback); _elc->Request(CMD_REST_SETUP, (uint32_t)&restCb, 3); _elc->Request(host, strlen(host)); _elc->Request(&port, 2); _elc->Request(&sec, 1); _elc->Request(); ELClientPacket *pkt = _elc->WaitReturn(); if (pkt && (int32_t)pkt->value >= 0) { remote_instance = pkt->value; return 0; } return (int)pkt->value; } /*! request(const char* path, const char* method, const char* data, int len) @brief Send request to REST server. @param path Path that extends the URL of the REST request (command or data for the REST server) @param method REST method, allowed values are "GET", "POST", "PUT" or "DELETE" @param data Pointer to data buffer @param len Size of data buffer @par Example @code no example code yet @endcode */ void ELClientRest::request(const char* path, const char* method, const char* data, int len) { _status = 0; if (remote_instance < 0) return; if (data != 0 && len > 0) _elc->Request(CMD_REST_REQUEST, remote_instance, 3); else _elc->Request(CMD_REST_REQUEST, remote_instance, 2); _elc->Request(method, strlen(method)); _elc->Request(path, strlen(path)); if (data != NULL && len > 0) { _elc->Request(data, len); } _elc->Request(); } /*! request(const char* path, const char* method, const char* data) @brief Send request to REST server. @details The data must be null-terminated. @param path Path that extends the URL of the REST request (command or data for the REST server) @param method REST method, allowed values are "GET", "POST", "PUT" or "DELETE" @param data Pointer to data buffer @par Example @code no example code yet @endcode */ void ELClientRest::request(const char* path, const char* method, const char* data) { request(path, method, data, strlen(data)); } /*! get(const char* path, const char* data) @brief Send GET request to REST server @warning The received data might not be null-terminated. @param path Path that extends the URL of the REST request (command or data for the REST server) @param data Pointer to data buffer @par Example @code // Request /utc/now from the previously set-up server rest.get("/utc/now"); @endcode */ void ELClientRest::get(const char* path, const char* data) { request(path, "GET", data); } /*! post(const char* path, const char* data) @brief Send POST request to REST server @warning The received data must be null-terminated. @param path Path that extends the URL of the REST request (command or data for the REST server) @param data Pointer to data buffer @par Example @code // Generate a fake value starting from 100 going up to 300 solarValue = solarValue + 0.5; if (solarValue == 300) { solarValue = 100; } String solarValString = String(solarValue); const char *solarValChar = solarValString.c_str(); // Reserve a buffer for sending the data char path_data[BUFLEN]; // Copy the path and API key into the buffer sprintf(path_data, "%s", "/update?api_key="); sprintf(path_data + strlen(path_data), "%s", api_key); // Copy the field number and value into the buffer // If you have more than one field to update, // repeat and change field1 to field2, field3, ... sprintf(path_data + strlen(path_data), "%s", "&field1="); sprintf(path_data + strlen(path_data), "%s", solarValChar); // Send PUT request to thingspeak.com rest.post(path_data,""); @endcode */ void ELClientRest::post(const char* path, const char* data) { request(path, "POST", data); } /*! put(const char* path, const char* data) @brief Send PUT request to REST server @warning The received data must be null-terminated. @param path Path that extends the URL of the REST request (command or data for the REST server) @param data Pointer to data buffer @par Example @code no example code yet @endcode */ void ELClientRest::put(const char* path, const char* data) { request(path, "PUT", data); } /*! del(const char* path) @brief Send DELETE request to REST server @param path Path that extends the URL of the REST request (command or data for the REST server) @par Example @code no example code yet @endcode */ void ELClientRest::del(const char* path) { request(path, "DELETE", 0); } /*! setHeader(const char* value) @brief Set generic header content @details If no generic header is set, it defaults to an empty string @param value Header content @par Example @code no example code yet @endcode */ void ELClientRest::setHeader(const char* value) { uint8_t header_index = HEADER_GENERIC; _elc->Request(CMD_REST_SETHEADER, remote_instance, 2); _elc->Request(&header_index, 1); _elc->Request(value, strlen(value)); _elc->Request(); } /*! setContentType(const char* value) @brief Set content type of header @details If no content type is set, it defaults to "x-www-form-urlencoded" @param value Content type @par Example @code no example code yet @endcode */ void ELClientRest::setContentType(const char* value) { uint8_t header_index = HEADER_CONTENT_TYPE; _elc->Request(CMD_REST_SETHEADER, remote_instance, 2); _elc->Request(&header_index, 1); _elc->Request(value, strlen(value)); _elc->Request(); } /*! setUserAgent(const char* value) @brief Set user agent of header @details If no user agent is set, it defaults to "esp-link" @param value User agent @par Example @code no example code yet @endcode */ void ELClientRest::setUserAgent(const char* value) { uint8_t header_index = HEADER_USER_AGENT; _elc->Request(CMD_REST_SETHEADER, remote_instance, 2); _elc->Request(&header_index, 1); _elc->Request(value, strlen(value)); _elc->Request(); } /*! getResponse(char* data, uint16_t maxLen) @brief Retrieve response. @details Checks if a response from the remote server was received, returns the HTTP status code or 0 if no response (may need to wait longer) @param data Pointer to buffer for received packet @param maxLen Size of buffer for received packet. If the received packet is larger than the buffer, the received packet will be truncated. @return uint16_t Size of received packet or number of sent bytes or 0 if no response @par Example @code #define BUFLEN 266 void loop() { // process any callbacks coming from esp_link esp.Process(); void loop() { // process any callbacks coming from esp_link esp.Process(); // if we're connected make an HTTP request if(wifiConnected) { // Request /utc/now from the previously set-up server rest.get("/utc/now"); char response[BUFLEN]; memset(response, 0, BUFLEN); uint16_t code = rest.waitResponse(response, BUFLEN); if(code == HTTP_STATUS_OK) { Serial.println("ARDUINO: GET successful:"); Serial.println(response); } else { Serial.print("ARDUINO: GET failed: "); Serial.println(code); } delay(1000); } } } @endcode */ uint16_t ELClientRest::getResponse(char* data, uint16_t maxLen) { if (_status == 0) return 0; memcpy(data, _data, _len>maxLen?maxLen:_len); int16_t s = _status; _status = 0; return s; } /*! waitResponse(char* data, uint16_t maxLen, uint32_t timeout) @brief Wait for the response @details Wait for the response from the remote server for time_out, returns the HTTP status code, 0 if no response (may need to wait longer) @warning Blocks the Arduino code for 5 seconds! not recommended to use. Received packet is NOT null-terminated @param data Pointer to buffer for received packet @param maxLen Size of buffer for received packet. If the received packet is larger than the buffer, the received packet will be truncated. @param timeout (optional) Timout in milli seconds to wait for a response, defaults to 5000ms @return uint16_t Size of received packet or number of sent bytes or 0 if no response @par Example @code #define BUFLEN 266 void loop() { // process any callbacks coming from esp_link esp.Process(); // if we're connected make an HTTP request if(wifiConnected) { // Request /utc/now from the previously set-up server rest.get("/utc/now"); char response[BUFLEN]; memset(response, 0, BUFLEN); uint16_t code = rest.waitResponse(response, BUFLEN); if(code == HTTP_STATUS_OK) { Serial.println("ARDUINO: GET successful:"); Serial.println(response); } else { Serial.print("ARDUINO: GET failed: "); Serial.println(code); } delay(1000); } } @endcode */ uint16_t ELClientRest::waitResponse(char* data, uint16_t maxLen, uint32_t timeout) { uint32_t wait = millis(); while (_status == 0 && (millis() - wait < timeout)) { _elc->Process(); } return getResponse(data, maxLen); }