├── .gitignore ├── README.markdown ├── config_rest.h ├── examples ├── Restful_Server_Ethernet │ └── Restful_Server_Ethernet.pde └── Restful_Server_Ethernet_DHCP │ └── Restful_Server_Ethernet_DHCP.ino ├── keywords.txt ├── rest_server.cpp ├── rest_server.h └── utility ├── message.cpp └── message.h /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | utility/.DS_Store -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | RestServer Library 2 | ================== 3 | 4 | This simple library was designed to enable your Arduino to respond to RESTful resource requests via Ethernet. Unlike most existing RESTful libraries for the Arduino, RestServer enables you to describe your own resources and to define how the Arduino should respond to each resource request rather than automatically map resources to pin numbers on the Arduino. 5 | 6 | The intent of this design approach is to embed knowledge about the resources available on a given Arduino locally on the device itself. This local knowledge enables the Arduino to inform client devices about the resources are available, along with relevant information about the resources such as resource range and types of requests supported. 7 | 8 | For more information about how this library works [check out the wiki](https://github.com/julioterra/Arduino_Rest_Server/wiki) 9 | 10 | @author Julio Terra 11 | @modified 12/25/12 by Julio 12 | @version 1.0.2 13 | 14 | 15 | License 16 | ======= 17 | 18 | The MIT License (MIT) 19 | Copyright © Julio Terra, http://www.julioterra.com 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /config_rest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011, Julio Terra 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | */ 23 | 24 | // RestSever: RESTfull request Library for Arduino. 25 | // Atmega328 required due to RAM requirements 26 | 27 | #ifndef __config_rest__ 28 | #define __config_rest__ 29 | 30 | #if defined(ARDUINO) && ARDUINO >= 100 31 | #include "Arduino.h" 32 | #else 33 | #include "WProgram.h" 34 | #endif 35 | 36 | #define REQUEST_MAX_LENGTH 75 // maximum length of incoming requests 37 | #define NAME_LENGTH 20 // maximum length of service names 38 | #define TIMEOUT_INTERVAL 2000 39 | 40 | /************************************************************************** 41 | DO NOT MAKE CHANGES BELOW THIS MARKER... 42 | ...unless you know what you are doing 43 | 44 | The data structures and constants below are not intended to be update, unless you 45 | are planning to change the functionality of the RestServer library. 46 | **************************************************************************/ 47 | 48 | // data structure that holds range information for each resource description 49 | typedef struct resource_range_t { 50 | int min; 51 | int max; 52 | }; 53 | 54 | // data structure that holds resource descriptions defined by users 55 | typedef struct resource_description_t { 56 | char name [NAME_LENGTH]; // name of resource 57 | boolean post_enabled; // flag that notes whether resource supports post requests 58 | resource_range_t range; // resource range 59 | }; 60 | 61 | typedef struct resource_t { 62 | char name [NAME_LENGTH]; // name of resource 63 | boolean post_enabled; // flag that notes whether resource supports post requests 64 | resource_range_t range; // resource range 65 | int state; // holds current state of resource 66 | boolean get; // get request flag for this resource 67 | boolean post; // post request flag for this resource 68 | }; 69 | 70 | // constants related to end of line and end of header sequences, and div characters 71 | #define DIV_ELEMENTS 4 // number of DIV_ELEMENTS available 72 | #define EOL_LENGTH 2 // number of characters in end of line sequence (EOL) 73 | #define EOH_LENGTH 4 // number of characters in end of header sequence (EOH) 74 | 75 | #endif // #endif __config_rest__ 76 | -------------------------------------------------------------------------------- /examples/Restful_Server_Ethernet/Restful_Server_Ethernet.pde: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | RestServer Ethernet Example 3 | 4 | This sketch is an example of how to use the RestSever working with the 5 | Ethernet library. The RestLibrary is a simple library that enables the 6 | Arduino to read and respond to incoming requests that are structured as 7 | restfull requests. 8 | 9 | For more information check out the GitHub repository at: 10 | https://github.com/julioterra/Arduino_Rest_Server/wiki 11 | 12 | Sketch and library created by Julio Terra - November 20, 2011 13 | http://www.julioterra.com/journal 14 | 15 | Ethernet code was based on example created on 18 Dec 2009 by David A. Mellis and 16 | modified on 4 Sep 2010 by Tom Igoe 17 | 18 | **********************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define SERVICES_COUNT 10 26 | #define CRLF "\r\n" 27 | 28 | // Enter a MAC address and IP address for your Arduino below. 29 | // The IP address will be dependent on your local network: 30 | byte mac[] = {0x90, 0xA2, 0xDA, 0x00, 0x68, 0xF8}; 31 | byte ip[] = {192,168,2,200}; 32 | byte gateway[] = {192,168,2,1}; 33 | byte subnet[] = {255,255,0,0}; 34 | 35 | // Start a TCP server on port 7999 36 | EthernetServer server(7999); 37 | 38 | // Create instance of the RestServer 39 | RestServer request_server = RestServer(Serial); 40 | 41 | // input and output pin assignments 42 | int service_get_pins [] = {A0, A1, A2, A3, A4, A5}; 43 | int service_set_pins [] = {3,5,6,9}; 44 | 45 | // method that register the resource_descriptions with the request_server 46 | // it is important to define this array in its own method so that it will 47 | // be discarted from the Arduino's RAM after the registration. 48 | void register_rest_server() { 49 | resource_description_t resource_description [SERVICES_COUNT] = {{"output_1", false, {0, 1024}}, 50 | {"output_2", false, {0, 1024}}, {"output_3", false, {0, 1024}}, 51 | {"output_4", false, {0, 1024}}, {"output_5", false, {0, 1024}}, 52 | {"output_6", false, {0, 1024}}, {"input_1", true, {0, 255}}, 53 | {"input_2", true, {0, 255}}, {"input_3", true, {0, 255}}, 54 | {"input_4", true, {0, 255}} }; 55 | request_server.register_resources(resource_description, SERVICES_COUNT); 56 | } 57 | 58 | void setup() { 59 | // start the Ethernet connection and the server: 60 | Ethernet.begin(mac, ip, gateway, subnet); 61 | server.begin(); 62 | 63 | // initialize input and output pins 64 | for(int i = 0; i < 6; i++) { pinMode(service_get_pins[i], INPUT); } 65 | for(int i = 0; i < 4; i++) { pinMode(service_set_pins[i], OUTPUT); } 66 | 67 | // register resources with resource_server 68 | register_rest_server(); 69 | } 70 | 71 | void loop() { 72 | // listen for incoming clients 73 | EthernetClient client = server.available(); 74 | 75 | // CONNECTED TO CLIENT 76 | if (client) { 77 | while (client.connected()) { 78 | 79 | // get request from client, if available 80 | if (request_server.handle_requests(client)) { 81 | read_data(); 82 | write_data(); 83 | request_server.respond(); // tell RestServer: ready to respond 84 | } 85 | 86 | // send data to client, when ready 87 | if (request_server.handle_response(client)) break; 88 | } 89 | // give the web browser time to receive the data and close connection 90 | delay(1); 91 | client.stop(); 92 | } 93 | } 94 | 95 | 96 | void read_data() { 97 | int pin_array_number = 0; 98 | for (int j = 0; j < SERVICES_COUNT; j++) { 99 | if (!request_server.resource_post_enabled(j)) { 100 | request_server.resource_set_state(j, analogRead(service_get_pins[pin_array_number])); 101 | pin_array_number++; 102 | } 103 | } 104 | } 105 | 106 | void write_data() { 107 | int pin_array_number = 0; 108 | for (int j = 0; j < SERVICES_COUNT; j++) { 109 | if (request_server.resource_post_enabled(j)) { 110 | analogWrite(service_set_pins[pin_array_number], request_server.resource_get_state(j)); 111 | pin_array_number++; 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /examples/Restful_Server_Ethernet_DHCP/Restful_Server_Ethernet_DHCP.ino: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | RestServer Ethernet Example 3 | 4 | This sketch is an example of how to use the RestSever working with the 5 | Ethernet library. The RestLibrary is a simple library that enables the 6 | Arduino to read and respond to incoming requests that are structured as 7 | restfull requests. 8 | 9 | For more information check out the GitHub repository at: 10 | https://github.com/julioterra/Arduino_Rest_Server/wiki 11 | 12 | Sketch and library created by Julio Terra - November 20, 2011 13 | http://www.julioterra.com/journal 14 | 15 | Ethernet code was based on example created on 18 Dec 2009 by David A. Mellis and 16 | modified on 4 Sep 2010 by Tom Igoe 17 | 18 | **********************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define SERVICES_COUNT 10 26 | #define CRLF "\r\n" 27 | 28 | // Enter a MAC address and IP address for your Arduino below. 29 | // The IP address will be dependent on your local network: 30 | byte mac[] = {0x90, 0xA2, 0xDA, 0x00, 0x68, 0xF8}; 31 | 32 | // Start a TCP server on port 7999 33 | EthernetServer server(7999); 34 | 35 | // Create instance of the RestServer 36 | RestServer request_server = RestServer(Serial); 37 | 38 | // input and output pin assignments 39 | int service_get_pins [] = {A0, A1, A2, A3, A4, A5}; 40 | int service_set_pins [] = {3,5,6,9}; 41 | 42 | // method that register the resource_descriptions with the request_server 43 | // it is important to define this array in its own method so that it will 44 | // be discarted from the Arduino's RAM after the registration. 45 | void register_rest_server() { 46 | resource_description_t resource_description [SERVICES_COUNT] = {{"output_1", false, {0, 1024}}, 47 | {"output_2", false, {0, 1024}}, {"output_3", false, {0, 1024}}, 48 | {"output_4", false, {0, 1024}}, {"output_5", false, {0, 1024}}, 49 | {"output_6", false, {0, 1024}}, {"input_1", true, {0, 255}}, 50 | {"input_2", true, {0, 255}}, {"input_3", true, {0, 255}}, 51 | {"input_4", true, {0, 255}} }; 52 | request_server.register_resources(resource_description, SERVICES_COUNT); 53 | request_server.set_debug_code(true); 54 | } 55 | 56 | void setup() { 57 | Serial.begin(9600); 58 | // start the Ethernet connection and the server: 59 | if (Ethernet.begin(mac) == 0) { 60 | Serial.println(F("ERROR: ethernet")); 61 | } else { 62 | Serial.print(F("ethernet: connected\n - ip: ")); 63 | Serial.println(Ethernet.localIP()); 64 | server.begin(); 65 | } 66 | 67 | // initialize input and output pins 68 | for(int i = 0; i < 6; i++) { pinMode(service_get_pins[i], INPUT); } 69 | for(int i = 0; i < 4; i++) { pinMode(service_set_pins[i], OUTPUT); } 70 | 71 | // register resources with resource_server 72 | register_rest_server(); 73 | } 74 | 75 | void loop() { 76 | // listen for incoming clients 77 | EthernetClient client = server.available(); 78 | 79 | // CONNECTED TO CLIENT 80 | if (client) { 81 | while (client.connected()) { 82 | 83 | // get request from client, if available 84 | if (request_server.handle_requests(client)) { 85 | read_data(); 86 | write_data(); 87 | request_server.respond(); // tell RestServer: ready to respond 88 | } 89 | 90 | // send data to client, when ready 91 | if (request_server.handle_response(client)) break; 92 | } 93 | // give the web browser time to receive the data and close connection 94 | delay(1); 95 | client.stop(); 96 | } 97 | } 98 | 99 | 100 | void read_data() { 101 | int pin_array_number = 0; 102 | for (int j = 0; j < SERVICES_COUNT; j++) { 103 | if (!request_server.resource_post_enabled(j)) { 104 | request_server.resource_set_state(j, analogRead(service_get_pins[pin_array_number])); 105 | pin_array_number++; 106 | } 107 | } 108 | } 109 | 110 | void write_data() { 111 | int pin_array_number = 0; 112 | for (int j = 0; j < SERVICES_COUNT; j++) { 113 | if (request_server.resource_post_enabled(j)) { 114 | analogWrite(service_set_pins[pin_array_number], request_server.resource_get_state(j)); 115 | pin_array_number++; 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map for RestServer 3 | 4 | ######################## 5 | # 1. Datatypes (KEYWORD1) 6 | 7 | resource_range_t KEYWORD1 8 | resource_description_t KEYWORD1 9 | resource_t KEYWORD1 10 | RestServer KEYWORD1 11 | 12 | ######################## 13 | # 2. Methods and Functions (KEYWORD2) 14 | 15 | register_resources KEYWORD2 16 | set_callback KEYWORD2 17 | set_post_with_get KEYWORD2 18 | set_json_lock KEYWORD2 19 | get_server_state KEYWORD2 20 | handle_requests KEYWORD2 21 | respond KEYWORD2 22 | handle_response KEYWORD2 23 | resource_get_state KEYWORD2 24 | resource_set_state KEYWORD2 25 | resource_post_enabled KEYWORD2 26 | resource_requested KEYWORD2 27 | resource_updated KEYWORD2 28 | 29 | ######################## 30 | # 3. Constants (LITERAL1) 31 | 32 | LISTENING LITERAL1 33 | READ_VERB LITERAL1 34 | READ_RESOURCE LITERAL1 35 | PARSE LITERAL1 36 | PROCESS LITERAL1 37 | RESPOND LITERAL1 38 | RESET LITERAL1 39 | REQUEST_MAX_LENGTH LITERAL1 40 | NAME_LENGTH LITERAL1 41 | TIMEOUT_INTERVAL LITERAL1 42 | -------------------------------------------------------------------------------- /rest_server.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | Copyright © Julio Terra, http://www.julioterra.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | and associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 9 | is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 15 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | RestSever: RESTfull request Library for Arduino. 21 | Atmega328 required due to RAM requirements 22 | */ 23 | 24 | #include "rest_server.h" 25 | 26 | /******************************************************** 27 | PUBLIC METHODS 28 | Methods that provide the API for the RestServer. These methods are the 29 | only ones that are avaiable from outside of the RestServer class. 30 | 31 | ********************************************************/ 32 | 33 | RestServer::RestServer(Print &_serial) : debug_log(_serial) { 34 | request = Message(); 35 | div_chars[0] = '/'; div_chars[1] = ' '; div_chars[2] = '='; div_chars[3] = '&'; 36 | eol_sequence[0] = '\r'; eol_sequence[1] = '\n'; 37 | eoh_sequence[0] = '\r'; eoh_sequence[1] = '\n'; eoh_sequence[2] = '\r'; eoh_sequence[3] = '\n'; 38 | server_options = 0 | CALLBACK; 39 | debug_code = false; 40 | server_state == RESET; 41 | } 42 | 43 | void RestServer::register_resources(resource_description_t *_resources_descriptions, int _resources_count){ 44 | resources_count = _resources_count; 45 | 46 | resources = (resource_t*) malloc(sizeof(resource_t) * int(resources_count)); 47 | if (resources) memset(resources, 0, sizeof(sizeof(resource_t) * int(resources_count))); 48 | 49 | for (int i = 0; i < int(resources_count); i++) { 50 | for (int k = 0; k < NAME_LENGTH; k ++) resources[i].name[k] = _resources_descriptions[i].name[k]; 51 | resources[i].post_enabled = _resources_descriptions[i].post_enabled; 52 | resources[i].range.min = _resources_descriptions[i].range.min; 53 | resources[i].range.max = _resources_descriptions[i].range.max; 54 | resources[i].state = 0; 55 | } 56 | prepare_for_next_client(); 57 | } 58 | 59 | 60 | void RestServer::set_callback(boolean _flag) { 61 | _flag ? (server_options = server_options | CALLBACK) : (server_options &= ~CALLBACK); 62 | if (debug_code) debug_log.print(F("[set_callback] updated server_options: ")); 63 | if (debug_code) debug_log.println(request_options, BIN); 64 | } 65 | 66 | void RestServer::set_post_with_get(boolean _flag) { 67 | _flag ? (server_options = server_options | POST_WITH_GET) : (server_options &= ~POST_WITH_GET); 68 | if (debug_code) debug_log.print(F("[set_post_with_get] updated server_options: ")); 69 | if (debug_code) debug_log.println(request_options, BIN); 70 | } 71 | 72 | void RestServer::set_json_lock(boolean _flag) { 73 | _flag ? (server_options = server_options | JSON_LOCK) : (server_options &= ~JSON_LOCK); 74 | if (debug_code) debug_log.print(F("[set_json_lock] updated server_options: ")); 75 | if (debug_code) debug_log.println(request_options, BIN); 76 | } 77 | 78 | void RestServer::set_debug_code(boolean _flag) { 79 | debug_code = _flag; 80 | } 81 | 82 | 83 | int RestServer::get_server_state() { 84 | return server_state; 85 | } 86 | 87 | boolean RestServer::handle_requests(Stream &_client) { 88 | if (_client.available()) { 89 | start_timer(); 90 | read_request(_client.read()); 91 | } 92 | parse_request(); 93 | process(); 94 | if (server_state == PROCESS) return true; 95 | else return false; 96 | } 97 | 98 | void RestServer::respond() { 99 | if (server_state == PROCESS) server_state = RESPOND; 100 | 101 | // debug code 102 | if (debug_code) debug_log.println("respond method call received\n"); 103 | 104 | } 105 | 106 | boolean RestServer::handle_response(Stream &_client) { 107 | send_response(_client); 108 | check_timer(); 109 | prepare_for_next_client(); 110 | if (server_state == LISTENING) return true; 111 | else return false; 112 | } 113 | 114 | int RestServer::resource_get_state(char *resource_name) { 115 | for (int i = 0; i < int(resources_count); i++) { 116 | if (strcmp(resources[i].name, resource_name) == 0) { 117 | return resources[i].state; 118 | } 119 | } 120 | } 121 | 122 | int RestServer::resource_get_state(int resource_num) { 123 | return resources[resource_num].state; 124 | } 125 | 126 | void RestServer::resource_set_state(char *resource_name, int new_state) { 127 | for (int i = 0; i < int(resources_count); i++) { 128 | if (strcmp(resources[i].name, resource_name) == 0) { 129 | resources[i].state = constrain(new_state, 130 | resources[i].range.min, 131 | resources[i].range.max); 132 | } 133 | } 134 | } 135 | 136 | void RestServer::resource_set_state(int resource_num, int new_state) { 137 | resources[resource_num].state = constrain(new_state, 138 | resources[resource_num].range.min, 139 | resources[resource_num].range.max); 140 | } 141 | 142 | boolean RestServer::resource_post_enabled(char *resource_name) { 143 | for (int i = 0; i < int(resources_count); i++) { 144 | if (strcmp(resources[i].name, resource_name) == 0) { 145 | return resources[i].post_enabled; 146 | } 147 | } 148 | } 149 | 150 | boolean RestServer::resource_post_enabled(int resource_num) { 151 | return resources[resource_num].post_enabled; 152 | } 153 | 154 | boolean RestServer::resource_requested(char* resource_name) { 155 | for (int i = 0; i < int(resources_count); i++) { 156 | if (strcmp(resources[i].name, resource_name) == 0) { 157 | return resources[i].get; 158 | } 159 | } 160 | } 161 | 162 | boolean RestServer::resource_requested(int resource_num) { 163 | return resources[resource_num].get; 164 | } 165 | 166 | boolean RestServer::resource_updated(char* resource_name){ 167 | for (int i = 0; i < int(resources_count); i++) { 168 | if (strcmp(resources[i].name, resource_name) == 0) { 169 | return resources[i].post; 170 | } 171 | } 172 | } 173 | 174 | boolean RestServer::resource_updated(int resource_num) { 175 | return resources[resource_num].post; 176 | } 177 | 178 | 179 | /******************************************************** 180 | INITIALIZATION METHODS 181 | ********************************************************/ 182 | 183 | void RestServer::prepare_for_next_client() { 184 | if (server_state == RESET) { 185 | request.clear(); 186 | 187 | timeout_start_time = 0; 188 | 189 | post_read_state = POST_NOT_PROCESSED; 190 | post_length_expected = 0; 191 | post_length_actual = 0; 192 | 193 | request_options = B00000000; 194 | for (byte i = 0; i < int(resources_count); i++) { 195 | resources[i].get = false; 196 | resources[i].post = false; 197 | } 198 | server_state = LISTENING; 199 | } 200 | } 201 | 202 | void RestServer::start_timer() { 203 | if ((server_state == LISTENING) && (request.length == 0)) { 204 | timeout_start_time = millis(); 205 | server_state = READ_VERB; 206 | } 207 | } 208 | 209 | void RestServer::check_timer() { 210 | if ((millis() - timeout_start_time > TIMEOUT_INTERVAL) && (request.length > 0)) { 211 | server_state = RESET; 212 | 213 | // debug code 214 | if (debug_code) debug_log.println("response timeout - reset timer"); 215 | 216 | } 217 | } 218 | 219 | /******************************************************** 220 | READ METHODS 221 | ********************************************************/ 222 | void RestServer::read_request(char new_char) { 223 | // debug code 224 | if (debug_code) Serial.print(new_char); 225 | 226 | if (server_state == READ_VERB) get_verb(new_char); 227 | 228 | else if (server_state == READ_RESOURCE) { 229 | if (request_type == GET_REQUESTS) read_get_requests(new_char); 230 | else if (request_type == POST_REQUESTS) read_post_requests(new_char); 231 | if (request.length == REQUEST_MAX_LENGTH-1) server_state = PARSE; 232 | } 233 | } 234 | 235 | void RestServer::get_verb(char new_char) { 236 | if (server_state == READ_VERB) { 237 | request.add(new_char); 238 | 239 | int match_index = request.match_string("GET ", request.length-4); 240 | if (match_index == NO_MATCH) match_index = request.match_string("POST ", request.length-5); 241 | 242 | // if a match is found then process the request 243 | if (match_index != NO_MATCH) { 244 | // set the type of request that was found 245 | if (request.msg[0] == 'G') request_type = GET_REQUESTS; 246 | else request_type = POST_REQUESTS; 247 | 248 | server_state = READ_RESOURCE; 249 | request.clear(); 250 | 251 | // debug code 252 | if (debug_code) debug_log.println("\nrestful request received"); 253 | } 254 | } 255 | } 256 | 257 | void RestServer::read_get_requests(char new_char) { 258 | if (request_type == GET_REQUESTS) { 259 | request.add(new_char); 260 | if (new_char == eol_sequence[EOL_LENGTH-1]) { 261 | // check for full end sequence, if match found change server_state and remove end seq 262 | int msg_end_index = request.match_string(eol_sequence, request.length-EOL_LENGTH); 263 | if (msg_end_index != NO_MATCH) { 264 | request.slice(0, request.length-EOL_LENGTH); 265 | 266 | // remove unused content from end of request (after second space) 267 | msg_end_index = request.find(' ', check_start_single(0)); 268 | if (msg_end_index != NO_MATCH) request.slice(0, msg_end_index); 269 | 270 | server_state = PARSE; 271 | } 272 | } 273 | } 274 | } 275 | 276 | void RestServer::read_post_requests(char new_char) { 277 | if (request_type == POST_REQUESTS) { 278 | if (post_read_state == POST_NOT_PROCESSED) { 279 | if(add_char_and_match(new_char, "Length: ") == true) post_read_state = POST_LENGTH_FOUND; 280 | } 281 | 282 | else if (post_read_state == POST_LENGTH_FOUND) { 283 | if ((match_div_char(new_char) || match_eol_char(new_char)) && request.length > 0) { 284 | post_length_expected = request.to_i(0, request.length-1); 285 | if (post_length_expected != NO_MATCH) post_read_state = POST_LENGTH_READY; 286 | request.clear(); 287 | } 288 | else request.add(new_char); 289 | } 290 | 291 | else if (post_read_state == POST_LENGTH_READY) { 292 | if (match_eoh_sequence(new_char) == true) post_read_state = POST_READ; 293 | } 294 | 295 | else if (post_read_state == POST_READ) { 296 | request.add(new_char); 297 | post_length_actual++; 298 | if (post_length_expected <= post_length_actual) server_state = PARSE; 299 | } 300 | } 301 | } 302 | 303 | boolean RestServer::match_eoh_sequence(char new_char) { 304 | for (byte i = 0; i < EOH_LENGTH - 1; i++) request.msg[i] = request.msg[i+1]; 305 | request.msg[EOH_LENGTH - 1] = new_char; 306 | 307 | if(strncmp(request.msg, eoh_sequence, EOH_LENGTH) == 0) { 308 | request.clear(); 309 | return true; 310 | } 311 | else return false; 312 | } 313 | 314 | boolean RestServer::add_char_and_match(char new_char, char *_match_string) { 315 | byte match_string_length = byte(strlen(_match_string)); 316 | for (byte i = 0; i < int(match_string_length) - 1; i++) request.msg[i] = request.msg[i+1]; 317 | request.msg[int(match_string_length) - 1] = new_char; 318 | 319 | if(strncmp(request.msg, _match_string, match_string_length) == 0) { 320 | request.clear(); 321 | return true; 322 | } 323 | else return false; 324 | } 325 | 326 | 327 | /******************************************************** 328 | PARSE METHODS 329 | ********************************************************/ 330 | void RestServer::parse_request() { 331 | if (server_state == PARSE) { 332 | int start_index = 0; 333 | 334 | // if JSON_LOCK is set then make response in json format 335 | if ((server_options & JSON_LOCK) == JSON_LOCK) { 336 | request_options = request_options | JSON_FORMAT; 337 | 338 | if (debug_code) debug_log.print(F("[parse_request] updated request_options: ")); 339 | if (debug_code) debug_log.println(request_options, BIN); 340 | } 341 | 342 | // Check for root request 343 | int match_index = request.match_string("/", start_index); 344 | if (match_index != NO_MATCH && request.length == 1) { 345 | 346 | // debug code 347 | if (debug_code) debug_log.println(F("root request received")); 348 | 349 | for (int i = 0; i < int(resources_count); i++) resources[i].get = true; 350 | server_state = PROCESS; 351 | return; 352 | } 353 | 354 | // Check if this is a resource information request 355 | match_index = request.match_string("/resource_info", start_index); 356 | if (match_index != NO_MATCH) { 357 | request_options = request_options | RESOURCE_REQ; 358 | server_state = RESPOND; 359 | return; 360 | } 361 | 362 | // see if this is a json request 363 | match_index = request.match_string("/json", start_index); 364 | if (match_index != NO_MATCH) { 365 | start_index = match_index + 1; 366 | if (match_div_char(request.msg[start_index]) || (request.length <= 6)) { 367 | request_options = request_options | JSON_FORMAT; 368 | if (request.length <= 6) { 369 | for (int i = 0; i < int(resources_count); i++) resources[i].get = true; 370 | } 371 | } 372 | } 373 | 374 | // see if an /all request is present 375 | match_index = request.match_string("/all", start_index); 376 | if (match_index != NO_MATCH) { 377 | start_index = match_index + 1; 378 | if (match_div_char(request.msg[start_index]) || (request.length <= 5)) { 379 | for (int i = 0; i < int(resources_count); i++) resources[i].get = true; 380 | } 381 | } 382 | 383 | // look for individual service/resource requests 384 | parse_resources(); 385 | 386 | server_state = PROCESS; 387 | } 388 | } 389 | 390 | /* parse_resources() 391 | Processes the services/resources that are part of a request. This method walks 392 | through each element from a request and attempts to match each one with the 393 | available services on the Arduino. Based on these matches this method also 394 | updates the services_xxx_requested and services_set_updated arrays, which identify what 395 | services were requested or updated by the current request. 396 | Accepts: n/a 397 | Returns: n/a 398 | */ 399 | void RestServer::parse_resources() { 400 | int next_start_pos = 0; 401 | boolean processing_request = true; 402 | 403 | while(processing_request == true) { 404 | 405 | // re-initializing the start and end position of current element 406 | int cur_start_pos = next_start_pos; 407 | next_start_pos = next_element(cur_start_pos); 408 | 409 | // if next_start_pos returns a NO_MATCH then we have reached end of resource request 410 | if (next_start_pos == NO_MATCH) { 411 | next_start_pos = request.length - 1; 412 | processing_request = false; 413 | } 414 | 415 | // loop through each resource/service name to look for a match 416 | for (int i = 0; i < int(resources_count); i++) { 417 | int match_index = service_match(i, cur_start_pos); 418 | if (match_index != NO_MATCH) { 419 | next_start_pos = match_index; 420 | if (next_start_pos >= request.length) break; 421 | } 422 | } 423 | } 424 | } 425 | 426 | /* service_match(int, int, int) 427 | Checks the element at the current location in the request message against 428 | one of the available services on the Arduino. If a SET service is matched 429 | then this method also attempts to read a service state. 430 | Accepts: an integer that identifies whether the service type being checked is 431 | a get service (0), or a set service (1); the position of the specific service 432 | to be checked within the get or set services arrays; and, the position of the 433 | current element within the request char array. 434 | Returns: returns location of the next element within the request, if a service 435 | is found; otherwise, returns NO_MATCH 436 | */ 437 | int RestServer::service_match(int _r_index, int _start_pos) { 438 | // check that start pos is not a div char, and that it is smaller than the request's length 439 | _start_pos = check_start(_start_pos); 440 | if (_start_pos == NO_MATCH) return NO_MATCH; 441 | int match_index = NO_MATCH; 442 | 443 | // get current resource name and try to match to current request element 444 | match_index = request.match_string(resources[_r_index].name, _start_pos); 445 | 446 | if (match_index != NO_MATCH) { 447 | resources[_r_index].get = true; 448 | 449 | if (resources[_r_index].post_enabled) { 450 | if (request_type == POST_REQUESTS || ((server_options & POST_WITH_GET) != 0)) { 451 | match_index = state_match(_r_index, (match_index + 1)); 452 | } 453 | } 454 | } 455 | return match_index; 456 | } 457 | 458 | /* state_match(int, int, int) 459 | Checks the element at the current location in the request message to determine if 460 | it is a state message. If state message is found it reads the message into the 461 | service_set_state array. 462 | Accepts: an integer that identifies whether the service type being checked is 463 | a get service (0), or a set service (1); the position of the specific service 464 | to be checked within the get or set services arrays; and, the position of the 465 | current element within the request char array. 466 | Returns: returns location of the next element within the request, if a state message 467 | is found; otherwise, returns NO_MATCH 468 | */ 469 | int RestServer::state_match(int _r_index, int _start_pos) { 470 | // check if for a state message (a number following an UPDATE-capable service) 471 | int new_number = check_for_state_msg(_start_pos); 472 | 473 | // if match exists, then (1) set updated array to true, (2) update state array 474 | if (new_number != NO_MATCH) { 475 | resources[_r_index].post = true; 476 | resources[_r_index].state = constrain(new_number, 477 | resources[_r_index].range.min, 478 | resources[_r_index].range.max); 479 | 480 | // check the position of the next element 481 | _start_pos = next_element(_start_pos); 482 | if (_start_pos == NO_MATCH) _start_pos = request.length; 483 | } 484 | return _start_pos; 485 | } 486 | 487 | /* check_for_state_msg(int) 488 | Checks if element at current location is a state message. Currently, only positive integers 489 | state messages are supported. 490 | Accepts: index where to start searching for state message. 491 | Returns: integer that reflects state, if one is found; otherwise, NO_MATCH is returned. 492 | */ 493 | int RestServer::check_for_state_msg(int _start) { 494 | // check that current start location is not a div character 495 | _start = check_start(_start); 496 | if (_start == NO_MATCH || _start > request.length-1) return NO_MATCH; 497 | 498 | // search for end position of current element and adjust as required 499 | int end_index = next_element(_start); 500 | if (end_index == NO_MATCH) end_index = request.length - 1; 501 | else end_index -= 1; // move end index to location before the division character 502 | 503 | // try to convert current element into a number by calling request.to_i method 504 | int new_num = request.to_i(_start, end_index); 505 | 506 | return new_num; 507 | } 508 | 509 | /* next_element(int) 510 | Looks for the next restful service element by searching for element division characters 511 | specified in the the div_chars array. 512 | Accepts: index where to start searching for next element within the request. 513 | Returns: index position of the next div element if one is found, otherwise, returns NO_MATCH. 514 | */ 515 | int RestServer::next_element(int _start) { 516 | _start = check_start(_start); 517 | if (_start == NO_MATCH) return NO_MATCH; 518 | // if (_start >= request.length) return NO_MATCH; 519 | int match_index = NO_MATCH; 520 | 521 | // loop through each element of div_chars array to search for a match in the request 522 | for (int i = 0; i < DIV_ELEMENTS; i ++ ) { 523 | int new_index = request.find(div_chars[i], _start); 524 | 525 | // if match is found then update the match_index if... 526 | if (new_index != NO_MATCH) { 527 | // ... match_index equals NO_MATCH, or new_index is smaller then match_index 528 | if (match_index == NO_MATCH || (new_index < match_index)) match_index = new_index; 529 | } 530 | } 531 | return match_index; 532 | } 533 | 534 | 535 | 536 | /******************************************************** 537 | PROCESS METHODS 538 | ********************************************************/ 539 | void RestServer::process() { 540 | if (server_state == PROCESS) { 541 | 542 | boolean service_active = false; 543 | for (int i = 0; i < int(resources_count); i++) { 544 | if (resources[i].get || resources[i].post) service_active = true; 545 | } 546 | 547 | // Update process state if callback is turned off, or no services have been requested or updated 548 | if ((server_options & CALLBACK) == 0 || !service_active) server_state = RESPOND; 549 | 550 | // debug code 551 | if (debug_code) debug_log.println("waiting for respond method call"); 552 | 553 | } 554 | } 555 | 556 | 557 | 558 | /******************************************************** 559 | RESPONSE METHODS 560 | ********************************************************/ 561 | void RestServer::send_response(Stream &_client) { 562 | if (server_state == RESPOND) { 563 | // Serial.println("__START__"); 564 | 565 | // handle resource info/description requests 566 | if ((request_options & RESOURCE_REQ) == RESOURCE_REQ) { 567 | print_resource_description(_client); 568 | 569 | // handle standard GET and POST requests 570 | } else { 571 | // handle requests in JSON format 572 | if ((request_options & JSON_FORMAT) == JSON_FORMAT) { 573 | print_json(_client); 574 | } 575 | // handle HTML requests 576 | else if (((request_options & JSON_FORMAT) == 0)) { 577 | print_html(_client); 578 | } 579 | } 580 | 581 | // Serial.println("__END__"); 582 | 583 | server_state = RESET; 584 | } 585 | } 586 | 587 | void RestServer::print_html(Stream &_client) { 588 | if ((request_options & JSON_FORMAT) == 0) { 589 | print_flash_string(PSTR("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"), _client); 590 | 591 | for(int i = 0; i < int(resources_count); i++) { 592 | if (resources[i].get || resources[i].post) { 593 | _client.print(resources[i].name); 594 | print_flash_string(PSTR(": "), _client); 595 | _client.print(resources[i].state); 596 | print_flash_string(PSTR("
\r\n"), _client); 597 | } 598 | } 599 | print_form(_client); 600 | print_flash_string(PSTR("\r\n\r\n\r\n"), _client); 601 | } 602 | } 603 | 604 | void RestServer::print_json(Stream &_client) { 605 | print_flash_string(PSTR("HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n"), _client); 606 | 607 | // determine number of of resources that have been requested (via get or post methods) 608 | byte resources_get_post_count = 0; 609 | for(int i = 0; i < int(resources_count); i++) { 610 | if (resources[i].get || resources[i].post) resources_get_post_count ++; 611 | } 612 | 613 | print_flash_string(PSTR("[\r\n"), _client); 614 | for(int i = 0; i < int(resources_count); i++) { 615 | if (resources[i].get || resources[i].post) { 616 | print_flash_string(PSTR("{\r\n\"resource_name\":\""), _client); 617 | _client.print(resources[i].name); 618 | print_flash_string(PSTR("\",\r\n\"state\":"), _client); 619 | _client.print(resources[i].state); 620 | print_flash_string(PSTR("\r\n}"), _client); 621 | if (i < (int(resources_get_post_count) - 1)) { 622 | print_flash_string(PSTR(","), _client); 623 | } 624 | print_flash_string(PSTR("\r\n"), _client); 625 | } 626 | } 627 | print_flash_string(PSTR("]\r\n\r\n\r\n"), _client); 628 | } 629 | 630 | void RestServer::print_resource_description(Stream &_client) { 631 | print_flash_string(PSTR("HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n"), _client); 632 | 633 | print_flash_string(PSTR("[\r\n"), _client); 634 | for(byte i = 0; i < int(resources_count); i++) { 635 | print_flash_string(PSTR("{\r\n\"resource_name\":\""), _client); 636 | _client.print(resources[i].name); 637 | print_flash_string(PSTR("\",\r\n\"post_enabled\":\""), _client); 638 | if (resources[i].post_enabled) print_flash_string(PSTR("true"), _client); 639 | else print_flash_string(PSTR("false"), _client); 640 | print_flash_string(PSTR("\",\r\n\"range\":{\"min\":"), _client); 641 | _client.print(resources[i].range.min); 642 | print_flash_string(PSTR(",\"max\":"), _client); 643 | _client.print(resources[i].range.max); 644 | print_flash_string(PSTR("}\r\n}"), _client); 645 | if (i < (int(resources_count) - 1)) print_flash_string(PSTR(","), _client); 646 | print_flash_string(PSTR("\r\n"), _client); 647 | } 648 | print_flash_string(PSTR("]\r\n\r\n\r\n"), _client); 649 | } 650 | 651 | void RestServer::print_form(Stream &_client) { 652 | if ((request_options & JSON_FORMAT) == 0) { 653 | print_flash_string(PSTR("
Update State
\r\n"), _client); 654 | print_flash_string(PSTR("
"), _client); 662 | 663 | for(byte i = 0; i < int(resources_count); i++) { 664 | if (resources[i].get && resources[i].post_enabled) { 665 | _client.print(resources[i].name); 666 | print_flash_string(PSTR(":
\r\n"), _client); 669 | } 670 | } 671 | 672 | print_flash_string(PSTR("
"), _client); 673 | } 674 | } 675 | 676 | void RestServer::print_flash_string(PGM_P string_const, Stream &_client) { 677 | char cur_char; 678 | while ((cur_char = pgm_read_byte(string_const++)) != 0) { 679 | _client.print(cur_char); 680 | } 681 | } 682 | 683 | 684 | 685 | /******************************************************** 686 | HELPER METHODS (low-level) 687 | Methods that provide support for other methods from various different 688 | categories listed above. 689 | /********************************************************/ 690 | 691 | /* check_start(int) 692 | Checks the char at current position in char array to determine if it is a div char; 693 | if so, then it moves the current position forward by one and then checks again. 694 | Accepts: index where to look for div char with char array. 695 | Returns: integer that reflects adjusted index position within char array; if end of 696 | array is reached during this process then it returns NO_MATCH 697 | */ 698 | int RestServer::check_start(int _start) { 699 | if (check_start_single(_start) == _start + 1) return check_start_single(_start + 1); 700 | return _start; 701 | } 702 | 703 | /* check_start_single(int) 704 | Checks the char at current position in char array to determine if it is a div char; 705 | if so, then it moves the current position forward by one. 706 | Accepts: index where to look for div char with char array. 707 | Returns: integer that reflects adjusted index position within char array; if end of 708 | array is reached during this process then it returns NO_MATCH 709 | */ 710 | int RestServer::check_start_single(int _start) { 711 | // check if start pos is equal to or greater then request length return NO_MATCH 712 | if (_start >= request.length) return NO_MATCH; 713 | 714 | if (match_div_char(request.msg[_start])) return _start + 1; 715 | else return _start; 716 | } 717 | 718 | // check if _new_char matches any resource div characters (from div_chars array) 719 | boolean RestServer::match_div_char(char _new_char) { 720 | for (byte i = 0; i < DIV_ELEMENTS; i ++ ) { 721 | if (_new_char == div_chars[i]) return true; 722 | } 723 | return false; 724 | } 725 | 726 | // check if _new_char matches any end of line characters (from eol_sequence array) 727 | boolean RestServer::match_eol_char(char _new_char) { 728 | for (byte i = 0; i < EOL_LENGTH; i ++ ) { 729 | if (_new_char == eol_sequence[i]) return true; 730 | } 731 | return false; 732 | } -------------------------------------------------------------------------------- /rest_server.h: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | Copyright © Julio Terra, http://www.julioterra.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | and associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 9 | is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 15 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | RestSever: RESTfull request Library for Arduino. 21 | Atmega328 required due to RAM requirements 22 | */ 23 | 24 | #ifndef __Restful_server_h__ 25 | #define __Restful_server_h__ 26 | 27 | #if defined(ARDUINO) && ARDUINO >= 100 28 | #include "Arduino.h" 29 | #else 30 | #include "WProgram.h" 31 | #endif 32 | 33 | #include 34 | #include 35 | #include 36 | #include "config_rest.h" 37 | #include "utility/message.h" 38 | 39 | class RestServer { 40 | 41 | private: 42 | // resource_active_t contains following resource description and state information 43 | resource_t *resources; 44 | 45 | byte resources_count; // holds the number of resources 46 | 47 | Message request; // holds the resource requests and temporary data during POST req. reading process 48 | 49 | boolean debug_code; 50 | Print &debug_log; 51 | 52 | byte server_state; // holds state of RestServer 53 | byte server_options; // each bit holds a separate server option including: 54 | #define CALLBACK 1 // 1. callback enabled (B00000001) 55 | #define POST_WITH_GET 2 // 2. post with get and form not displayed (B00000010) 56 | #define JSON_LOCK 4 // 3. set all responses to json format (B00000100) 57 | 58 | byte request_type; // holds request type (GET or POST) 59 | byte request_options; // each bit holds a separate request option including: 60 | #define JSON_FORMAT 1 // 2. return json format (B00000001) 61 | #define RESOURCE_REQ 2 // 1. resource description request (B00000010) 62 | 63 | byte post_read_state; // holds current state when reading post requests 64 | #define POST_NOT_PROCESSED 0 // 0: post not yet processed 65 | #define POST_LENGTH_FOUND 1 // 1: post length found 66 | #define POST_LENGTH_READY 2 // 2: post length has been retrieved 67 | #define POST_READ 3 // 3: post data has been read 68 | byte post_length_expected; // holds the expected post length based on header 69 | byte post_length_actual; // holds actual post length 70 | 71 | // constants that identify the number and type of requests that are 72 | // supported by the RestServer library 73 | #define GET_REQUESTS 0 // set GET_REQUESTS equals 0 74 | #define POST_REQUESTS 1 // set POST_REQUESTS equals 1 75 | 76 | // constant used to identify non-matches in several methods from RestServer and Message classes 77 | #define NO_MATCH -1 78 | 79 | char eol_sequence[EOL_LENGTH + 1]; // request end sequence match chars 80 | char eoh_sequence[EOH_LENGTH + 1]; // request end sequence match chars 81 | char div_chars[DIV_ELEMENTS + 1]; // element division chars 82 | 83 | long timeout_start_time; // start time for timeout timer 84 | 85 | // methods for reading request 86 | void read_request(char); 87 | void get_verb(char); 88 | void read_get_requests(char); 89 | void read_post_requests(char); 90 | boolean match_div_char(char); 91 | boolean match_eol_char(char); 92 | boolean match_eoh_sequence(char); 93 | boolean add_char_and_match(char, char*); 94 | 95 | // methods for parsing request 96 | void parse_request(); 97 | void parse_resources(); 98 | int service_match(int, int); 99 | int state_match(int, int); 100 | int next_element(int); 101 | int check_for_state_msg(int); 102 | int check_start(int); 103 | int check_start_single(int); 104 | 105 | // methods for processing data in prepartion to respond to request 106 | void process(); 107 | 108 | // methods for responding to request 109 | void send_response(Stream &_client); 110 | void print_json(Stream &_client); 111 | void print_html(Stream &_client); 112 | void print_resource_description(Stream &_client); 113 | void print_form(Stream &_client); 114 | void print_flash_string(PGM_P, Stream &_client); 115 | 116 | // methods for initializing data after and managing timeout 117 | void prepare_for_next_client(); 118 | void start_timer(); 119 | void check_timer(); 120 | 121 | public: 122 | // server_state constants 123 | #define LISTENING 0 124 | #define READ_VERB 1 125 | #define READ_RESOURCE 2 126 | #define PARSE 3 127 | #define PROCESS 4 128 | #define RESPOND 5 129 | #define RESET 6 130 | 131 | // initialization and state inquiry methods 132 | RestServer(Print &_serial); // initializes the RestServer, pass the Serial port for debugging 133 | void register_resources(resource_description_t *, int); 134 | void set_callback(boolean); // sets callback option 135 | void set_post_with_get(boolean); // sets get with post option 136 | void set_json_lock(boolean); // sets json lock option 137 | void set_debug_code(boolean); // prints debug messages 138 | int get_server_state(); // returns current server state 139 | 140 | // client handling methods 141 | boolean handle_requests(Stream &_client); // reads request from client 142 | void respond(); // notifies rest_server when ready to respond 143 | boolean handle_response(Stream &_client); // sends response to client 144 | 145 | // resource state getter and setter methods 146 | int resource_get_state(char*); // get state of named resource 147 | int resource_get_state(int); // get state of numbered resource 148 | void resource_set_state(char*, int); // set state of named resource 149 | void resource_set_state(int, int); // set state of numbered resource 150 | 151 | boolean resource_post_enabled(char*); // identify if named resource can be updated 152 | boolean resource_post_enabled(int); // identify if numbered resource can be updated 153 | 154 | boolean resource_requested(char*); // identify if named resource was requested 155 | boolean resource_requested(int); // identify if numbered resource was requested 156 | boolean resource_updated(char*); // identify if named resource's state was updated 157 | boolean resource_updated(int); // identify if numbered resource's state was update 158 | 159 | }; 160 | 161 | #endif // endif __Restful_server_h__ 162 | -------------------------------------------------------------------------------- /utility/message.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011, Julio Terra 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | */ 23 | 24 | // Message: simple string-related Library for Arduino. 25 | // Developed in conjunction to RestServer library 26 | 27 | #include "message.h" 28 | 29 | /* Message() 30 | * Initializes the msg and length variables for new instance of the 31 | * Message class. 32 | */ 33 | Message::Message () { 34 | for (int i = 0; i < REQUEST_MAX_LENGTH; i++) { msg[i] = '\0'; } 35 | length = 0; 36 | } 37 | 38 | /* add(char) 39 | * Accepts a char and adds it to the end of the message. If the request 40 | * has reached its maximum length then the new letter are ignored. 41 | */ 42 | void Message::add (char _new_char) { 43 | if (length >= REQUEST_MAX_LENGTH) return; 44 | msg[length] = _new_char; 45 | length += 1; 46 | } 47 | 48 | /* slice(char*, int, int) 49 | * accepts a string (char array) and a start index and an end index. 50 | * it changes the existing array to only contain the information 51 | * within the start and end index. 52 | */ 53 | void Message::slice (int _start_index, int _end_index) 54 | { 55 | if (_end_index > length) _end_index = length; 56 | if (_start_index > _end_index || _start_index < 0 || _end_index < 0) return; 57 | 58 | for (int i = 0; i < length; i++) 59 | { 60 | int shift_start_index = _start_index + i; 61 | if (shift_start_index < _end_index) msg[i] = msg[shift_start_index]; 62 | else msg[i] = '\0'; 63 | } 64 | length = strlen(msg); 65 | } 66 | 67 | /* Message::clear() 68 | * clears the full Message::msg array and resets the Message::msg_index to 0. 69 | */ 70 | void Message::clear(){ 71 | for (int i = 0; i < REQUEST_MAX_LENGTH; i++) { msg[i] = '\0'; } 72 | length = 0; 73 | } 74 | 75 | /* Message::find(char, char*, int) 76 | * accepts a search character, a source string and a start index. 77 | * returns the index of next occurence of a char after the start 78 | * index. If the char does not exist then it returns a -1. 79 | */ 80 | int Message::find(char _char_search, int _start_index) { 81 | if (length <= _start_index) return -1; 82 | for (int i = _start_index; i < length; i++) { 83 | if (_char_search == msg[i]) return i; 84 | } 85 | return -1; 86 | } 87 | 88 | /* Message::match_string(char*, char*, int) 89 | * accepts a search string, a source string and a start index. 90 | * searches the source string for a match with the search string 91 | * starting at the start index location only. 92 | * if match is found it returns the index number of the last 93 | * matching element. Otherwise, it returns -1. 94 | */ 95 | int Message::match_string(char* _char_search, int _start_index) { 96 | int length_search = strlen(_char_search); 97 | int location = -1; 98 | 99 | if (length < length_search + _start_index) return -1; 100 | 101 | for (int i = 0; i < length_search; i++) { 102 | if (_char_search[i] == msg[i+_start_index]) location = i + _start_index; 103 | else return -1; 104 | } 105 | 106 | return location; 107 | } 108 | 109 | /* Message::to_i(char*, int, int) 110 | * accepts a msg string, a start and an end index. Then it attempts 111 | * to convert the character string into digits. If any of the 112 | * characters are not numbers then the method returns a -1. 113 | * otherwise the method returns the number as an int. 114 | */ 115 | int Message::to_i(int _start_index, int end_index) { 116 | int return_num = 0; 117 | int reverse_counter = (end_index - _start_index); 118 | 119 | for(int i = 0; i <= (end_index - _start_index); i++) { 120 | char cur_char = msg[i+_start_index]; 121 | if (int(cur_char) < 48 || int(cur_char) > 57) { return -1; } 122 | int mult = 1; 123 | for(int j = 0; j < reverse_counter; j++) { mult = mult * 10; } 124 | return_num += (int(cur_char)-48) * mult; 125 | reverse_counter--; 126 | } 127 | return return_num; 128 | } -------------------------------------------------------------------------------- /utility/message.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011, Julio Terra 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | */ 23 | 24 | // Message: simple string-related Library for Arduino. 25 | // Developed in conjunction to RestServer library 26 | 27 | #ifndef __RestMessage_h__ 28 | #define __RestMessage_h_ 29 | 30 | #if defined(ARDUINO) && ARDUINO >= 100 31 | #include "Arduino.h" 32 | #else 33 | #include "WProgram.h" 34 | #endif 35 | 36 | #include "config_rest.h" 37 | #include 38 | 39 | class Message { 40 | 41 | public: 42 | char msg [REQUEST_MAX_LENGTH]; 43 | 44 | int length; 45 | int max_length; 46 | 47 | Message(); 48 | void add(char); 49 | void clear(); 50 | void slice(int, int); 51 | int find(char, int); 52 | int match_string(char*, int); 53 | int to_i(int, int); 54 | 55 | }; 56 | 57 | #endif --------------------------------------------------------------------------------