Files
webkeyboard/server.c
T
2025-07-19 11:18:30 -04:00

501 lines
13 KiB
C

#include <stdio.h>
#include <string.h>
#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 "server.h"
#include "parse_keys.h"
#include "dhcpserver.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[] = {
"num",
"caps",
"scroll",
"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[] = {
{ "/sendkeys.cgi", sendkeys_cgi },
{ "/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, 2);
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 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 GET requests to sendkeys.cgi
const char * sendkeys_cgi (int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) {
for (int i=0; i < iNumParams; i++) {
// search GET parameters for keys and forward for processing to USB
if (strcmp(pcParam[i], "keys") == 0 ) {
parse_key_list(pcValue[i]);
}
}
lastActive= get_absolute_time();
// send return page
return "/success.html";
}
// 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: //num - numlock status
if (led_code & 1) {
printed = snprintf(pcInsert, iInsertLen, "on");
} else {
printed = snprintf(pcInsert, iInsertLen, "off");
}
break;
case 1: //caps - capslock status
if (led_code & 2) {
printed = snprintf(pcInsert, iInsertLen, "on");
} else {
printed = snprintf(pcInsert, iInsertLen, "off");
}
break;
case 2: //scroll - scrollock status
if (led_code & 4) {
printed = snprintf(pcInsert, iInsertLen, "on");
} else {
printed = snprintf(pcInsert, iInsertLen, "off");
}
break;
case 3: //ssid - network config SSID
if (config_loaded && wifi.ssid) {
printed = snprintf(pcInsert, iInsertLen, "value=\"%s\"", wifi.ssid);
} else {
printed = 0;
}
break;
case 4: //pass - network config password
if (config_loaded && wifi.pass) {
printed = snprintf(pcInsert, iInsertLen, "value=\"%s\"", wifi.pass);
} else {
printed = 0;
}
break;
case 5: //host - network config hostname
if (config_loaded && wifi.host) {
printed = snprintf(pcInsert, iInsertLen, "value=\"%s\"", wifi.host);
} else {
printed = 0;
}
break;
case 6:
case 7:
case 8:
case 9: //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-6));
} else {
printed = 0;
}
break;
case 10:
case 11:
case 12:
case 13: //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-10));
} else {
printed = 0;
}
break;
case 14:
case 15:
case 16:
case 17: //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-14));
} else {
printed = 0;
}
break;
case 18: // 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;
}
// save keyboard's LED indicator status to memory
void set_indicator(uint8_t const* buffer) {
led_code = *buffer;
}
// 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';
}