code clean up and added comments

This commit is contained in:
2025-05-30 09:17:55 -04:00
parent 5184d5c744
commit 94ea767285
8 changed files with 104 additions and 93 deletions
+8 -7
View File
@@ -18,24 +18,24 @@ cable to the host computer or other device.
On initial boot, the Raspberry Pi Pico W will enter Access Point mode with
SSID "picokb" and password "password". Connect to this network with another
device and open a browser window to [http://picokb](http://picokb).
device and open a browser window to http://192.168.0.1.
Go to the "Configure W-Fi" page and enter the SSID and password to the
network that you would like the Raspberry Pi Pico W to connect. Optionally,
also enter a hostname for the device and static IP information. Hit the "Save"
button to save the network information to device flash.
Browse back to the main UI page at [http://picokb](http://picokb) and press
Browse back to the main UI page at http://192.168.0.1 and press
"Reboot". The device should automatically connect to the network with the
saved information. You can then connect to the device using a web browser
at the device's assigned IP or hostname by accessing http://IP_ADDRESS or
http://HOSTNAME
http://HOSTNAME (http://picokb is no hostname is set).
If the Pico W fails to connect to the saved Wi-Fi network, it will once again
enter Access Point mode, and you can connect to the device directly to modify
the network information.
Alternatively, you can use the device directly in Access Point mode by
accessing the device at [http://picokb](http://picokb). The program is
accessing the device at http://192.168.0.1. The program is
set to reboot automatically to try to reconnect to the nextwork after 10
minutes of inactivity if no keyboard input is received during that time.
@@ -44,7 +44,7 @@ minutes of inactivity if no keyboard input is received during that time.
This software is distributed under the [GNU General Public License version
3](LICENSE), with the exception of the libraries in the following section.
## Attributions
## Credits
The project uses code from the following sources:
@@ -53,5 +53,6 @@ The project uses code from the following sources:
- [Raspberry Pi Pico SDK Examples](https://github.com/raspberrypi/pico-examples)
for a modified version of [pico_w/wifi/lwipopts_examples_common.h](lwipopts.h)
distributed under the BSD-3-Clause license
- [TinyUSB](https://github.com/hathach/tinyusb) for the base version of
[tusb_config.h](tusb_config.h) distributed under the MIT license
- [TinyUSB](https://github.com/hathach/tinyusb) for template versions of
[hid.c](hid.c), [tusb_config.h](tusb_config.h), and
[usb_descriptors.c/h](usb_descriptors.c) distributed under the MIT license
Binary file not shown.
+3 -35
View File
@@ -44,7 +44,6 @@ int run_hid_device(void) {
while (true) {
tud_task(); // tinyusb device task
//tud_cdc_write_flush();
}
return 0;
@@ -59,10 +58,11 @@ void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_
if (instance == 0 && report_type==HID_REPORT_TYPE_OUTPUT && report_id==REPORT_ID_KEYBOARD && bufsize==1) {
//received keyboard LED status, so update
//received keyboard indicator LED status, so update
set_indicator(buffer);
}
// forward USB report to CDC for debugging
char tempbuf[128];
size_t count;
if(bufsize>0) {
@@ -84,13 +84,7 @@ void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen)
{
// TODO not Implemented
(void) instance;
(void) report_id;
(void) report_type;
(void) buffer;
(void) reqlen;
// echo USB communication to CDC for debugging
char tempbuf[128];
size_t count;
if(reqlen>0) {
@@ -108,29 +102,3 @@ uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_t
return 0;
}
// Invoked when sent REPORT successfully to host
// Application can use this to send the next report
// Note: For composite reports, report[0] is report ID
/*
void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_t len)
{
(void) instance;
(void) len;
char tempbuf[128];
size_t count;
if(len>0) {
count = sprintf(tempbuf, "\nDevice sent report on interface %u (%u)\n", instance, len);
tud_cdc_write(tempbuf, count);
for(int i=0;i<len;i++){
count=sprintf(tempbuf,"%02X ",report[i]);
tud_cdc_write(tempbuf,count);
}
count = sprintf(tempbuf, "\n");
tud_cdc_write(tempbuf,count);
tud_cdc_write_flush();
}
}
*/
+6 -1
View File
@@ -9,17 +9,22 @@
int main() {
stdio_init_all();
// setup multicore processes
multicore_reset_core1();
multicore_launch_core1(core1_main);
// allow core 1 to pause core 0
// this is needed to allow saving network config to flash
multicore_lockout_victim_init();
// run USB HID device on core 0
run_hid_device();
}
void core1_main() {
// run HTTP services on core 1
run_http_server();
}
+20 -6
View File
@@ -7,19 +7,29 @@
#include "parse_keys.h"
#include "usb_descriptors.h"
// take a list of Javascript keys representing pressed keys and turn into
// a 8 byte USB boot keyboard report format
void parse_key_list(char * keys) {
uint8_t keypos = 2;
uint8_t keypos = 2; // bytes 2-7 are used for normal keys
uint8_t code = 0x00;
static unsigned char boot_report[8];
memset(boot_report, 0x00, 8);
// Javascript sends the list as comma delimited, so split into individual
// keys by splitting at commas
char * token = strtok(keys, ",");
while (token != NULL) {
code = parse_key_single(token);
if (code && keypos < 9) {
// add scan code to the boot report unless we have already added
// the 8th byte (6 scan codes) to the report
if (code && keypos < 8) {
boot_report[keypos] = code;
keypos++;
}
// if a scan code was not returned, it might be a modifier
// search for the correct modifier bit and add it to byte 0 of the
// USB boot keyboard report
if (code == 0x00) {
code = parse_mod(token);
if (code) {
@@ -29,6 +39,7 @@ void parse_key_list(char * keys) {
token = strtok(NULL, ",");
}
// print resulting HID boot keyboard report to CDC for debugging
printf("HID report: ");
for(int i=0; i<8; i++){
printf("%02X ",boot_report[i]);
@@ -38,24 +49,27 @@ void parse_key_list(char * keys) {
tud_hid_report(REPORT_ID_KEYBOARD, boot_report, 8);
}
uint8_t parse_key_single(char * key) {
//printf("single key: %s\n", key);
// take a single Javascript key code and convert it to USB HID scancode
// if it is a regular key, i.e. not a modifier key
uint8_t parse_key_single(char * key) {
// search keycode dictionary defined in parse_keys.h to see if it's a
// regular key
for (int i=0; i < NKEYS; i++) {
keycode_dict * keycode = &keytable[i];
if (strcmp(keycode->key, key) == 0){
//printf("key found");
return keycode->val;
}
}
// key not found in lookup table
return 0x00;
}
// parse modifier keys and turn them into the appropriate modifier bit code
uint8_t parse_mod(char * key) {
for (int i=0; i < NMODS; i++) {
keycode_dict * modcode = &modtable[i];
if (strcmp(modcode->key, key) == 0){
//printf("key found");
return modcode->val;
}
}
+5 -8
View File
@@ -9,6 +9,8 @@ uint8_t parse_mod(char * key);
typedef struct { char * key; uint8_t val; } keycode_dict;
// modifier key bit codes
#define KEY_MOD_LCTRL 0x01
#define KEY_MOD_LSHIFT 0x02
#define KEY_MOD_LALT 0x04
@@ -18,14 +20,7 @@ typedef struct { char * key; uint8_t val; } keycode_dict;
#define KEY_MOD_RALT 0x40
#define KEY_MOD_RMETA 0x80
/**
* Scan codes - last N slots in the HID report (usually 6).
* 0x00 if no key pressed.
*
* If more than N keys are pressed, the HID reports
* KEY_ERR_OVF in all slots to indicate this condition.
*/
// list of USB scan codes for individual keys
#define KEY_NONE 0x00 // No key pressed
#define KEY_ERR_OVF 0x01 // Keyboard Error Roll Over - used for all slots if too many keys are pressed ("Phantom key")
// 0x02 // Keyboard POST Fail
@@ -277,6 +272,7 @@ typedef struct { char * key; uint8_t val; } keycode_dict;
#define KEY_MEDIA_REFRESH 0xfa
#define KEY_MEDIA_CALC 0xfb
// dictionary converting Javascript key code strings to USB scan codes
static keycode_dict keytable[] = {
{"KeyA", KEY_A},
{"KeyB", KEY_B},
@@ -376,6 +372,7 @@ static keycode_dict keytable[] = {
{"WakeUp", KEY_POWER}
};
// dictionary for Javascript key code strings to USB keyboard modifier bits
static keycode_dict modtable[] = {
{"ControlLeft", KEY_MOD_LCTRL},
{"ShiftLeft", KEY_MOD_LSHIFT},
+56 -21
View File
@@ -26,6 +26,7 @@ static bool config_loaded = false;
static bool bad_config = false;
static bool reboot = false;
// list of SSI variable names for lwIP SSI handler
const char * __not_in_flash("httpd") ssi_tags[] = {
"num",
"caps",
@@ -56,48 +57,53 @@ static const tCGI cgi_handlers[] = {
};
void run_http_server() {
//sleep_ms(5000);
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
//cyw43_arch_lwip_begin();
// 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);
//cyw43_arch_lwip_end();
// 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) {
//start AP mode
// 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);
@@ -105,12 +111,13 @@ void run_http_server() {
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 server
// start the HTTP web server for keyboard input and config page
httpd_init();
http_set_cgi_handlers(cgi_handlers, 3);
for (size_t i = 0; i < LWIP_ARRAYSIZE(ssi_tags); i++) {
@@ -120,14 +127,19 @@ void run_http_server() {
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();
}
@@ -139,12 +151,16 @@ void run_http_server() {
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();
}
@@ -153,10 +169,11 @@ void run_http_server() {
}
}
// 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 ) {
//printf("Web input: %s\n", pcValue[i]);
parse_key_list(pcValue[i]);
}
}
@@ -167,6 +184,7 @@ const char * sendkeys_cgi (int iIndex, int iNumParams, char *pcParam[], char *pc
return "/success.html";
}
// lwIP cgi handler for form submission of network config to wifi.cgi
const char * save_wifi (int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) {
// clear all values in manual network settings
memset(ip, 0, sizeof(ip));
@@ -176,6 +194,7 @@ const char * save_wifi (int iIndex, int iNumParams, char *pcParam[], char *pcVal
bad_config = false;
wifi.manual = true;
// read parameter values
for (int i=0; i < iNumParams; i++) {
if (strcmp(pcParam[i], "ssid") == 0){
if (pcValue[i][0] == "\0") {
@@ -217,8 +236,10 @@ const char * save_wifi (int iIndex, int iNumParams, char *pcParam[], char *pcVal
}
if (bad_config) {
// config is invalid, so don't save and return to config page
return "/wifi.shtml";
} else {
// 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;
@@ -237,6 +258,7 @@ const char * save_wifi (int iIndex, int iNumParams, char *pcParam[], char *pcVal
watchdog_update();
// save configuratitons to flash
net_config_write(&wifi);
printf("wifi settings saved\n");
@@ -245,7 +267,9 @@ const char * save_wifi (int iIndex, int iNumParams, char *pcParam[], char *pcVal
}
}
// 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";
@@ -253,20 +277,28 @@ const char * reboot_cgi(int iIndex, int iNumParams, char *pcParam[], char *pcVal
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();
flash_range_erase(FLASH_TARGET_OFFSET, FLASH_SECTOR_SIZE);
flash_range_program(FLASH_TARGET_OFFSET, buf, FLASH_PAGE_SIZE);
restore_interrupts(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));
@@ -274,45 +306,46 @@ bool net_config_load(net_config *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
case 0: //num - numlock status
if (led_code & 1) {
printed = snprintf(pcInsert, iInsertLen, "on");
} else {
printed = snprintf(pcInsert, iInsertLen, "off");
}
break;
case 1: //caps
case 1: //caps - capslock status
if (led_code & 2) {
printed = snprintf(pcInsert, iInsertLen, "on");
} else {
printed = snprintf(pcInsert, iInsertLen, "off");
}
break;
case 2: //scroll
case 2: //scroll - scrollock status
if (led_code & 4) {
printed = snprintf(pcInsert, iInsertLen, "on");
} else {
printed = snprintf(pcInsert, iInsertLen, "off");
}
break;
case 3: //ssid
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
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
case 5: //host - network config hostname
if (config_loaded && wifi.host) {
printed = snprintf(pcInsert, iInsertLen, "value=\"%s\"", wifi.host);
} else {
@@ -322,7 +355,7 @@ uint16_t __time_critical_func(ssi_handler)(int iIndex, char *pcInsert, int iInse
case 6:
case 7:
case 8:
case 9: //ip0-3
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 {
@@ -332,7 +365,7 @@ uint16_t __time_critical_func(ssi_handler)(int iIndex, char *pcInsert, int iInse
case 10:
case 11:
case 12:
case 13: //mask0-3
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 {
@@ -342,14 +375,14 @@ uint16_t __time_critical_func(ssi_handler)(int iIndex, char *pcInsert, int iInse
case 14:
case 15:
case 16:
case 17: //gw0-3
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
case 18: // dhcp - network config DHCP client enabled/disabled
if ((!config_loaded) || !(wifi.manual) ){
printed = snprintf(pcInsert, iInsertLen, "checked");
} else {
@@ -364,10 +397,12 @@ uint16_t __time_critical_func(ssi_handler)(int iIndex, char *pcInsert, int iInse
return (uint16_t)printed;
}
// save keyboard's LED indicator status to memory
void set_indicator(uint8_t const* buffer) {
led_code = *buffer;
}
// turn URL-formatted string from GET request and turn into regular string
void urldecode(char *urlstring, char *decoded) {
uint8_t conv;
while(*urlstring) {
+6 -15
View File
@@ -123,11 +123,11 @@ uint8_t const * tud_descriptor_configuration_cb(uint8_t index)
char const* string_desc_arr [] =
{
(const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
"Raspberry Pi", // 1: Manufacturer
"Pico Web Keyboard", // 2: Product
"1234567890123456789", // 3: Serials, should use chip ID
"Pico Web Keyboard CDC", // 4: CC Interface
"TinyUSB Keyboard", // 5: HID Keyboard Interface
"Raspberry Pi", // 1: Manufacturer
"Pico Web Keyboard", // 2: Product
"1234567890123456789", // 3: Serials, should use chip ID
"Pico Web Keyboard CDC", // 4: CDC Interface
"TinyUSB Keyboard", // 5: HID Keyboard Interface
};
static uint16_t _desc_str[32];
@@ -181,15 +181,6 @@ uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid)
uint8_t const * tud_hid_descriptor_report_cb(uint8_t itf)
{
(void) itf;
//switch(itf) {
// case 0:
return desc_hid_report;
// case 1:
// return desc_nkro_report;
// case 2:
// return desc_media_report;
// default:
// return 0;
//}
return desc_hid_report;
}