#include #include #include "pico/stdlib.h" #include "lwip/apps/httpd.h" #include "lwip/ip4_addr.h" #include "pico/cyw43_arch.h" #include "hardware/watchdog.h" #include "hardware/flash.h" #include "hardware/sync.h" #include "pico/multicore.h" #include "parse_keys.h" #include "dhcpserver.h" #include "websocket.h" #include "server.h" uint8_t led_code = 0; static uint8_t ip[4]; static uint8_t mask[4]; static uint8_t gw[4]; static absolute_time_t lastCheck; static absolute_time_t lastActive; static net_config wifi; static bool clientmode = false; static bool config_loaded = false; static bool bad_config = false; static bool reboot = false; static void *current_connection; // list of SSI variable names for lwIP SSI handler const char * __not_in_flash("httpd") ssi_tags[] = { "ssid", "pass", "host", "ip0", "ip1", "ip2", "ip3", "mask0", "mask1", "mask2", "mask3", "gw0", "gw1", "gw2", "gw3", "dhcp" }; const char * ip_parts[] = { "ip0=", "ip1=", "ip2=", "ip3=", "mask0=", "mask1=", "mask2=", "mask3=", "gw0=", "gw1=", "gw2=", "gw3=" }; #define NPARTS ( sizeof( ip_parts )/ sizeof(ip_parts[0]) ) static const tCGI cgi_handlers[] = { { "/reboot.cgi", reboot_cgi }, }; void run_http_server() { if (cyw43_arch_init_with_country(CYW43_COUNTRY_USA)) { printf("failed to initialize\n"); return; } // fall back to AP mode unless Wi-Fi connection is successful clientmode = false; // read network config from flash config_loaded = net_config_load(&wifi); if ( config_loaded ){ printf("read config file\n"); cyw43_arch_enable_sta_mode(); // set hostname if saved, otherwise use default setting struct netif *n = &cyw43_state.netif[CYW43_ITF_STA]; if (wifi.host) { netif_set_hostname(n, wifi.host); } else { netif_set_hostname(n, DEFAULTHOST); } // use manual IP address if configured if (wifi.manual) { dhcp_release_and_stop(n); netif_set_addr(n, &(wifi.ip), &(wifi.mask), &(wifi.gw)); // inform mDNS of hostname dhcp_inform(n); } netif_set_up(n); // attempt to connect to saved Wi-Fi network if(cyw43_arch_wifi_connect_timeout_ms(wifi.ssid, wifi.pass, CYW43_AUTH_WPA2_AES_PSK, 10000)){ printf("failed to connect to %s\n", wifi.ssid); } else{ // Wi-Fi client connection successful clientmode = true; } } if (!clientmode) { // did not connect to Wi-Fi, launch AP mode cyw43_arch_enable_ap_mode(DEFAULTHOST, DEFAULTPASS, CYW43_AUTH_WPA2_AES_PSK); // set AP address to 192.168.0.1 ip4_addr_t ip, mask, gw; IP4_ADDR(&ip, 192, 168, 0, 1); IP4_ADDR(&mask, 255, 255, 255, 0); IP4_ADDR(&gw, 192, 168, 0, 1); netif_set_ipaddr(netif_default, &ip); netif_set_up(netif_default); // init DHCP server to hand out addresses to connected clients dhcp_server_t dhcp; dhcp_server_init(&dhcp, &gw, &mask); printf("launched in AP mode\n"); } // start the HTTP web server for keyboard input and config page httpd_init(); http_set_cgi_handlers(cgi_handlers, 1); for (size_t i = 0; i < LWIP_ARRAYSIZE(ssi_tags); i++) { LWIP_ASSERT("tag too long for LWIP_HTTPD_MAX_TAG_NAME_LEN", strlen(ssi_tags[i]) <= LWIP_HTTPD_MAX_TAG_NAME_LEN); } http_set_ssi_handler(ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags)); printf("HTTP server initialized\n"); // start the websocket server ws_server_init(); ws_set_open_handler(ws_open_handler); ws_set_receive_handler(ws_receive_handler); printf("Websocket server initialized\n"); // start a watchdog timer with 8 seconds so it can reboot if it disconnects watchdog_enable(8000,1); if (clientmode) { // signal that device is in client mode with solid LED on cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN,1); while(true) { if ( absolute_time_diff_us(lastCheck, get_absolute_time()) > 5000000) { // check if Wi-Fi is still connected every 5 seconds and // feed the watchdog timer if it is to prevent reboot if ( cyw43_tcpip_link_status(&cyw43_state, CYW43_ITF_STA) == 3 ) { lastCheck = get_absolute_time(); // update watchdog if reboot has not been requested if(!reboot){ watchdog_update(); } } } } } else { lastActive = get_absolute_time(); //initialize activity timer static int led = 1; while(true) { if (absolute_time_diff_us(lastCheck, get_absolute_time()) > 1000000) { // signal device is in AP mode with blinking LED led = !led; cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, led); lastCheck = get_absolute_time(); if ( config_loaded && (absolute_time_diff_us(lastActive, lastCheck) > 600000000)) { // if there is no keyboard activity for 10 minutes // reboot to try to (re)connect to Wi-Fi reboot = true; } // update watchdog unless reboot has been requested if(!reboot) { watchdog_update(); } } } } } // lwIP cgi handler for reboots initiated from the web const char * reboot_cgi(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) { // turn on reboot flag to prevent watchdog from updating reboot = true; return "/success.html"; } // write configuration data to flash memory void net_config_write(net_config *config) { // core 0 must have its TinyUSB interrupts disabled to allow flash writes multicore_lockout_start_blocking(); // blank out memory for staging config save uint8_t buf[FLASH_PAGE_SIZE]; memset(buf, 0x00, FLASH_PAGE_SIZE); memcpy(buf, config, sizeof(net_config)); // turn off core 1 web server interrupts to allow flash writes uint32_t interrupts = save_and_disable_interrupts(); // erase page where save will go in flash flash_range_erase(FLASH_TARGET_OFFSET, FLASH_SECTOR_SIZE); // write new config to flash flash_range_program(FLASH_TARGET_OFFSET, buf, FLASH_PAGE_SIZE); // restore interrupts on both core 1 and core 0 restore_interrupts(interrupts); multicore_lockout_end_blocking(); } // read network config from flash and loads to program memory bool net_config_load(net_config *config) { const uint8_t *data = (const uint8_t *) (XIP_BASE+FLASH_TARGET_OFFSET); memcpy(config, data, sizeof(net_config)); return ( (config->header == STARTFILE) && (config->footer == ENDFILE) ); } // lwIP SSI handler for loading variables into HTML pages uint16_t __time_critical_func(ssi_handler)(int iIndex, char *pcInsert, int iInsertLen) { size_t printed; switch (iIndex) { case 0: //ssid - network config SSID if (config_loaded && wifi.ssid) { printed = snprintf(pcInsert, iInsertLen, "value=\"%s\"", wifi.ssid); } else { printed = 0; } break; case 1: //pass - network config password if (config_loaded && wifi.pass) { printed = snprintf(pcInsert, iInsertLen, "value=\"%s\"", wifi.pass); } else { printed = 0; } break; case 2: //host - network config hostname if (config_loaded && wifi.host) { printed = snprintf(pcInsert, iInsertLen, "value=\"%s\"", wifi.host); } else { printed = 0; } break; case 3: case 4: case 5: case 6: //ip0-3 - network config manual IP address parts if (config_loaded && wifi.ip.addr) { printed = snprintf(pcInsert, iInsertLen, "value=\"%d\"", ip4_addr_get_byte(&(wifi.ip), iIndex-3)); } else { printed = 0; } break; case 7: case 8: case 9: case 10: //mask0-3 - network config manual netmask parts if (config_loaded && wifi.mask.addr) { printed = snprintf(pcInsert, iInsertLen, "value=\"%d\"", ip4_addr_get_byte(&(wifi.mask), iIndex-7)); } else { printed = 0; } break; case 11: case 12: case 13: case 14: //gw0-3 - network config manual gateway parts if (config_loaded && wifi.gw.addr) { printed = snprintf(pcInsert, iInsertLen, "value=\"%d\"", ip4_addr_get_byte(&(wifi.gw), iIndex-11)); } else { printed = 0; } break; case 15: // dhcp - network config DHCP client enabled/disabled if ((!config_loaded) || !(wifi.manual) ){ printed = snprintf(pcInsert, iInsertLen, "checked"); } else { printed = 0; } break; default: //undefined tag printed = 0; break; } LWIP_ASSERT("illegal length returned", printed <= 0xFFFF); return (uint16_t)printed; } // lwIP handler for POST requests err_t httpd_post_begin(void *connection, const char *uri, const char * http_request, u16_t http_request_len, int content_len, char *response_uri,u16_t response_uri_len, u8_t *post_auto_wnd) { if (memcmp(uri, "/wifi.cgi", 9) == 0 && current_connection != connection) { // initiate post request to wifi.cgi current_connection = connection; // default page is wifi config page snprintf(response_uri, response_uri_len, "/wifi.shtml"); *post_auto_wnd = 1; return ERR_OK; } return ERR_VAL; } // lwIP handler for receiving POST data err_t httpd_post_receive_data(void *connection, struct pbuf *p) { if (current_connection == connection) { // clear all values in manual network settings memset(ip, 0, sizeof(ip)); memset(mask, 0, sizeof(mask)); memset(gw, 0, sizeof(gw)); bad_config = false; wifi.manual = true; char buf[MAX_POST_PARAM_LEN]; char * value; // read parameter values value = find_post_param(p, "ssid=", buf, sizeof(buf)); if (value) { urldecode(value, wifi.ssid); } else { bad_config = true; } value = find_post_param(p, "pass=", buf, sizeof(buf)); if (value) { urldecode(value, wifi.pass); } else { wifi.pass[0]=0; } value = find_post_param(p, "host=", buf, sizeof(buf)); if (value) { urldecode(value, wifi.host); } else { wifi.host[0]=0; } value = find_post_param(p, "dhcp=", buf, sizeof(buf)); if (value) { wifi.manual = false; } else { for (int i=0; i< NPARTS; i++) { value = find_post_param(p, ip_parts[i], buf, sizeof(buf)); if (value) { int ip_part = atoi(value); if (ip_part > 255 || ip_part < 0) { bad_config = true; } else { if (strncmp(ip_parts[i], "ip", 2) == 0) { uint8_t part = atoi(&ip_parts[i][2]); ip[part] = (uint8_t) ip_part; } else if (strncmp(ip_parts[i], "gw", 2) == 0) { uint8_t part = atoi(&ip_parts[i][2]); gw[part] = (uint8_t) ip_part; } else if (strncmp(ip_parts[i], "mask", 4) == 0) { uint8_t part = atoi(&ip_parts[i][4]); mask[part] = (uint8_t) ip_part; } } } else { bad_config = true; } } } if (!bad_config) { // config is valid, prepare for saving to flash IP4_ADDR(&(wifi.ip), ip[0], ip[1], ip[2], ip[3]); if(!wifi.ip.addr) { wifi.ip.addr = IPADDR_NONE; } IP4_ADDR(&(wifi.mask), mask[0], mask[1], mask[2], mask[3]); if(!wifi.mask.addr) { wifi.mask.addr = IPADDR_NONE; } IP4_ADDR(&(wifi.gw), gw[0], gw[1], gw[2], gw[3]); if(!wifi.gw.addr) { wifi.gw.addr = IPADDR_NONE; } wifi.header = STARTFILE; wifi.footer = ENDFILE; config_loaded = true; watchdog_update(); // save configurations to flash net_config_write(&wifi); printf("wifi settings saved\n"); pbuf_free(p); return ERR_OK; } } pbuf_free(p); current_connection = NULL; return ERR_VAL; } // lwIP handler for end of POST request void httpd_post_finished(void *connection, char *response_uri, u16_t response_uri_len) { // return to Wi-Fi config page unless save was successful snprintf(response_uri, response_uri_len, "/wifi.shtml"); if (current_connection == connection) { snprintf(response_uri, response_uri_len, "/success.html"); } current_connection = NULL; } // return a value for a parameter from POST char *find_post_param(struct pbuf *p, const char *param, char *buf, size_t len) { size_t param_len = strlen(param); uint16_t param_pos = pbuf_memfind(p, param, param_len, 0); if (param_pos != 0xFFFF) { uint16_t value_pos = param_pos + param_len; uint16_t value_len = 0; uint16_t tmp; tmp = pbuf_memfind(p, "&", 1, value_pos); if (tmp != 0xFFFF) { value_len = tmp - value_pos; } else { value_len = p->tot_len - value_pos; } if (value_len > 0 && value_len < len) { char *value = (char *) pbuf_get_contiguous(p, buf, len, value_len, value_pos); if (value) { value[value_len]=0; return value; } } } return NULL; } const void ws_open_handler(struct ws_state * wss) { send_indicators(); } // handler for data received on websocket connection const void ws_receive_handler(uint8_t *data, uint16_t len) { if (strncmp(data, "K: ", 3)==0) { parse_key_list(&data[3]); } else if (strncmp(data, "M: ", 3) == 0) { parse_mouse_list(&data[3]); } lastActive= get_absolute_time(); } // save keyboard's LED indicator status to memory void set_indicator(uint8_t const* buffer) { led_code = *buffer; send_indicators(); } // read individual bits for indicator LED status and send over websocket to client void send_indicators(void){ char buf[16]=""; if (led_code & 1) { strcat(buf, "num,"); } if (led_code & 2) { strcat(buf, "caps,"); } if (led_code & 4) { strcat(buf, "scroll"); } ws_send_all(buf, strlen(buf)); } // take URL-formatted string from GET request and turn into regular string void urldecode(char *urlstring, char *decoded) { uint8_t conv; while(*urlstring) { if(*urlstring == '+') { *decoded=' '; } else if (*urlstring == '%') { urlstring++; sscanf(urlstring, "%02hhx", &conv); *decoded = conv; urlstring++; } else { *decoded = *urlstring; } urlstring++;; decoded++; } *decoded = '\0'; }