#include #include "pico/stdlib.h" #include "hardware/uart.h" #include "hardware/gpio.h" #include "hardware/spi.h" #include "pico_wireless.hpp" #include "secrets.h" #include #include #define HTTP_PORT 80 #define HTTP_REQUEST_BUF_SIZE 2048 #define DNS_CLOUDFLARE IPAddress(1, 1, 1, 1) #define DNS_GOOGLE IPAddress(8, 8, 8, 8) #define USE_DNS DNS_CLOUDFLARE using namespace pimoroni; PicoWireless wireless; uint8_t r, g, b; uint8_t request_buf[HTTP_REQUEST_BUF_SIZE]; const std::string response_501 = "HTTP/1.1 501 Not Implemented\nContent-Length: 19\n\n501 Not Implemented"; enum http_request_method_t { GET, POST }; typedef std::string_view(*http_request_handler)(http_request_method_t method, std::string_view path, std::vector request_head, std::vector request_body); std::vector split(std::string_view str, std::string delim="\r\n") { std::vector result; size_t offset = 0; while (offset < str.size()) { const auto pos = str.find_first_of(delim, offset); // Emit an empty view even if two adjacent delimiters are found // this ensurs the HTTP "blank line" start of content is found result.emplace_back(str.substr(offset, pos - offset)); if (pos == std::string_view::npos) break; offset = pos + delim.length(); } return result; } uint32_t millis() { return to_us_since_boot(get_absolute_time()) / 1000; } bool wifi_connect(std::string network, std::string password, IPAddress dns_server, uint32_t timeout=10000) { printf("Connecting to %s...\n", network.c_str()); wireless.wifi_set_passphrase(network, password); uint32_t t_start = millis(); while(millis() - t_start < timeout) { if(wireless.get_connection_status() == WL_CONNECTED) { printf("Connected!\n"); wireless.set_dns(1, dns_server, 0); return true; } wireless.set_led(255, 0, 0); sleep_ms(500); wireless.set_led(0, 0, 0); sleep_ms(500); printf("...\n"); } return false; } int socket_accept(int8_t server_sock) { uint8_t new_sock = wireless.avail_server(server_sock); if(new_sock != server_sock && new_sock != 255) { return new_sock; } return -1; } uint8_t start_server(uint16_t http_port) { printf("Starting server...\n"); // Get a socket for our server uint8_t server_sock = wireless.get_socket(); wireless.start_server(http_port, server_sock); while(1){ uint8_t state = wireless.get_server_state(server_sock); if(state == LISTEN) break; } IPAddress ip; wireless.get_ip_address(ip); printf("Server listening on %s:%i\n", ip.to_string().c_str(), http_port); return server_sock; } bool poll_http_requests(uint8_t server_sock, http_request_handler request_handler, uint32_t timeout = 1000) { uint16_t request_length = 0; uint16_t avail_length = 0; uint32_t t_start = millis(); // Clear the request buffer memset(request_buf, 0, HTTP_REQUEST_BUF_SIZE); // Try to accept an incoming connection int client_sock = socket_accept(server_sock); if(client_sock == -1) return false; printf("Got client: %d\n", client_sock); // Any data? This *usually* seems to be available immediately, // so we can dispense with any ongoing polling and hope for the best. avail_length = wireless.avail_data(client_sock); if(avail_length == 0) return false; printf("Got request: %i bytes\n", avail_length); // Read the full response // Sometimes the bytes read is less than the bytes we request, so loop until we get the data we expect while(request_length < avail_length) { uint16_t read_length = avail_length; // Request the full buffer wireless.get_data_buf(client_sock, request_buf + request_length, &read_length); request_length += read_length; // Increment the response_length by the amount we actually read // Also check for timeouts here, too if(millis() - t_start >= timeout) break; } // Bail if we timed out. if(millis() - t_start >= timeout) { wireless.stop_client(client_sock); }; if(request_length > 0) { std::vector request = split(std::string_view((char *)request_buf, request_length)); std::vector request_body; std::vector request_detail = split(request[0], " "); http_request_method_t method; // Bail early on an invalid HTTP request if(request_detail[0].compare("POST") == 0) { method = POST; } else if (request_detail[0].compare("GET") == 0) { method = GET; } else { // PUT, DELETE, HEAD? // Return a 501 unimplemented wireless.send_data(client_sock, (const uint8_t *)response_501.data(), response_501.length()); wireless.stop_client(client_sock); return false; } // Get the request path so we can handle routes std::string_view path = request_detail[1]; // Scan for the blank line indicating content start for form post data auto body_start = std::find(request.begin(), request.end(), ""); // Split the body from the head (ow!) if(body_start != request.end()) { request_body = std::vector(body_start + 1, request.end()); request = std::vector(request.begin(), body_start); } std::string_view response_body = request_handler(method, path, request, request_body); std::string response_head = "HTTP/1.1 200 OK\nContent-Length: " + std::to_string(response_body.length()) + "\nContent-Type: text/html\n\n"; wireless.send_data(client_sock, (const uint8_t *)response_head.data(), response_head.length()); wireless.send_data(client_sock, (const uint8_t *)response_body.data(), response_body.length()); wireless.stop_client(client_sock); return true; } return false; } int main() { stdio_init_all(); wireless.init(); sleep_ms(500); printf("Firmware version Nina %s\n", wireless.get_fw_version()); if(!wifi_connect(NETWORK, PASSWORD, USE_DNS)) { return 0; } uint8_t server_sock = start_server(HTTP_PORT); g = 255; wireless.set_led(r, g, b); while(1) { // Handle any incoming HTTP requests poll_http_requests(server_sock, []( http_request_method_t request_method, std::string_view request_path, std::vector request_head, std::vector request_body) -> std::string_view { if(request_method == POST) { // Split the URL-encoded POST data and parse the RGB values std::vector post_data = split(request_body[0], "&"); for(auto &data : post_data) { // Must be at least 3 chars for "x=y" if(data.length() < 3) continue; // Must be in the form "x=y" if(data[1] != '=') continue; // "v" will be 0 on a parse failure, which isn't so bad. int v = atoi((const char *)(data.data() + 2)); switch(data[0]) { case 'r': r = v; printf("Got R: %i\n", r); break; case 'g': g = v; printf("Got G: %i\n", g); break; case 'b': b = v; printf("Got B: %i\n", b); break; } wireless.set_led(r, g, b); } } else { // GET if(request_path.compare("/hello") == 0) { return "Hello World!"; } } return "\
\ \ \ \ \
"; }); sleep_ms(10); } return 0; }