/* aREST Library for Arduino See the README file for more details. Written in 2014 by Marco Schwartz. This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License: http://creativecommons.org/licenses/by-sa/4.0/ Version 2.5.0 Changelog: Version 2.5.0: Added support for the ESP32 WiFi chip (local & cloud) Version 2.4.2: Added publish() support for MKR1000 Version 2.4.1: Additional fixes for Pro plans Version 2.4.0: Added support for aREST Pro & several fixes Version 2.3.1: Fixed pin mapping for NodeMCU/Wemos boards Version 2.3.0: Implement required changes for the cloud server upgrade Version 2.2.1: Added compatibility with the WINC1500 chip Version 2.2.0: Added compatibility with the Arduino MKR1000 board Version 2.1.2: Added data about hardware type in JSON answer Version 2.1.1: Fixed analogWrite() for ESP8266 chips Version 2.1.0: Added publish() function Version 2.0.2: Able to change MQTT remote server Version 2.0.2: Added cloud access support for the Ethernet library Version 2.0.1: Added beta support for cloud access via cloud.arest.io Version 2.0.0: Added beta support for MQTT communications Version 1.9.10: Added support for floats & Strings for Uno (without the CC3000 chip) Version 1.9.8: Added support for ESP8266 chip Version 1.9.7: Added support for Arduino 1.6.2 Version 1.9.6: Added support for float variables for Arduino Mega Version 1.9.5: Added compatibility with Arduino IDE 1.5.8 Version 1.9.4: Bug fixes & added support for configuring analog pints as digital outputs Version 1.9.3: Added description of available variables for the /id and / routes Version 1.9.2: Added compatibility with the Arduino WiFi library Version 1.9.1: Added compatibility with CORS Version 1.9: New speedup of the library (answers 2x faster in HTTP compared to version 1.8) Version 1.8: Speedup of the library (answers 2.5x faster with the CC3000 WiFi chip) Version 1.7.5: Reduced memory footprint of the library Version 1.7.4: Added a function to read all analog & digital inputs at once Version 1.7.3: Added LIGHTWEIGHT mode to only send limited data back Version 1.7.2: Added possibility to assign a status pin connected to a LED Version 1.7.1: Added possibility to change number of exposed variables & functions Version 1.7: Added compatibility with the Arduino Due & Teensy 3.x Version 1.6: Added compatibility with the Arduino Yun Version 1.5: Size reduction, and added compatibility with Adafruit BLE Version 1.4: Added authentification with API key Version 1.3: Added support for the Ethernet shield Version 1.2: Added support of Serial communications Version 1.1: Added variables & functions support Version 1.0: First working version of the library */ #ifndef aRest_h #define aRest_h // Include Arduino header #include "Arduino.h" // MQTT packet size #undef MQTT_MAX_PACKET_SIZE #define MQTT_MAX_PACKET_SIZE 512 // Using ESP8266 ? #if defined(ESP8266) || defined(ESP32) #include "stdlib_noniso.h" #endif // Which board? #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(CORE_WILDFIRE) || defined(ESP8266) || defined(ESP32) #define NUMBER_ANALOG_PINS 16 #define NUMBER_DIGITAL_PINS 54 #define OUTPUT_BUFFER_SIZE 2000 #elif defined(__AVR_ATmega328P__) && !defined(ADAFRUIT_CC3000_H) #define NUMBER_ANALOG_PINS 6 #define NUMBER_DIGITAL_PINS 14 #define OUTPUT_BUFFER_SIZE 350 #elif defined(ADAFRUIT_CC3000_H) #define NUMBER_ANALOG_PINS 6 #define NUMBER_DIGITAL_PINS 14 #define OUTPUT_BUFFER_SIZE 275 #else #define NUMBER_ANALOG_PINS 6 #define NUMBER_DIGITAL_PINS 14 #define OUTPUT_BUFFER_SIZE 350 #endif // Hardware data #if defined(ESP8266) #define HARDWARE "esp8266" #elif defined(ESP32) #define HARDWARE "esp32" #else #define HARDWARE "arduino" #endif // Size of name & ID #define NAME_SIZE 20 #define ID_SIZE 10 // Subscriptions #define NUMBER_SUBSCRIPTIONS 4 // Debug mode #ifndef DEBUG_MODE #define DEBUG_MODE 0 #endif // Use light answer mode #ifndef LIGHTWEIGHT #define LIGHTWEIGHT 0 #endif // Default number of max. exposed variables #ifndef NUMBER_VARIABLES #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(CORE_WILDFIRE) || defined(ESP8266)|| defined(ESP32) || !defined(ADAFRUIT_CC3000_H) #define NUMBER_VARIABLES 10 #else #define NUMBER_VARIABLES 5 #endif #endif // Default number of max. exposed functions #ifndef NUMBER_FUNCTIONS #if defined(__AVR_ATmega1280__) || defined(ESP32) || defined(__AVR_ATmega2560__) || defined(CORE_WILDFIRE) || defined(ESP8266) #define NUMBER_FUNCTIONS 10 #else #define NUMBER_FUNCTIONS 5 #endif #endif class aREST { public: aREST() { command = 'u'; pin_selected = false; status_led_pin = 255; state = 'u'; } aREST(char* rest_remote_server, int rest_port) { command = 'u'; pin_selected = false; status_led_pin = 255; state = 'u'; remote_server = rest_remote_server; port = rest_port; } #if defined(_ADAFRUIT_MQTT_FONA_H_) #endif #if defined(PubSubClient_h) // With default server aREST(PubSubClient& client) { command = 'u'; pin_selected = false; status_led_pin = 255; state = 'u'; private_mqtt_server = false; client.setServer(mqtt_server, 1883); } // With another server aREST(PubSubClient& client, char* new_mqtt_server) { command = 'u'; pin_selected = false; status_led_pin = 255; state = 'u'; private_mqtt_server = true; setMQTTServer(new_mqtt_server); client.setServer(new_mqtt_server, 1883); } // Get topic char* get_topic() { return out_topic; } // Subscribe to events void subscribe(String device, String eventName) { // Build topic String topic = device + "_" + eventName + "_in"; // Subscribe char charBuf[50]; topic.toCharArray(charBuf, 50); subscriptions_names[subscriptions_index] = charBuf; subscriptions_index++; } // Publish to cloud template void publish(PubSubClient& client, String eventName, T data) { // Get event data if (DEBUG_MODE) { Serial.print("Publishing event " + eventName + " with data: "); Serial.println(data); } // Build message String message = "{\"client_id\": \"" + String(id) + "\", \"event_name\": \"" + eventName + "\", \"data\": \"" + String(data) + "\"}"; if (DEBUG_MODE) { Serial.print("Sending message via MQTT: "); Serial.println(message); } // Convert char charBuf[100]; message.toCharArray(charBuf, 100); // Publish client.publish(publish_topic, charBuf); } void setKey(char* proKey, PubSubClient& client) { // Assign MQTT server mqtt_server = "104.131.78.157"; client.setServer(mqtt_server, 1883); // Set key proKey = proKey; // Generate MQTT random ID String randomId; randomId = gen_random(6); // Assign ID strncpy(id, randomId.c_str(), ID_SIZE); // Build topics IDs String inTopic = randomId + String(proKey) + String("_in"); String outTopic = randomId + String(proKey) + String("_out"); strcpy(in_topic, inTopic.c_str()); strcpy(out_topic, outTopic.c_str()); // Build client ID String clientId = randomId + String(proKey); strcpy(client_id, clientId.c_str()); } #endif // Set status LED void set_status_led(uint8_t pin){ // Set variables status_led_pin = pin; // Set pin as output pinMode(status_led_pin,OUTPUT); } #if !defined(ESP32) // Glow status LED void glow_led() { if(status_led_pin != 255){ unsigned long i = millis(); int j = i % 4096; if (j > 2048) { j = 4096 - j;} analogWrite(status_led_pin,j/8); } } #endif // Send HTTP headers for Ethernet & WiFi void send_http_headers(){ addToBuffer(F("HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: POST, GET, PUT, OPTIONS\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n")); } // Reset variables after a request void reset_status() { if (DEBUG_MODE) { #if defined(ESP8266) Serial.print("Memory loss before reset:"); Serial.println(freeMemory - ESP.getFreeHeap(),DEC); freeMemory = ESP.getFreeHeap(); #endif } answer = ""; command = 'u'; pin_selected = false; state = 'u'; arguments = ""; index = 0; //memset(&buffer[0], 0, sizeof(buffer)); if (DEBUG_MODE) { #if defined(ESP8266) Serial.print("Memory loss after reset:"); Serial.println(freeMemory - ESP.getFreeHeap(),DEC); freeMemory = ESP.getFreeHeap(); Serial.print("Memory free:"); Serial.println(freeMemory, DEC); #endif } } // Handle request with the Adafruit CC3000 WiFi library #ifdef ADAFRUIT_CC3000_H void handle(Adafruit_CC3000_ClientRef& client) { if (client.available()) { // Handle request handle_proto(client,true,0); // Answer sendBuffer(client,32,20); client.stop(); // Reset variables for the next command reset_status(); } } template void publish(Adafruit_CC3000_ClientRef& client, String eventName, T value) { // Publish request publish_proto(client, eventName, value); } // Handle request with the Arduino Yun #elif defined(_YUN_CLIENT_H_) void handle(YunClient& client) { if (client.available()) { // Handle request handle_proto(client,false,0); // Answer sendBuffer(client,25,10); client.stop(); // Reset variables for the next command reset_status(); } } template void publish(YunClient& client, String eventName, T value) { // Publish request publish_proto(client, eventName, value); } // Handle request with the Adafruit BLE board #elif defined(_ADAFRUIT_BLE_UART_H_) void handle(Adafruit_BLE_UART& serial) { if (serial.available()) { // Handle request handle_proto(serial,false,0); // Answer sendBuffer(serial,100,1); // Reset variables for the next command reset_status(); } } // template // void publish(Adafruit_BLE_UART& serial, String eventName, T value) { // // Publish request // publish_proto(client, eventName, value); // } // Handle request for the Arduino Ethernet shield #elif defined(ethernet_h) void handle(EthernetClient& client){ if (client.available()) { // Handle request handle_proto(client,true,0); // Answer sendBuffer(client,50,0); client.stop(); // Reset variables for the next command reset_status(); } } template void publish(EthernetClient& client, String eventName, T value) { // Publish request publish_proto(client, eventName, value); } // Handle request for the Cytron Clone ESP8266 #elif defined(_CYTRONWIFISERVER_H_) void handle(ESP8266Client& client){ if (client.available()) { // Handle request handle_proto(client,true,0); // Answer sendBuffer(client,0,0); client.stop(); // Reset variables for the next command reset_status(); } } // Handle request for the ESP8266 chip #elif defined(ESP8266) void handle(WiFiClient& client){ if (DEBUG_MODE) { Serial.print("Memory loss before available:"); Serial.println(freeMemory - ESP.getFreeHeap(),DEC); freeMemory = ESP.getFreeHeap(); } if (client.available()) { if (DEBUG_MODE) { Serial.print("Memory loss before handling:"); Serial.println(freeMemory - ESP.getFreeHeap(),DEC); freeMemory = ESP.getFreeHeap(); } // Handle request handle_proto(client,true,0); if (DEBUG_MODE) { Serial.print("Memory loss after handling:"); Serial.println(freeMemory - ESP.getFreeHeap(),DEC); freeMemory = ESP.getFreeHeap(); } // Answer sendBuffer(client,0,0); client.stop(); // Reset variables for the next command reset_status(); } } template void publish(WiFiClient& client, String eventName, T value) { // Publish request publish_proto(client, eventName, value); } // Handle request for the Arduino MKR1000 board #elif defined(WIFI_H) void handle(WiFiClient& client){ if (client.available()) { if (DEBUG_MODE) {Serial.println("Request received");} // Handle request handle_proto(client,true,0); // Answer sendBuffer(client,0,0); client.stop(); // Reset variables for the next command reset_status(); } } template void publish(WiFiClient& client, String eventName, T value) { // Publish request publish_proto(client, eventName, value); } // Handle request for the Arduino WiFi shield #elif defined(WiFi_h) void handle(WiFiClient& client){ if (client.available()) { if (DEBUG_MODE) {Serial.println("Request received");} // Handle request handle_proto(client,true,0); // Answer sendBuffer(client,50,1); client.stop(); // Reset variables for the next command reset_status(); } } template void publish(WiFiClient& client, String eventName, T value) { // Publish request publish_proto(client, eventName, value); } #elif defined(CORE_TEENSY) // Handle request on the Serial port void handle(usb_serial_class& serial){ if (serial.available()) { // Handle request handle_proto(serial,false,1); // Answer sendBuffer(serial,25,1); // Reset variables for the next command reset_status(); } } template void publish(usb_serial_class& client, String eventName, T value) { // Publish request publish_proto(client, eventName, value); } #elif defined(__AVR_ATmega32U4__) // Handle request on the Serial port void handle(Serial_& serial){ if (serial.available()) { // Handle request handle_proto(serial,false,1); // Answer sendBuffer(serial,25,1); // Reset variables for the next command reset_status(); } } template void publish(Serial_& client, String eventName, T value) { // Publish request publish_proto(client, eventName, value); } #else // Handle request on the Serial port void handle(HardwareSerial& serial){ if (serial.available()) { // Handle request handle_proto(serial,false,1); // Answer sendBuffer(serial,25,1); // Reset variables for the next command reset_status(); } } template void publish(HardwareSerial& client, String eventName, T value) { // Publish request publish_proto(client, eventName, value); } #endif void handle(char * string) { // Process String handle_proto(string); // Reset variables for the next command reset_status(); } void handle_proto(char * string) { // Check if there is data available to read for (int i = 0; i < strlen(string); i++){ char c = string[i]; answer = answer + c; // Process data process(c); } // Send command send_command(false); } template void publish_proto(T& client, String eventName, V value) { // Format data String data = "name=" + eventName + "&data=" + String(value); Serial.println("POST /" + String(id) + "/events HTTP/1.1"); Serial.println("Host: " + String(remote_server) + ":" + String(port)); Serial.println(F("Content-Type: application/x-www-form-urlencoded")); Serial.print(F("Content-Length: ")); Serial.println(data.length()); Serial.println(); Serial.print(data); // Send request client.println(F("POST /1/events HTTP/1.1")); client.println("Host: " + String(remote_server) + ":" + String(port)); client.println(F("Content-Type: application/x-www-form-urlencoded")); client.print(F("Content-Length: ")); client.println(data.length()); client.println(); client.print(data); } template void handle_proto(T& serial, bool headers, uint8_t read_delay) { // Check if there is data available to read while (serial.available()) { // Get the server answer char c = serial.read(); delay(read_delay); answer = answer + c; //if (DEBUG_MODE) {Serial.print(c);} // Process data process(c); } // Send command send_command(headers); } #if defined(PubSubClient_h) // Process callback void handle_callback(PubSubClient& client, char* topic, byte* payload, unsigned int length) { // Process received message int i; char mqtt_msg[100]; for(i = 0; i < length; i++) { mqtt_msg[i] = payload[i]; } mqtt_msg[i] = '\0'; String msgString = String(mqtt_msg); if (DEBUG_MODE) { Serial.print("Received message via MQTT: "); Serial.println(msgString); } // Process aREST commands String modified_message = String(msgString) + " /"; char char_message[100]; modified_message.toCharArray(char_message, 100); // Handle command with aREST handle(char_message); // Read answer char * answer = getBuffer(); // Send response if (DEBUG_MODE) { Serial.print("Sending message via MQTT: "); Serial.println(answer); Serial.print("Size of MQTT message: "); Serial.println(strlen(answer)); Serial.print("Size of client ID: "); Serial.println(strlen(client_id)); } int max_message_size = 128 - 20 - strlen(client_id); if (strlen(answer) < max_message_size) { client.publish(out_topic, answer); } else { // Max iteration uint8_t max_iteration = (int)(strlen(answer)/max_message_size) + 1; // Send data for (uint8_t i = 0; i < max_iteration; i++) { char intermediate_buffer[max_message_size+1]; memcpy(intermediate_buffer, buffer + i*max_message_size, max_message_size); intermediate_buffer[max_message_size] = '\0'; if (DEBUG_MODE) { Serial.print("Intermediate buffer: "); Serial.println(intermediate_buffer); Serial.print("Intermediate buffer size: "); Serial.println(strlen(intermediate_buffer)); } client.publish(out_topic, intermediate_buffer); } } // Send message // client.publish(out_topic, answer); // Reset buffer resetBuffer(); } // Handle request on the Serial port void loop(PubSubClient& client){ // Connect to cloud if (!client.connected()) { reconnect(client); } client.loop(); } void handle(PubSubClient& client){ // Connect to cloud if (!client.connected()) { reconnect(client); } client.loop(); } void reconnect(PubSubClient& client) { // Loop until we're reconnected while (!client.connected()) { Serial.print(F("Attempting MQTT connection...")); // Attempt to connect if (client.connect(client_id)) { if (private_mqtt_server) { Serial.println(F("Connected to MQTT server")); } else { Serial.println(F("Connected to aREST.io")); } client.subscribe(in_topic); // Subscribe to all // if (subscriptions_index > 0) { // // for (int i = 0; i < subscriptions_index; i++) { // if (DEBUG_MODE) { // Serial.print(F("Subscribing to additional topic: ")); // Serial.println(subscriptions_names[i]); // } // // client.subscribe(subscriptions_names[i]); // } // // } } else { Serial.print(F("failed, rc=")); Serial.print(client.state()); Serial.println(F(" try again in 5 seconds")); // Wait 5 seconds before retrying delay(5000); } } } #endif void process(char c){ // Check if we are receveing useful data and process it if ((c == '/' || c == '\r') && state == 'u') { if (DEBUG_MODE) { // #if defined(ESP8266) // Serial.print("Memory loss:"); // Serial.println(freeMemory - ESP.getFreeHeap(),DEC); // freeMemory = ESP.getFreeHeap(); // #endif Serial.println(answer); } // If the command is mode, and the pin is already selected if (command == 'm' && pin_selected && state == 'u') { // Get state state = answer[0]; } // If a digital command has been received, process the data accordingly if (command == 'd' && pin_selected && state == 'u') { // If it's a read command, read from the pin and send data back if (answer[0] == 'r') {state = 'r';} // If not, get value we want to apply to the pin else {value = answer.toInt(); state = 'w';} } // If analog command has been selected, process the data accordingly if (command == 'a' && pin_selected && state == 'u') { // If it's a read, read from the correct pin if (answer[0] == 'r') {state = 'r';} // Else, write analog value else {value = answer.toInt(); state = 'w';} } // If the command is already selected, get the pin if (command != 'u' && pin_selected == false) { // Get pin if (answer[0] == 'A') { pin = 14 + answer[1] - '0'; } else { pin = answer.toInt(); } // Save pin for message message_pin = pin; // For ESP8266-12 boards (NODEMCU) #if defined(ARDUINO_ESP8266_NODEMCU) || defined(ARDUINO_ESP8266_WEMOS_D1MINI) pin = esp_12_pin_map(pin); #endif if (DEBUG_MODE) { Serial.print("Selected pin: "); Serial.println(pin); } // Mark pin as selected pin_selected = true; // Nothing more ? if ((answer[1] != '/' && answer[2] != '/') || (answer[1] == ' ' && answer[2] == '/') || (answer[2] == ' ' && answer[3] == '/')) { // Nothing more & digital ? if (command == 'd') { // Read all digital ? if (answer[0] == 'a') {state = 'a';} // Save state & end there else {state = 'r';} } // Nothing more & analog ? if (command == 'a') { // Read all analog ? if (answer[0] == 'a') {state = 'a';} // Save state & end there else {state = 'r';} } } } // Digital command received ? if (answer.startsWith("digital")) {command = 'd';} // Mode command received ? if (answer.startsWith("mode")) {command = 'm';} // Analog command received ? if (answer.startsWith("analog")) { command = 'a'; #if defined(ESP8266) analogWriteRange(255); #endif } // Variable or function request received ? if (command == 'u') { // Check if variable name is in int array for (uint8_t i = 0; i < variables_index; i++){ if(answer.startsWith(int_variables_names[i])) { // End here pin_selected = true; state = 'x'; // Set state command = 'v'; value = i; } } // Check if variable name is in float array (Mega & ESP8266 only) #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(ESP8266) || defined(CORE_WILDFIRE) || !defined(ADAFRUIT_CC3000_H) || defined(ESP32) for (uint8_t i = 0; i < float_variables_index; i++){ if(answer.startsWith(float_variables_names[i])) { // End here pin_selected = true; state = 'x'; // Set state command = 'l'; value = i; } } #endif // Check if variable name is in float array (Mega & ESP8266 only) #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(ESP8266) || defined(CORE_WILDFIRE) || !defined(ADAFRUIT_CC3000_H) || defined(ESP32) for (uint8_t i = 0; i < string_variables_index; i++){ if(answer.startsWith(string_variables_names[i])) { // End here pin_selected = true; state = 'x'; // Set state command = 's'; value = i; } } #endif // Check if function name is in array for (uint8_t i = 0; i < functions_index; i++){ if(answer.startsWith(functions_names[i])) { // End here pin_selected = true; state = 'x'; // Set state command = 'f'; value = i; // Get command arguments = ""; uint8_t header_length = strlen(functions_names[i]); if (answer.substring(header_length, header_length + 1) == "?") { uint8_t footer_start = answer.length(); if (answer.endsWith(" HTTP/")) footer_start -= 6; // length of " HTTP/" arguments = answer.substring(header_length + 8, footer_start); } } } // If the command is "id", return device id, name and status if ( (answer[0] == 'i' && answer[1] == 'd') ){ // Set state command = 'i'; // End here pin_selected = true; state = 'x'; } if (answer[0] == ' '){ // Set state command = 'r'; // End here pin_selected = true; state = 'x'; } // Check the type of HTTP request // if (answer.startsWith("GET")) {method = "GET";} // if (answer.startsWith("POST")) {method = "POST";} // if (answer.startsWith("PUT")) {method = "PUT";} // if (answer.startsWith("DELETE")) {method = "DELETE";} // if (DEBUG_MODE && method != "") { // Serial.print("Selected method: "); // Serial.println(method); // } } answer = ""; } } bool send_command(bool headers) { if (DEBUG_MODE) { #if defined(ESP8266) Serial.print("Memory loss:"); Serial.println(freeMemory - ESP.getFreeHeap(),DEC); freeMemory = ESP.getFreeHeap(); #endif Serial.println(F("Sending command")); Serial.print(F("Command: ")); Serial.println(command); Serial.print(F("State: ")); Serial.println(state); Serial.print(F("State of buffer at the start: ")); Serial.println(buffer); } // Start of message if (headers && command != 'r') {send_http_headers();} // Mode selected if (command == 'm'){ // Send feedback to client if (!LIGHTWEIGHT){ addToBuffer(F("{\"message\": \"Pin D")); addToBuffer(message_pin); } // Input if (state == 'i'){ // Set pin to Input pinMode(pin,INPUT); // Send feedback to client if (!LIGHTWEIGHT){addToBuffer(F(" set to input\", "));} } // Input with pullup if (state == 'I'){ // Set pin to Input with pullup pinMode(pin,INPUT_PULLUP); // Send feedback to client if (!LIGHTWEIGHT){addToBuffer(F(" set to input with pullup\", "));} } // Output if (state == 'o'){ // Set to Output pinMode(pin,OUTPUT); // Send feedback to client if (!LIGHTWEIGHT){addToBuffer(F(" set to output\", "));} } } // Digital selected if (command == 'd') { if (state == 'r'){ // Read from pin value = digitalRead(pin); // Send answer if (LIGHTWEIGHT){addToBuffer(value);} else { addToBuffer(F("{\"return_value\": ")); addToBuffer(value); addToBuffer(F(", ")); } } #if !defined(__AVR_ATmega32U4__) || !defined(ADAFRUIT_CC3000_H) if (state == 'a') { if (!LIGHTWEIGHT) {addToBuffer(F("{"));} for (uint8_t i = 0; i < NUMBER_DIGITAL_PINS; i++) { // Read analog value value = digitalRead(i); // Send feedback to client if (LIGHTWEIGHT){ addToBuffer(value); addToBuffer(F(",")); } else { addToBuffer(F("\"D")); addToBuffer(i); addToBuffer(F("\": ")); addToBuffer(value); addToBuffer(F(", ")); } } } #endif if (state == 'w') { // Disable analogWrite if ESP8266 #if defined(ESP8266) analogWrite(pin, 0); #endif // Apply on the pin digitalWrite(pin,value); // Send feedback to client if (!LIGHTWEIGHT){ addToBuffer(F("{\"message\": \"Pin D")); addToBuffer(message_pin); addToBuffer(F(" set to ")); addToBuffer(value); addToBuffer(F("\", ")); } } } // Analog selected if (command == 'a') { if (state == 'r'){ // Read analog value value = analogRead(pin); // Send feedback to client if (LIGHTWEIGHT){addToBuffer(value);} else { addToBuffer(F("{\"return_value\": ")); addToBuffer(value); addToBuffer(F(", ")); } } #if !defined(__AVR_ATmega32U4__) if (state == 'a') { if (!LIGHTWEIGHT) {addToBuffer(F("{"));} for (uint8_t i = 0; i < NUMBER_ANALOG_PINS; i++) { // Read analog value value = analogRead(i); // Send feedback to client if (LIGHTWEIGHT){ addToBuffer(value); addToBuffer(F(",")); } else { addToBuffer(F("\"A")); addToBuffer(i); addToBuffer(F("\": ")); addToBuffer(value); addToBuffer(F(", ")); } } } #endif if (state == 'w') { // Write output value #if !defined(ESP32) analogWrite(pin,value); #endif // Send feedback to client addToBuffer(F("{\"message\": \"Pin D")); addToBuffer(message_pin); addToBuffer(F(" set to ")); addToBuffer(value); addToBuffer(F("\", ")); } } // Variable selected if (command == 'v') { // Send feedback to client if (LIGHTWEIGHT){addToBuffer(*int_variables[value]);} else { addToBuffer(F("{\"")); addToBuffer(int_variables_names[value]); addToBuffer(F("\": ")); addToBuffer(*int_variables[value]); addToBuffer(F(", ")); } } // Float ariable selected (Mega only) #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(ESP8266) || defined(CORE_WILDFIRE) || !defined(ADAFRUIT_CC3000_H) || defined(ESP32) if (command == 'l') { // Send feedback to client if (LIGHTWEIGHT){addToBuffer(*float_variables[value]);} else { addToBuffer(F("{\"")); addToBuffer(float_variables_names[value]); addToBuffer(F("\": ")); addToBuffer(*float_variables[value]); addToBuffer(F(", ")); } } #endif // String variable selected (Mega & ESP8266 only) #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(ESP8266) || defined(CORE_WILDFIRE) || !defined(ADAFRUIT_CC3000_H) || defined(ESP32) if (command == 's') { // Send feedback to client if (LIGHTWEIGHT){addToBuffer(*string_variables[value]);} else { addToBuffer(F("{\"")); addToBuffer(string_variables_names[value]); addToBuffer(F("\": \"")); addToBuffer(*string_variables[value]); addToBuffer(F("\", ")); } } #endif // Function selected if (command == 'f') { // Execute function int result = functions[value](arguments); // Send feedback to client if (!LIGHTWEIGHT) { addToBuffer(F("{\"return_value\": ")); addToBuffer(result); addToBuffer(F(", ")); //addToBuffer(F(", \"message\": \"")); //addToBuffer(functions_names[value]); //addToBuffer(F(" executed\", ")); } } if (command == 'r' || command == 'u') { root_answer(); } if (command == 'i') { if (LIGHTWEIGHT) {addToBuffer(id);} else { addToBuffer(F("{")); } } // End of message if (LIGHTWEIGHT){ addToBuffer(F("\r\n")); } else { if (command != 'r' && command != 'u') { addToBuffer(F("\"id\": \"")); addToBuffer(id); addToBuffer(F("\", \"name\": \"")); addToBuffer(name); addToBuffer(F("\", \"hardware\": \"")); addToBuffer(HARDWARE); addToBuffer(F("\", \"connected\": true}\r\n")); } } if (DEBUG_MODE) { #if defined(ESP8266) Serial.print("Memory loss:"); Serial.println(freeMemory - ESP.getFreeHeap(),DEC); freeMemory = ESP.getFreeHeap(); #endif Serial.print(F("State of buffer at the end: ")); Serial.println(buffer); } // End here return true; } virtual void root_answer() { #if defined(ADAFRUIT_CC3000_H) || defined(ESP8266) || defined(ethernet_h) || defined(WiFi_h) #if !defined(PubSubClient_h) if (command != 'u') { addToBuffer(F("HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: POST, GET, PUT, OPTIONS\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n")); } #endif #endif if (LIGHTWEIGHT) {addToBuffer(id);} else { // Start addToBuffer(F("{\"variables\": {")); #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(ESP8266) || defined(CORE_WILDFIRE) || !defined(ADAFRUIT_CC3000_H) // Int variables if (variables_index == 0 && string_variables_index == 0 && float_variables_index == 0){ addToBuffer(F(" }, ")); } else { if (variables_index > 0){ for (uint8_t i = 0; i < variables_index; i++){ addToBuffer(F("\"")); addToBuffer(int_variables_names[i]); addToBuffer(F("\": ")); addToBuffer(*int_variables[i]); addToBuffer(F(", ")); } } if (string_variables_index > 0){ for (uint8_t i = 0; i < string_variables_index; i++){ addToBuffer(F("\"")); addToBuffer(string_variables_names[i]); addToBuffer(F("\": \"")); addToBuffer(*string_variables[i]); addToBuffer(F("\", ")); } } if (float_variables_index > 0){ for (uint8_t i = 0; i < float_variables_index; i++){ addToBuffer(F("\"")); addToBuffer(float_variables_names[i]); addToBuffer(F("\": ")); addToBuffer(*float_variables[i]); addToBuffer(F(", ")); } } removeLastBufferChar(); removeLastBufferChar(); addToBuffer(F("}, ")); } #else // Int variables if (variables_index > 0){ for (uint8_t i = 0; i < variables_index-1; i++){ addToBuffer(F("\"")); addToBuffer(int_variables_names[i]); addToBuffer(F("\": ")); addToBuffer(*int_variables[i]); addToBuffer(F(", ")); } // End addToBuffer(F("\"")); addToBuffer(int_variables_names[variables_index-1]); addToBuffer(F("\": ")); addToBuffer(*int_variables[variables_index-1]); addToBuffer(F("}, ")); } else { addToBuffer(F(" }, ")); } #endif } // End addToBuffer(F("\"id\": \"")); addToBuffer(id); addToBuffer(F("\", \"name\": \"")); addToBuffer(name); addToBuffer(F("\", \"hardware\": \"")); addToBuffer(HARDWARE); #if defined(PubSubClient_h) addToBuffer(F("\", \"connected\": true}")); #else addToBuffer(F("\", \"connected\": true}\r\n")); #endif } void variable(char * variable_name, int *variable){ int_variables[variables_index] = variable; int_variables_names[variables_index] = variable_name; variables_index++; } // Float variables (Mega & ESP only, or without CC3000) #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(ESP8266) || defined(CORE_WILDFIRE) || !defined(ADAFRUIT_CC3000_H) || defined(ESP32) void variable(char * variable_name, float *variable){ float_variables[float_variables_index] = variable; float_variables_names[float_variables_index] = variable_name; float_variables_index++; } #endif // String variables (Mega & ESP only, or without CC3000) #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(ESP8266) || defined(CORE_WILDFIRE) || !defined(ADAFRUIT_CC3000_H) || defined(ESP32) void variable(char * variable_name, String *variable){ string_variables[string_variables_index] = variable; string_variables_names[string_variables_index] = variable_name; string_variables_index++; } #endif void function(char * function_name, int (*f)(String)){ functions_names[functions_index] = function_name; functions[functions_index] = f; functions_index++; } // Set device ID void set_id(char *device_id){ strncpy(id, device_id, ID_SIZE); #if defined(PubSubClient_h) // Generate MQTT random ID String randomId; randomId = gen_random(6); // Build topics IDs String inTopic = randomId + String(id) + String("_in"); String outTopic = randomId + String(id) + String("_out"); strcpy(in_topic, inTopic.c_str()); strcpy(out_topic, outTopic.c_str()); // inTopic.toCharArray(in_topic, inTopic.length()); // outTopic.toCharArray(out_topic, outTopic.length()); // Build client ID String clientId = randomId + String(id); strcpy(client_id, clientId.c_str()); // clientId.toCharArray(client_id, clientId.length()); if (DEBUG_MODE) { Serial.print("Input MQTT topic: "); Serial.println(in_topic); Serial.print("Output MQTT topic: "); Serial.println(out_topic); Serial.print("Client ID: "); Serial.println(client_id); } #endif } #if defined(__arm__) String getChipId() { volatile uint32_t val1, val2, val3, val4; volatile uint32_t *ptr1 = (volatile uint32_t *)0x0080A00C; val1 = *ptr1; volatile uint32_t *ptr = (volatile uint32_t *)0x0080A040; val2 = *ptr; ptr++; val3 = *ptr; ptr++; val4 = *ptr; char buf[33]; sprintf(buf, "%8x%8x%8x%8x", val1, val2, val3, val4); return String(buf); } #endif #if defined(PubSubClient_h) String gen_random(int length) { String randomString; #if defined(ESP8266) randomString = String(ESP.getChipId()); randomString = randomString.substring(0, 6); #elif defined(__arm__) randomString = getChipId(); randomString = randomString.substring(0, 6); #else String charset = "abcdefghijklmnopqrstuvwxyz0123456789"; // Generate int l = charset.length(); int key; for (int n = 0; n < length; n++) { key = random(0, l - 1); randomString += charset[key]; } #endif return randomString; } #endif // Set device name void set_name(char *device_name){ strcpy(name, device_name); } // Set device name void set_name(String device_name){ device_name.toCharArray(name, NAME_SIZE); } // Set device ID void set_id(String device_id){ device_id.toCharArray(id, ID_SIZE); set_id(id); } // Remove last char from buffer void removeLastBufferChar() { index = index - 1; } // Add to output buffer void addToBuffer(char * toAdd){ if (DEBUG_MODE) { #if defined(ESP8266) Serial.print("Memory loss:"); Serial.println(freeMemory - ESP.getFreeHeap(),DEC); freeMemory = ESP.getFreeHeap(); #endif Serial.print(F("Added to buffer as char: ")); Serial.println(toAdd); } for (int i = 0; i < strlen(toAdd); i++){ buffer[index+i] = toAdd[i]; } index = index + strlen(toAdd); } // Add to output buffer #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(ESP8266) || defined(CORE_WILDFIRE) || !defined(ADAFRUIT_CC3000_H) || defined(ESP32) void addToBuffer(String toAdd){ if (DEBUG_MODE) { #if defined(ESP8266) Serial.print("Memory loss:"); Serial.println(freeMemory - ESP.getFreeHeap(),DEC); freeMemory = ESP.getFreeHeap(); #endif Serial.print(F("Added to buffer as String: ")); Serial.println(toAdd); } for (int i = 0; i < toAdd.length(); i++){ buffer[index+i] = toAdd[i]; } index = index + toAdd.length(); } #endif // Add to output buffer void addToBuffer(uint16_t toAdd){ char number[10]; itoa(toAdd,number,10); addToBuffer(number); } // Add to output buffer void addToBuffer(int toAdd){ char number[10]; itoa(toAdd,number,10); addToBuffer(number); } // Add to output buffer (Mega & ESP only) #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(ESP8266) || defined(CORE_WILDFIRE) || !defined(ADAFRUIT_CC3000_H) void addToBuffer(float toAdd){ char number[10]; dtostrf(toAdd, 5, 2, number); addToBuffer(number); } #endif // Add to output buffer void addToBuffer(const __FlashStringHelper *toAdd){ if (DEBUG_MODE) { #if defined(ESP8266) Serial.print("Memory loss:"); Serial.println(freeMemory - ESP.getFreeHeap(),DEC); freeMemory = ESP.getFreeHeap(); #endif Serial.print(F("Added to buffer as progmem: ")); Serial.println(toAdd); } uint8_t idx = 0; PGM_P p = reinterpret_cast(toAdd); while (1) { unsigned char c = pgm_read_byte(p++); if (c == 0) break; buffer[index + idx] = c; idx++; } index = index + idx; } template void sendBuffer(T& client, uint8_t chunkSize, uint8_t wait_time) { if (DEBUG_MODE) { #if defined(ESP8266) Serial.print("Memory loss before sending:"); Serial.println(freeMemory - ESP.getFreeHeap(),DEC); freeMemory = ESP.getFreeHeap(); #endif Serial.print(F("Buffer size: ")); Serial.println(index); } // Send all of it if (chunkSize == 0) { client.print(buffer); } // Send chunk by chunk else { // Max iteration uint8_t max_iteration = (int)(index/chunkSize) + 1; // Send data for (uint8_t i = 0; i < max_iteration; i++) { char intermediate_buffer[chunkSize+1]; memcpy(intermediate_buffer, buffer + i*chunkSize, chunkSize); intermediate_buffer[chunkSize] = '\0'; // Send intermediate buffer #ifdef ADAFRUIT_CC3000_H client.fastrprint(intermediate_buffer); #else client.print(intermediate_buffer); #endif // Wait for client to get data delay(wait_time); if (DEBUG_MODE) { Serial.print(F("Sent buffer: ")); Serial.println(intermediate_buffer); } } } if (DEBUG_MODE) { #if defined(ESP8266) Serial.print("Memory loss after sending:"); Serial.println(freeMemory - ESP.getFreeHeap(),DEC); freeMemory = ESP.getFreeHeap(); #endif Serial.print(F("Buffer size: ")); Serial.println(index); } // Reset the buffer resetBuffer(); if (DEBUG_MODE) { #if defined(ESP8266) Serial.print("Memory loss after buffer reset:"); Serial.println(freeMemory - ESP.getFreeHeap(),DEC); freeMemory = ESP.getFreeHeap(); #endif Serial.print(F("Buffer size: ")); Serial.println(index); } } char * getBuffer() { return buffer; } void resetBuffer(){ memset(&buffer[0], 0, sizeof(buffer)); // free(buffer); } uint8_t esp_12_pin_map(uint8_t pin) { // Right pin uint8_t mapped_pin; // Map switch (pin) { case 0: mapped_pin = 16; break; case 1: mapped_pin = 5; break; case 2: mapped_pin = 4; break; case 3: mapped_pin = 0; break; case 4: mapped_pin = 2; break; case 5: mapped_pin = 14; break; case 6: mapped_pin = 12; break; case 7: mapped_pin = 13; break; case 8: mapped_pin = 15; break; case 9: mapped_pin = 3; break; case 10: mapped_pin = 1; break; default: mapped_pin = 0; } return mapped_pin; } // For non AVR boards #if defined (__arm__) char *dtostrf (double val, signed char width, unsigned char prec, char *sout) { char fmt[20]; sprintf(fmt, "%%%d.%df", width, prec); sprintf(sout, fmt, val); return sout; } #endif // Memory debug #if defined(ESP8266) void initFreeMemory(){ freeMemory = ESP.getFreeHeap(); } #endif #if defined(PubSubClient_h) void setMQTTServer(char* new_mqtt_server){ mqtt_server = new_mqtt_server; } #endif private: String answer; char command; uint8_t pin; uint8_t message_pin; char state; uint16_t value; boolean pin_selected; char* remote_server; int port; char name[NAME_SIZE]; char id[ID_SIZE+1]; String arguments; // Output uffer char buffer[OUTPUT_BUFFER_SIZE]; uint16_t index; // Status LED uint8_t status_led_pin; // Int variables arrays uint8_t variables_index; int * int_variables[NUMBER_VARIABLES]; char * int_variables_names[NUMBER_VARIABLES]; // MQTT client #if defined(PubSubClient_h) // Topics char in_topic[ID_SIZE + 17]; char out_topic[ID_SIZE + 17]; char publish_topic[ID_SIZE + 10]; char client_id[ID_SIZE + 17]; // Subscribe topics & handlers uint8_t subscriptions_index; char * subscriptions_names[NUMBER_SUBSCRIPTIONS]; // aREST.io server char* mqtt_server = "45.55.196.201"; bool private_mqtt_server; // Key char* proKey; #endif // Float variables arrays (Mega & ESP8266 only) #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(ESP8266) || defined(CORE_WILDFIRE) || !defined(ADAFRUIT_CC3000_H) || defined(ESP32) uint8_t float_variables_index; float * float_variables[NUMBER_VARIABLES]; char * float_variables_names[NUMBER_VARIABLES]; #endif // String variables arrays (Mega & ESP8266 only) #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(ESP8266) || defined(CORE_WILDFIRE) || !defined(ADAFRUIT_CC3000_H) || defined(ESP32) uint8_t string_variables_index; String * string_variables[NUMBER_VARIABLES]; char * string_variables_names[NUMBER_VARIABLES]; #endif // Functions array uint8_t functions_index; int (*functions[NUMBER_FUNCTIONS])(String); char * functions_names[NUMBER_FUNCTIONS]; // Memory debug #if defined(ESP8266) int freeMemory; #endif }; #endif