8 Commits

Author SHA1 Message Date
kenji 84aa9a2e74 add Pico 2 W firmware 2025-08-18 08:09:28 -04:00
kenji cf17420973 update VID/PID/serial 2025-08-12 11:30:03 -04:00
kenji 692c5af8c9 code cleanup & fix compile with pico-sdk 2.2.0 2025-08-12 07:49:12 -04:00
kenji 4f313b3336 improve websocket implementation 2025-07-28 10:15:49 -04:00
kenji 99824ee2a6 fix mouse reports and improve timeout handling 2025-07-20 21:34:56 -04:00
kenji c7d70c669b send keys over WebSocket 2025-07-20 17:43:10 -04:00
kenji d25218898b Change Wi-Fi config to POST 2025-07-19 11:18:30 -04:00
kenji d3b12890bb add simple mouse controls 2025-07-19 07:05:28 -04:00
17 changed files with 2320 additions and 1427 deletions
+9 -2
View File
@@ -1,7 +1,10 @@
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
set(PROJECT webkeyboard) set(PROJECT webkeyboard)
set(PICO_SDK_PATH /home/kenji/programming/pico/c/pico-sdk) set(PICO_SDK_PATH /home/kenji/programming/pico/c/pico-sdk)
set(PICO_BOARD pico_w) if (NOT DEFINED PICO_BOARD)
set(PICO_BOARD pico_w)
endif()
#set(PICO_BOARD pico2_w)
include (${PICO_SDK_PATH}/external/pico_sdk_import.cmake) include (${PICO_SDK_PATH}/external/pico_sdk_import.cmake)
project(${PROJECT} C CXX ASM) project(${PROJECT} C CXX ASM)
@@ -33,10 +36,12 @@ target_sources(${PROJECT} PRIVATE
usb_descriptors.c usb_descriptors.c
parse_keys.c parse_keys.c
dhcpserver.c dhcpserver.c
websocket.c
) )
pico_enable_stdio_usb(${PROJECT} 1) pico_enable_stdio_usb(${PROJECT} 1)
pico_enable_stdio_uart(${PROJECT} 1)
set_target_properties(${PROJECT} PROPERTIES OUTPUT_NAME "${PROJECT}-${PICO_BOARD}")
pico_add_extra_outputs(${PROJECT}) pico_add_extra_outputs(${PROJECT})
@@ -47,6 +52,8 @@ target_link_libraries(${PROJECT}
pico_lwip_http pico_lwip_http
pico_stdlib pico_stdlib
pico_multicore pico_multicore
pico_mbedtls
tinyusb_board
tinyusb_device tinyusb_device
) )
+16 -13
View File
@@ -2,28 +2,31 @@
## About ## About
This project turns a Raspberry Pi Pico W into a web-based USB HID keyboard This project turns a Raspberry Pi Pico W or Raspberry Pi Pico 2 W into a
device. Users can access a webpage served over HTTP to enter keyboard input web-based USB HID keyboard device. Users can access a webpage served over HTTP
onto the host device of the Raspberry Pi Pico W. Input keys can be entered to enter keyboard input onto the host device of the Raspberry Pi Pico (2) W.
through a keyboard on the client device or by pressing on the keys on the Input keys can be entered through a keyboard on the client device or by
graphical interface served on the webpage. pressing on the keys on the graphical interface served on the webpage.
![Web interface for Web Keyboard](web-ui.jpg) ![Web interface for Web Keyboard](web-ui.jpg)
## Setup ## Setup
Download the webkeyboard.uf2 file from the latest [Installation and demo video](https://youtu.be/uORnxt5DLTw)
[release](https://git.kkozai.com/kenji/webkeyboard/releases) and flash
onto the Raspberry Pi Pico W by holding down the BOOTSEL button while plugging
into your computer so that it appears as a USB drive, then transfer the
firmware file onto the Pico W. After unmounting, connect the Raspberry Pi Pico
W with a micro USB cable to the host computer or other device.
On initial boot, the Raspberry Pi Pico W will enter Access Point mode with Download the appropriate firmware file for your board, either
webkeyboard-pico_w.uf2 or webkeyboard-pico2_w.uf2, from the latest
[release](https://git.kkozai.com/kenji/webkeyboard/releases) and flash
onto the Raspberry Pi Pico (2) W by holding down the BOOTSEL button while
plugging into your computer so that it appears as a USB drive, then transfer the
firmware file onto the Pico (2) W. After unmounting, connect the Raspberry Pi
Pico (2) W with a micro USB cable to the host computer or other device.
On initial boot, the Raspberry Pi Pico (2) W will enter Access Point mode with
SSID "picokb" and password "password". Connect to this network with another SSID "picokb" and password "password". Connect to this network with another
device and open a browser window to http://192.168.0.1. 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 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, network that you would like the Raspberry Pi Pico (2) W to connect. Optionally,
also enter a hostname for the device and static IP information. Hit the "Save" also enter a hostname for the device and static IP information. Hit the "Save"
button to save the network information to device flash. button to save the network information to device flash.
+125 -73
View File
@@ -48,12 +48,6 @@ div.blank {
opacity: 0; opacity: 0;
} }
div.info div {
font-size: 1.5em;
display: inline-block;
margin: 1em;
}
div#macros div { div#macros div {
width: 9em; width: 9em;
display: inline-block; display: inline-block;
@@ -89,8 +83,8 @@ div.menu a:hover {
<script> <script>
var keys = []; var keys = [];
var fetchqueue = []; var mouse = [];
var curLed = 0; var sendMouseInterval = null;
const indicator_list = [ const indicator_list = [
{id: "num", label: "NumLock"}, {id: "num", label: "NumLock"},
{id: "caps", label: "CapsLock"}, {id: "caps", label: "CapsLock"},
@@ -102,7 +96,7 @@ const macro_list = [
{id: "close", label: "ALT+F4", keys: ["AltLeft", "F4"]}, {id: "close", label: "ALT+F4", keys: ["AltLeft", "F4"]},
{id: "cycle", label: "ALT+Tab", keys: ["AltLeft", "Tab"]} {id: "cycle", label: "ALT+Tab", keys: ["AltLeft", "Tab"]}
]; ];
const key_list = [ const keyboard_list = [
[ [
{id: "Escape", label: "ESC", width: 2}, {id: "Escape", label: "ESC", width: 2},
{id: "F1", label: "F1"}, {id: "F1", label: "F1"},
@@ -223,15 +217,40 @@ const key_list = [
{id: "NumpadDecimal", label: "Del<br>."} {id: "NumpadDecimal", label: "Del<br>."}
], ],
]; ];
const mouse_list = [
[
{id: "", label: ""},
{id: "MouseUp", label: "<br>&uArr;", repeat: 1},
{id: "", label: ""}
],
[
{id: "MouseLeft", label: "<br>&lArr;", repeat: 1},
{id: "", label: ""},
{id: "MouseRight", label: "<br>&rArr;", repeat: 1}
],
[
{id: "", label: ""},
{id: "MouseDown", label: "<br>&dArr;", repeat: 1},
{id: "", label: ""}
],
[
{id: "MouseClickLeftt", label: "Left Click", width: 1.5},
{id: "MouseClickRight", label: "Right Click", width: 1.5}
],
];
window.setInterval("refreshIndicators();",2000);
window.addEventListener("keydown", onKeyDown, true);
window.addEventListener("keyup", onKeyUp, true);
window.onload = (event) => { window.onload = (event) => {
createMacros("macros"); createMacros("macros");
createKeys("keyboard"); createKeys("keyboard", keyboard_list);
createKeys("mouse", mouse_list);
createIndicators("indicators");
window.addEventListener("keydown", onKeyDown, true);
window.addEventListener("keyup", onKeyUp, true);
socket = new WebSocket("ws://" + window.location.hostname + ":8080/");
socket.onmessage = function (event) { updateLEDs(event.data); };
} }
function onKeyDown(event) { function onKeyDown(event) {
@@ -245,12 +264,20 @@ function onKeyDown(event) {
event.preventDefault(); event.preventDefault();
} }
function pressKey(code) { function pressKey(code, repeat=false) {
if (keys.includes(code)) { return; } if (code.indexOf("Mouse") >= 0) {
if (mouse.includes(code)) { return; }
mouse.push(code);
sendKeys(mouse, true);
} else{
if (keys.includes(code)) { return; }
keys.push(code);
sendKeys(keys);
}
keys.push(code); if ( sendMouseInterval == null && repeat) {
sendMouseInterval = setInterval( function() { sendKeys(mouse, true); }, 50 );
sendKeys(keys); }
} }
function onKeyUp(event) { function onKeyUp(event) {
@@ -264,21 +291,27 @@ function onKeyUp(event) {
event.preventDefault(); event.preventDefault();
} }
function releaseKey(code) { function releaseKey(code, repeat=false) {
if (keys.includes(code)) { if (code.indexOf("Mouse") >= 0) {
keys.splice(keys.indexOf(code),1); if (mouse.includes(code)) {
mouse.splice(mouse.indexOf(code),1);
}
sendKeys(mouse, true);
} else{
if (keys.includes(code)) {
keys.splice(keys.indexOf(code),1);
}
sendKeys(keys);
} }
sendKeys(keys); if (sendMouseInterval != null && repeat) {
clearInterval(sendMouseInterval);
sendMouseInterval = null;
}
} }
function sendKeys(curKeys, is_mouse) {
function sendKeys(curKeys) { // update highlighting of pressed keys
const url="sendkeys.cgi?keys=".concat(curKeys);
fetchqueue.push(url);
console.log(fetchqueue);
document.getElementById("downKeys")
.innerHTML = curKeys;
prev_keys = document.getElementsByClassName("pressed"); prev_keys = document.getElementsByClassName("pressed");
for (let key of curKeys) { for (let key of curKeys) {
keyDiv = document.getElementById(key); keyDiv = document.getElementById(key);
@@ -289,33 +322,24 @@ function sendKeys(curKeys) {
key.classList.remove("pressed"); key.classList.remove("pressed");
} }
} }
if (fetchqueue.length == 1) {
runQueue(); // send updated key or mouse status
if (socket.readyState == WebSocket.OPEN) {
if (is_mouse == true) {
socket.send("M: " + curKeys);
} else {
socket.send("K: " + curKeys);
}
} else if (socket.readyState == WebSocket.CLOSED) {
socket = new WebSocket("ws://" + window.location.hostname + ":8080/");
setTimeout( function () { sendKeys(curKeys, is_mouse); }, 10);
} else {
setTimeout( function () { sendKeys(curKeys, is_mouse); }, 10);
} }
} }
async function runQueue() { function createKeys(div_id, key_list) {
const url = fetchqueue[0]; let keyboardDiv = document.getElementById(div_id);
await fetch(url);
fetchqueue.shift();
if (fetchqueue.length > 0) {
runQueue();
}
}
function updateIndicators() {
let indicatorFrame = document.getElementById("indicatorFrame");
let indicatorDiv = document.getElementById("indicators");
indicatorDiv.innerHTML = indicatorFrame.contentDocument.body.innerHTML;
}
function refreshIndicators() {
let indicatorFrame = document.getElementById("indicatorFrame");
indicatorFrame.contentWindow.location.reload(true);
}
function createKeys(keyboard_id) {
let keyboardDiv = document.getElementById(keyboard_id);
for (let row of key_list) { for (let row of key_list) {
let newRow = document.createElement("div"); let newRow = document.createElement("div");
keyboardDiv.appendChild(newRow); keyboardDiv.appendChild(newRow);
@@ -325,14 +349,25 @@ function createKeys(keyboard_id) {
newDiv.id = key.id; newDiv.id = key.id;
newDiv.innerHTML = key.label; newDiv.innerHTML = key.label;
newDiv.className = "key"; newDiv.className = "key";
newDiv.addEventListener("touchstart", function (event) { if (key.repeat) {
event.preventDefault(); pressKey(key.id); }); newDiv.addEventListener("touchstart", function (event) {
newDiv.addEventListener("touchend", function (event) { event.preventDefault(); pressKey(key.id, true); });
event.preventDefault(); releaseKey(key.id); }); newDiv.addEventListener("touchend", function (event) {
newDiv.addEventListener("mousedown", function () { event.preventDefault(); releaseKey(key.id, true); });
pressKey(key.id); }); newDiv.addEventListener("mousedown", function () {
newDiv.addEventListener("mouseup", function () { pressKey(key.id, true); });
releaseKey(key.id); }); newDiv.addEventListener("mouseup", function () {
releaseKey(key.id, true); });
} else {
newDiv.addEventListener("touchstart", function (event) {
event.preventDefault(); pressKey(key.id); });
newDiv.addEventListener("touchend", function (event) {
event.preventDefault(); releaseKey(key.id); });
newDiv.addEventListener("mousedown", function () {
pressKey(key.id); });
newDiv.addEventListener("mouseup", function () {
releaseKey(key.id); });
}
} else { } else {
newDiv.className = "key blank"; newDiv.className = "key blank";
} }
@@ -365,6 +400,29 @@ function createMacros(macro_id) {
} }
} }
function createIndicators(indicator_id) {
let indicatorDiv = document.getElementById(indicator_id);
for (let indicator of indicator_list) {
let newDiv = document.createElement("div");
newDiv.id = indicator.id;
newDiv.textContent = indicator.label;
newDiv.className = "off";
indicatorDiv.appendChild(newDiv);
}
}
function updateLEDs(cur_state) {
let on_leds = cur_state.split(",");
for (let indicator of indicator_list) {
let indicator_div = document.getElementById(indicator.id);
if (on_leds.includes(indicator.id)) {
indicator_div.className = "on";
} else {
indicator_div.className = "off";
}
}
}
</script> </script>
</head> </head>
@@ -372,16 +430,15 @@ function createMacros(macro_id) {
<body> <body>
<div class="container"> <div class="container">
<div id="keyboard"></div> <div id="keyboard"></div>
<div id="indicators"></div> <div>
<div id="indicators"></div>
<div>Mouse Controls:</div>
<div id="mouse"></div>
</div>
</div> </div>
<div id="macros"></div> <div id="macros"></div>
<div class="info">
<div>Currently pressed keys:</div>
<div id="downKeys"></div>
</div>
<div> <div>
<div class="menu"> <div class="menu">
<a href="wifi.shtml">Configure Wi-Fi</a> <a href="wifi.shtml">Configure Wi-Fi</a>
@@ -391,11 +448,6 @@ function createMacros(macro_id) {
</div> </div>
</div> </div>
<div id="status_frames">
<iframe id="indicatorFrame" src="indicators.shtml"
onload="updateIndicators();"></iframe>
</div>
</body> </body>
</html> </html>
-15
View File
@@ -1,15 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Keyboard Indicators</title>
</head>
<body>
<div id="num" class="<!--#num-->">NumLock</div>
<div id="caps" class="<!--#caps-->">CapsLock</div>
<div id="scroll" class="<!--#scroll-->">ScrollLock</div>
</body>
</html>
+1 -1
View File
@@ -55,7 +55,7 @@ window.onload = () => {
<div><h1>Web Keyboard Wi-Fi Settings</h1></div> <div><h1>Web Keyboard Wi-Fi Settings</h1></div>
<div class="container"> <div class="container">
<form method="get" action="wifi.cgi"> <form method="post" action="wifi.cgi">
<div><h2>Wi-Fi Network</h2></div> <div><h2>Wi-Fi Network</h2></div>
<div class="field"> <div class="field">
+1
View File
@@ -115,6 +115,7 @@
#define LWIP_HTTPD_CGI 1 #define LWIP_HTTPD_CGI 1
#define LWIP_HTTPD_SSI 1 #define LWIP_HTTPD_SSI 1
#define LWIP_HTTPD_SSI_INCLUDE_TAG 0 #define LWIP_HTTPD_SSI_INCLUDE_TAG 0
#define LWIP_HTTPD_SUPPORT_POST 1
#define HTTPD_FSDATA_FILE "my_fsdata.c" #define HTTPD_FSDATA_FILE "my_fsdata.c"
+75
View File
@@ -0,0 +1,75 @@
#ifndef MBEDTLS_CONFIG_H
#define MBEDTLS_CONFIG_H
/* Workaround for some mbedtls source files using INT_MAX without including limits.h */
#include <limits.h>
#define MBEDTLS_NO_PLATFORM_ENTROPY
#define MBEDTLS_ENTROPY_HARDWARE_ALT
#define MBEDTLS_SSL_OUT_CONTENT_LEN 2048
#define MBEDTLS_ALLOW_PRIVATE_ACCESS
#define MBEDTLS_HAVE_TIME
#define MBEDTLS_PLATFORM_MS_TIME_ALT
#define MBEDTLS_CIPHER_MODE_CBC
#define MBEDTLS_ECP_DP_SECP192R1_ENABLED
#define MBEDTLS_ECP_DP_SECP224R1_ENABLED
#define MBEDTLS_ECP_DP_SECP256R1_ENABLED
#define MBEDTLS_ECP_DP_SECP384R1_ENABLED
#define MBEDTLS_ECP_DP_SECP521R1_ENABLED
#define MBEDTLS_ECP_DP_SECP192K1_ENABLED
#define MBEDTLS_ECP_DP_SECP224K1_ENABLED
#define MBEDTLS_ECP_DP_SECP256K1_ENABLED
#define MBEDTLS_ECP_DP_BP256R1_ENABLED
#define MBEDTLS_ECP_DP_BP384R1_ENABLED
#define MBEDTLS_ECP_DP_BP512R1_ENABLED
#define MBEDTLS_ECP_DP_CURVE25519_ENABLED
#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED
#define MBEDTLS_PKCS1_V15
#define MBEDTLS_SHA256_SMALLER
#define MBEDTLS_SSL_SERVER_NAME_INDICATION
#define MBEDTLS_AES_C
#define MBEDTLS_ASN1_PARSE_C
#define MBEDTLS_BIGNUM_C
#define MBEDTLS_CIPHER_C
#define MBEDTLS_CTR_DRBG_C
#define MBEDTLS_ENTROPY_C
#define MBEDTLS_ERROR_C
#define MBEDTLS_MD_C
#define MBEDTLS_MD5_C
#define MBEDTLS_OID_C
#define MBEDTLS_PKCS5_C
#define MBEDTLS_PK_C
#define MBEDTLS_PK_PARSE_C
#define MBEDTLS_PLATFORM_C
#define MBEDTLS_RSA_C
#define MBEDTLS_SHA1_C
#define MBEDTLS_SHA224_C
#define MBEDTLS_SHA256_C
#define MBEDTLS_SHA512_C
#define MBEDTLS_SSL_CLI_C
#define MBEDTLS_SSL_SRV_C
#define MBEDTLS_SSL_TLS_C
#define MBEDTLS_X509_CRT_PARSE_C
#define MBEDTLS_X509_USE_C
#define MBEDTLS_AES_FEWER_TABLES
/* TLS 1.2 */
#define MBEDTLS_SSL_PROTO_TLS1_2
#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
#define MBEDTLS_GCM_C
#define MBEDTLS_ECDH_C
#define MBEDTLS_ECP_C
#define MBEDTLS_ECDSA_C
#define MBEDTLS_ASN1_WRITE_C
// The following is needed to parse a certificate
#define MBEDTLS_PEM_PARSE_C
#define MBEDTLS_BASE64_C
// The following significantly speeds up mbedtls due to NIST optimizations.
#define MBEDTLS_ECP_NIST_OPTIM
#endif
+1237 -1091
View File
File diff suppressed because it is too large Load Diff
+37 -4
View File
@@ -7,6 +7,8 @@
#include "parse_keys.h" #include "parse_keys.h"
#include "usb_descriptors.h" #include "usb_descriptors.h"
static unsigned char boot_buf[8];
// take a list of Javascript keys representing pressed keys and turn into // take a list of Javascript keys representing pressed keys and turn into
// a 8 byte USB boot keyboard report format // a 8 byte USB boot keyboard report format
void parse_key_list(char * keys) { void parse_key_list(char * keys) {
@@ -28,9 +30,9 @@ void parse_key_list(char * keys) {
} }
// if a scan code was not returned, it might be a modifier // 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) { if (code == 0x00) {
// search for the correct modifier bit and add it to byte 0 of the
// USB boot keyboard report
code = parse_mod(token); code = parse_mod(token);
if (code) { if (code) {
boot_report[0] = boot_report[0] | code; boot_report[0] = boot_report[0] | code;
@@ -40,7 +42,7 @@ void parse_key_list(char * keys) {
} }
// print resulting HID boot keyboard report to CDC for debugging // print resulting HID boot keyboard report to CDC for debugging
printf("HID report: "); printf("Keyboard report: ");
for(int i=0; i<8; i++){ for(int i=0; i<8; i++){
printf("%02X ",boot_report[i]); printf("%02X ",boot_report[i]);
} }
@@ -49,6 +51,38 @@ void parse_key_list(char * keys) {
tud_hid_report(REPORT_ID_KEYBOARD, boot_report, 8); tud_hid_report(REPORT_ID_KEYBOARD, boot_report, 8);
} }
void parse_mouse_list (char * keys) {
static unsigned char mouse_report[5];
memset(mouse_report, 0x00, 5);
// Javascript sends the list as comma delimited, so split into individual
// keys by splitting at commas
char * token = strtok(keys, ",");
while (token != NULL) {
if ( strncmp(&token[0],"MouseLeft", 9) == 0 ){
mouse_report[1] = -1*MOUSE_SPEED;
} else if ( strncmp(&token[0], "MouseRight", 10) == 0 ) {
mouse_report[1] = 1*MOUSE_SPEED;
} else if ( strncmp(&token[0], "MouseUp", 7) == 0 ) {
mouse_report[2] = -1*MOUSE_SPEED;
} else if ( strncmp(&token[0], "MouseDown", 9) == 0 ) {
mouse_report[2] = 1*MOUSE_SPEED;
} else if ( strncmp(&token[0], "MouseClickLeft", 14) == 0 ) {
mouse_report[0] = mouse_report[0] | 1;
} else if ( strncmp(&token[0], "MouseClickRight", 15) == 0) {
mouse_report[0] = mouse_report[0] | 2;
}
token = strtok(NULL, ",");
}
// print resulting HID boot mouse report to CDC for debugging
printf("Mouse report: ");
for(int i=0; i<5; i++){
printf("%02X ",mouse_report[i]);
}
printf("\n");
tud_hid_report(REPORT_ID_MOUSE, mouse_report, 5);
}
// take a single Javascript key code and convert it to USB HID scancode // 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 // if it is a regular key, i.e. not a modifier key
@@ -75,4 +109,3 @@ uint8_t parse_mod(char * key) {
} }
return 0x00; return 0x00;
} }
+3 -2
View File
@@ -1,15 +1,16 @@
#ifndef PARSE_KEYS_H_ #ifndef PARSE_KEYS_H_
#define PRASE_KEYS_H_ #define PRASE_KEYS_H_
static unsigned char boot_buf[8]; #define MOUSE_SPEED 10
void parse_key_list(char * keys); void parse_key_list(char * keys);
void parse_mouse_list(char * keys);
uint8_t parse_key_single(char * key); uint8_t parse_key_single(char * key);
uint8_t parse_mod(char * key); uint8_t parse_mod(char * key);
typedef struct { char * key; uint8_t val; } keycode_dict; typedef struct { char * key; uint8_t val; } keycode_dict;
// modifier key bit codes // modifier key bit codes
#define KEY_MOD_LCTRL 0x01 #define KEY_MOD_LCTRL 0x01
#define KEY_MOD_LSHIFT 0x02 #define KEY_MOD_LSHIFT 0x02
+227 -149
View File
@@ -10,9 +10,11 @@
#include "hardware/sync.h" #include "hardware/sync.h"
#include "pico/multicore.h" #include "pico/multicore.h"
#include "server.h"
#include "parse_keys.h" #include "parse_keys.h"
#include "dhcpserver.h" #include "dhcpserver.h"
#include "websocket.h"
#include "server.h"
uint8_t led_code = 0; uint8_t led_code = 0;
static uint8_t ip[4]; static uint8_t ip[4];
@@ -25,12 +27,10 @@ static bool clientmode = false;
static bool config_loaded = false; static bool config_loaded = false;
static bool bad_config = false; static bool bad_config = false;
static bool reboot = false; static bool reboot = false;
static void *current_connection;
// list of SSI variable names for lwIP SSI handler // list of SSI variable names for lwIP SSI handler
const char * __not_in_flash("httpd") ssi_tags[] = { const char * __not_in_flash("httpd") ssi_tags[] = {
"num",
"caps",
"scroll",
"ssid", "ssid",
"pass", "pass",
"host", "host",
@@ -49,10 +49,23 @@ const char * __not_in_flash("httpd") ssi_tags[] = {
"dhcp" "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[] = { static const tCGI cgi_handlers[] = {
{ "/sendkeys.cgi", sendkeys_cgi },
{ "/wifi.cgi", save_wifi },
{ "/reboot.cgi", reboot_cgi }, { "/reboot.cgi", reboot_cgi },
}; };
@@ -119,7 +132,7 @@ void run_http_server() {
// start the HTTP web server for keyboard input and config page // start the HTTP web server for keyboard input and config page
httpd_init(); httpd_init();
http_set_cgi_handlers(cgi_handlers, 3); http_set_cgi_handlers(cgi_handlers, 1);
for (size_t i = 0; i < LWIP_ARRAYSIZE(ssi_tags); i++) { for (size_t i = 0; i < LWIP_ARRAYSIZE(ssi_tags); i++) {
LWIP_ASSERT("tag too long for LWIP_HTTPD_MAX_TAG_NAME_LEN", LWIP_ASSERT("tag too long for LWIP_HTTPD_MAX_TAG_NAME_LEN",
strlen(ssi_tags[i]) <= LWIP_HTTPD_MAX_TAG_NAME_LEN); strlen(ssi_tags[i]) <= LWIP_HTTPD_MAX_TAG_NAME_LEN);
@@ -127,6 +140,12 @@ void run_http_server() {
http_set_ssi_handler(ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags)); http_set_ssi_handler(ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags));
printf("HTTP server initialized\n"); 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 // start a watchdog timer with 8 seconds so it can reboot if it disconnects
watchdog_enable(8000,1); watchdog_enable(8000,1);
@@ -169,104 +188,6 @@ 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 ) {
parse_key_list(pcValue[i]);
}
}
lastActive= get_absolute_time();
// send return page
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));
memset(mask, 0, sizeof(mask));
memset(gw, 0, sizeof(gw));
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") {
bad_config = true;
} else {
urldecode(pcValue[i], wifi.ssid);
}
} else if (strcmp(pcParam[i], "pass") == 0) {
urldecode(pcValue[i], wifi.pass);
} else if (strcmp(pcParam[i], "host") == 0) {
urldecode(pcValue[i], wifi.host);
} else if (strcmp(pcParam[i], "dhcp") == 0) {
wifi.manual = false;
} else if (strncmp(pcParam[i], "ip", 2) ==0 ) {
uint8_t part = atoi(&(pcParam[i][2]));
int val = atoi(pcValue[i]);
if (pcValue[i][0] == "\0" || val > 255 || val < 0 || part > 4) {
bad_config = true;
} else {
ip[part] = (uint8_t) val;
}
} else if (strncmp(pcParam[i], "gw", 2) ==0 ) {
uint8_t part = atoi(&(pcParam[i][2]));
int val = atoi(pcValue[i]);
if (pcValue[i][0] == "\0" || val > 255 || val < 0 || part > 4) {
bad_config = true;
} else {
gw[part] = (uint8_t) val;
}
} else if (strncmp(pcParam[i], "mask", 4) ==0 ) {
uint8_t part = atoi(&(pcParam[i][4]));
int val = atoi(pcValue[i]);
if (pcValue[i][0] == "\0" || val > 255 || val < 0 || part > 4) {
bad_config = true;
} else {
mask[part] = (uint8_t) val;
}
}
}
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;
}
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 configuratitons to flash
net_config_write(&wifi);
printf("wifi settings saved\n");
return "/success.html";
}
}
// lwIP cgi handler for reboots initiated from the web // lwIP cgi handler for reboots initiated from the web
const char * reboot_cgi(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) { const char * reboot_cgi(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) {
// turn on reboot flag to prevent watchdog from updating // turn on reboot flag to prevent watchdog from updating
@@ -275,7 +196,7 @@ const char * reboot_cgi(int iIndex, int iNumParams, char *pcParam[], char *pcVal
return "/success.html"; return "/success.html";
} }
// write configuration data to flash memory
void net_config_write(net_config *config) { void net_config_write(net_config *config) {
// core 0 must have its TinyUSB interrupts disabled to allow flash writes // core 0 must have its TinyUSB interrupts disabled to allow flash writes
multicore_lockout_start_blocking(); multicore_lockout_start_blocking();
@@ -310,79 +231,58 @@ bool net_config_load(net_config *config) {
uint16_t __time_critical_func(ssi_handler)(int iIndex, char *pcInsert, int iInsertLen) { uint16_t __time_critical_func(ssi_handler)(int iIndex, char *pcInsert, int iInsertLen) {
size_t printed; size_t printed;
switch (iIndex) { switch (iIndex) {
case 0: //num - numlock status case 0: //ssid - network config SSID
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) { if (config_loaded && wifi.ssid) {
printed = snprintf(pcInsert, iInsertLen, "value=\"%s\"", wifi.ssid); printed = snprintf(pcInsert, iInsertLen, "value=\"%s\"", wifi.ssid);
} else { } else {
printed = 0; printed = 0;
} }
break; break;
case 4: //pass - network config password case 1: //pass - network config password
if (config_loaded && wifi.pass) { if (config_loaded && wifi.pass) {
printed = snprintf(pcInsert, iInsertLen, "value=\"%s\"", wifi.pass); printed = snprintf(pcInsert, iInsertLen, "value=\"%s\"", wifi.pass);
} else { } else {
printed = 0; printed = 0;
} }
break; break;
case 5: //host - network config hostname case 2: //host - network config hostname
if (config_loaded && wifi.host) { if (config_loaded && wifi.host) {
printed = snprintf(pcInsert, iInsertLen, "value=\"%s\"", wifi.host); printed = snprintf(pcInsert, iInsertLen, "value=\"%s\"", wifi.host);
} else { } else {
printed = 0; printed = 0;
} }
break; break;
case 6: 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 7:
case 8: case 8:
case 9: //ip0-3 - network config manual IP address parts case 9:
if (config_loaded && wifi.ip.addr) { case 10: //mask0-3 - network config manual netmask parts
printed = snprintf(pcInsert, iInsertLen, "value=\"%d\"", ip4_addr_get_byte(&(wifi.ip), iIndex-6)); if (config_loaded && wifi.mask.addr) {
printed = snprintf(pcInsert, iInsertLen, "value=\"%d\"", ip4_addr_get_byte(&(wifi.mask), iIndex-7));
} else { } else {
printed = 0; printed = 0;
} }
break; break;
case 10:
case 11: case 11:
case 12: case 12:
case 13: //mask0-3 - network config manual netmask parts case 13:
if (config_loaded && wifi.mask.addr) { case 14: //gw0-3 - network config manual gateway parts
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) { if (config_loaded && wifi.gw.addr) {
printed = snprintf(pcInsert, iInsertLen, "value=\"%d\"", ip4_addr_get_byte(&(wifi.gw), iIndex-14)); printed = snprintf(pcInsert, iInsertLen, "value=\"%d\"", ip4_addr_get_byte(&(wifi.gw), iIndex-11));
} else { } else {
printed = 0; printed = 0;
} }
break; break;
case 18: // dhcp - network config DHCP client enabled/disabled case 15: // dhcp - network config DHCP client enabled/disabled
if ((!config_loaded) || !(wifi.manual) ){ if ((!config_loaded) || !(wifi.manual) ){
printed = snprintf(pcInsert, iInsertLen, "checked"); printed = snprintf(pcInsert, iInsertLen, "checked");
} else { } else {
@@ -397,12 +297,190 @@ uint16_t __time_critical_func(ssi_handler)(int iIndex, char *pcInsert, int iInse
return (uint16_t)printed; 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 // save keyboard's LED indicator status to memory
void set_indicator(uint8_t const* buffer) { void set_indicator(uint8_t const* buffer) {
led_code = *buffer; led_code = *buffer;
send_indicators();
} }
// turn URL-formatted string from GET request and turn into regular string // 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) { void urldecode(char *urlstring, char *decoded) {
uint8_t conv; uint8_t conv;
while(*urlstring) { while(*urlstring) {
+6 -2
View File
@@ -2,11 +2,13 @@
#define SERVER_H_ #define SERVER_H_
#include "lwip/ip4_addr.h" #include "lwip/ip4_addr.h"
#include "lwip/pbuf.h"
#define STARTFILE 0x7fc6 #define STARTFILE 0x7fc6
#define ENDFILE 0x0464 #define ENDFILE 0x0464
#define DEFAULTHOST "picokb" #define DEFAULTHOST "picokb"
#define DEFAULTPASS "password" #define DEFAULTPASS "password"
#define MAX_POST_PARAM_LEN 63+1
typedef struct { typedef struct {
uint16_t header; uint16_t header;
@@ -21,14 +23,16 @@ typedef struct {
} net_config; } net_config;
void run_http_server(); void run_http_server();
const char * sendkeys_cgi(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]);
const char * save_wifi(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]);
const char * reboot_cgi(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]); const char * reboot_cgi(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]);
uint16_t __time_critical_func(ssi_handler)(int iIndex, char *pcInsert, int iInsertLen); uint16_t __time_critical_func(ssi_handler)(int iIndex, char *pcInsert, int iInsertLen);
void set_indicator(uint8_t const* buffer); void set_indicator(uint8_t const* buffer);
void send_indicators(void);
void urldecode(char *urlstring, char *dest); void urldecode(char *urlstring, char *dest);
bool net_config_load(net_config *wifi); bool net_config_load(net_config *wifi);
void net_config_write(net_config *wifi); void net_config_write(net_config *wifi);
char *find_post_param(struct pbuf *p, const char *param, char *buf, size_t len);
const void ws_receive_handler(uint8_t *data, uint16_t len);
const void ws_open_handler(struct ws_state * wss);
#define FLASH_TARGET_OFFSET ((PICO_FLASH_SIZE_BYTES) - FLASH_SECTOR_SIZE) #define FLASH_TARGET_OFFSET ((PICO_FLASH_SIZE_BYTES) - FLASH_SECTOR_SIZE)
+61 -75
View File
@@ -25,54 +25,44 @@
*/ */
#include "tusb.h" #include "tusb.h"
#include "bsp/board_api.h"
#include "usb_descriptors.h" #include "usb_descriptors.h"
/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug.
* Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC.
*
* Auto ProductID layout's Bitmap:
* [MSB] HID | MSC | CDC [LSB]
*/
#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) )
#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \
_PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) )
#define USB_VID 0xCafe
#define USB_BCD 0x0200
//--------------------------------------------------------------------+ //--------------------------------------------------------------------+
// Device Descriptors // Device Descriptors
//--------------------------------------------------------------------+ //--------------------------------------------------------------------+
tusb_desc_device_t const desc_device = tusb_desc_device_t const desc_device =
{ {
.bLength = sizeof(tusb_desc_device_t), .bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE, .bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = USB_BCD, .bcdUSB = USB_BCD,
// Use Interface Association Descriptor (IAD) for CDC // Use Interface Association Descriptor (IAD) for CDC
// As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1)
.bDeviceClass = TUSB_CLASS_MISC, .bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON, .bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD, .bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = USB_VID, .idVendor = USB_VID,
.idProduct = USB_PID, .idProduct = USB_PID,
.bcdDevice = 0x0100, .bcdDevice = 0x0100,
.iManufacturer = 0x01, .iManufacturer = 0x01,
.iProduct = 0x02, .iProduct = 0x02,
.iSerialNumber = 0x03, .iSerialNumber = 0x03,
.bNumConfigurations = 0x01 .bNumConfigurations = 0x01
}; };
// Invoked when received GET DEVICE DESCRIPTOR // Invoked when received GET DEVICE DESCRIPTOR
// Application return pointer to descriptor // Application return pointer to descriptor
uint8_t const * tud_descriptor_device_cb(void) uint8_t const * tud_descriptor_device_cb(void)
{ {
return (uint8_t const *) &desc_device; return (uint8_t const *) &desc_device;
} }
@@ -80,16 +70,10 @@ uint8_t const * tud_descriptor_device_cb(void)
// Configuration Descriptor // Configuration Descriptor
//--------------------------------------------------------------------+ //--------------------------------------------------------------------+
#define EPNUM_CDC_NOTIF 0x81
#define EPNUM_CDC_OUT 0x02
#define EPNUM_CDC_IN 0x82
#define EPNUM_HID 0x83
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_HID_DESC_LEN)
uint8_t const desc_hid_report[] = uint8_t const desc_hid_report[] =
{ {
TUD_HID_REPORT_DESC_KEYBOARD( HID_REPORT_ID(REPORT_ID_KEYBOARD)) TUD_HID_REPORT_DESC_KEYBOARD( HID_REPORT_ID(REPORT_ID_KEYBOARD)),
TUD_HID_REPORT_DESC_MOUSE( HID_REPORT_ID(REPORT_ID_MOUSE))
}; };
@@ -97,12 +81,12 @@ uint8_t const desc_hid_report[] =
// full speed configuration // full speed configuration
uint8_t const desc_fs_configuration[] = uint8_t const desc_fs_configuration[] =
{ {
// Config number, interface count, string index, total length, attribute, power in mA - atttribute 0xa0 enables wakeup // Config number, interface count, string index, total length, attribute, power in mA - atttribute 0xa0 enables wakeup
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0xa0, 100), TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0xa0, 100),
// Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval // Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64), TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64),
TUD_HID_DESCRIPTOR(ITF_NUM_HID, 5, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 5), TUD_HID_DESCRIPTOR(ITF_NUM_HID, 5, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 8),
}; };
// Invoked when received GET CONFIGURATION DESCRIPTOR // Invoked when received GET CONFIGURATION DESCRIPTOR
@@ -110,8 +94,8 @@ uint8_t const desc_fs_configuration[] =
// Descriptor contents must exist long enough for transfer to complete // Descriptor contents must exist long enough for transfer to complete
uint8_t const * tud_descriptor_configuration_cb(uint8_t index) uint8_t const * tud_descriptor_configuration_cb(uint8_t index)
{ {
(void) index; // for multiple configurations (void) index; // for multiple configurations
return desc_fs_configuration; return desc_fs_configuration;
} }
@@ -122,55 +106,57 @@ uint8_t const * tud_descriptor_configuration_cb(uint8_t index)
// array of pointer to string descriptors // array of pointer to string descriptors
char const* string_desc_arr [] = char const* string_desc_arr [] =
{ {
(const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
"Raspberry Pi", // 1: Manufacturer "Raspberry Pi", // 1: Manufacturer
"Pico Web Keyboard", // 2: Product "Pico Web Keyboard", // 2: Product
"1234567890123456789", // 3: Serials, should use chip ID NULL, // 3: Serials, should use chip ID
"Pico Web Keyboard CDC", // 4: CDC Interface "Pico Web Keyboard CDC", // 4: CDC
"TinyUSB Keyboard", // 5: HID Keyboard Interface "Pico Web Keyboard HID", // 5: HID
}; };
static uint16_t _desc_str[32]; static uint16_t _desc_str[32+1];
// Invoked when received GET STRING DESCRIPTOR request // Invoked when received GET STRING DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid)
{ {
(void) langid; (void) langid;
uint8_t chr_count; uint8_t chr_count;
if ( index == 0) switch (index) {
{ case 0: // langid
memcpy(&_desc_str[1], string_desc_arr[0], 2); memcpy(&_desc_str[1], string_desc_arr[0], 2);
chr_count = 1; chr_count = 1;
}else break;
{ case 3: // serial
// Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. chr_count = board_usb_get_serial(_desc_str+1, 32);
// https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors break;
default:
// Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
// https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL;
const char* str = string_desc_arr[index]; char* str = string_desc_arr[index];
// Cap at max char // Cap at max char
chr_count = (uint8_t) strlen(str); chr_count = (uint8_t) strlen(str);
if ( chr_count > 31 ) chr_count = 31; if ( chr_count > 31 ) chr_count = 31;
// Convert ASCII string into UTF-16 // Convert ASCII string into UTF-16
for(uint8_t i=0; i<chr_count; i++) for(uint8_t i=0; i<chr_count; i++) {
{ _desc_str[1+i] = str[i];
_desc_str[1+i] = str[i]; }
} break;
} }
// first byte is length (including header), second byte is string type // first byte is length (including header), second byte is string type
_desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2*chr_count + 2); _desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2*chr_count + 2);
return _desc_str; return _desc_str;
} }
//--------------------------------------------------------------------+ //--------------------------------------------------------------------+
// Device HID // Device HID
//--------------------------------------------------------------------+ //--------------------------------------------------------------------+
+11
View File
@@ -29,6 +29,7 @@ enum
{ {
REPORT_ID_KEYBOARD = 1, REPORT_ID_KEYBOARD = 1,
REPORT_ID_CONSUMER_CONTROL, REPORT_ID_CONSUMER_CONTROL,
REPORT_ID_MOUSE,
REPORT_ID_COUNT REPORT_ID_COUNT
}; };
@@ -40,5 +41,15 @@ enum
ITF_NUM_TOTAL ITF_NUM_TOTAL
}; };
#define USB_PID 0x0EBD
#define USB_VID 0xCEC0
#define USB_BCD 0x0200
#define EPNUM_CDC_NOTIF 0x81
#define EPNUM_CDC_OUT 0x02
#define EPNUM_CDC_IN 0x82
#define EPNUM_HID 0x83
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_HID_DESC_LEN)
#endif /* USB_DESCRIPTORS_H_ */ #endif /* USB_DESCRIPTORS_H_ */
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 79 KiB

+475
View File
@@ -0,0 +1,475 @@
#include <string.h>
#include "lwip/altcp.h"
#include "lwip/debug.h"
#include "mbedtls/base64.h"
#include "mbedtls/sha1.h"
#include "websocket.h"
static const char WS_GUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
static const char WS_RESPONSE[] = "HTTP/1.1 101 Switching Protocols\r\n" \
"Upgrade: websocket\r\n" \
"Connection: Upgrade\r\n" \
"Sec-WebSocket-Accept: ";
static uint8_t buf[WS_BUFFER_SIZE];
static uint16_t buf_len=0;
static tWSHandler ws_receive_cb = NULL;
static tWSOpenHandler ws_open_cb = NULL;
static struct ws_state * ws_connections;
static uint8_t ws_num_conns = 0;
static struct ws_state* ws_state_alloc(void);
static void ws_state_init(struct ws_state *wss);
static void ws_state_free(struct ws_state *wss);
static void ws_server_init_pcb( struct altcp_pcb *pcb, uint16_t port);
static err_t ws_accept(void *arg, struct altcp_pcb *pcb, err_t err);
static err_t ws_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err);
static err_t ws_sent(void *arg, struct altcp_pcb *pcb, uint16_t len);
static void ws_err (void *arg, err_t err);
static err_t ws_close_conn(struct altcp_pcb *pcb, struct ws_state *wss);
static err_t ws_close_or_abort_conn(struct altcp_pcb *pcb, struct ws_state *wss, uint8_t abort_conn);
static err_t ws_poll(void *arg, struct altcp_pcb *pcb);
static err_t ws_handshake(struct altcp_pcb *pcb, struct ws_state *wss, struct pbuf *p);
static err_t ws_read(struct altcp_pcb *pcb, struct ws_state *wss, struct pbuf *p);
static err_t ws_send(struct ws_state *wss, uint8_t *data, uint16_t len);
// allocate memory for ws_state instance
static struct ws_state * ws_state_alloc(void) {
struct ws_state *ret = WS_ALLOC_WS_STATE();
if ( ret != NULL) {
ws_state_init(ret);
if (ws_connections == NULL) {
ws_connections = ret;
} else {
struct ws_state *last;
for (last=ws_connections; last->next != NULL; last=last->next);
LWIP_ASSERT("last != NULL", last != NULL);
last->next = ret;
}
}
return ret;
}
// initiate ws_state instance
static void ws_state_init(struct ws_state *wss) {
memset(wss, 0, sizeof(struct ws_state));
wss->active = false;
}
// free memory from ws_state instance
static void ws_state_free(struct ws_state *wss) {
if (wss != NULL) {
if (ws_connections == wss) {
ws_connections = wss->next;
} else {
struct ws_state * last;
for (last = ws_connections; last->next != NULL; last = last->next) {
if (last->next = wss) {
last->next = wss->next;
break;
}
}
}
mem_free(wss);
}
}
// initiate websocket server on specified pcb
static void ws_server_init_pcb( struct altcp_pcb *pcb, uint16_t port) {
err_t err;
if (pcb) {
altcp_setprio(pcb, TCP_PRIO_MIN);
err = altcp_bind(pcb, IP_ANY_TYPE, port);
LWIP_UNUSED_ARG(err);
LWIP_ASSERT("ws_server_init: tcp_bind failed", err == ERR_OK);
pcb = altcp_listen(pcb);
LWIP_ASSERT("ws_server_init: tcp_listen failed", pcb != NULL);
altcp_accept(pcb, ws_accept);
}
}
// initiate a websocket server
void ws_server_init(void) {
struct altcp_pcb *pcb = altcp_tcp_new_ip_type(IPADDR_TYPE_ANY);
LWIP_ASSERT("ws_server_init: tcp_new failed", pcb != NULL);
ws_server_init_pcb(pcb, WS_PORT);
}
// set ws_receive_handler
void ws_set_receive_handler( tWSHandler ws_handler)
{
ws_receive_cb = ws_handler;
}
// set ws_open_handler
void ws_set_open_handler( tWSOpenHandler ws_handler)
{
ws_open_cb = ws_handler;
}
// callback for accepted websocket connection
static err_t ws_accept(void *arg, struct altcp_pcb *pcb, err_t err) {
struct ws_state *wss;
LWIP_UNUSED_ARG(err);
LWIP_UNUSED_ARG(arg);
LWIP_DEBUGF(WS_DEBUG, ("ws_accept %p / %p\n", (void *)pcb, arg));
if ((err != ERR_OK) || (pcb == NULL)) {
return ERR_VAL;
}
// create new ws_state object
wss = ws_state_alloc();
if (wss == NULL) {
LWIP_DEBUGF(WS_DEBUG, ("ws_accept: Out of memory, RST\n"));
return ERR_MEM;
}
wss->pcb = pcb;
// make ws_state object the argument of callbacks
altcp_arg(pcb, wss);
// register callbacks for tcp events
altcp_recv(pcb, ws_recv);
altcp_sent(pcb, ws_sent);
altcp_poll(pcb, ws_poll, WS_POLL_INTERVAL);
altcp_err(pcb, ws_err);
return ERR_OK;
}
// call when data is received
static err_t ws_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err) {
struct ws_state *wss = (struct ws_state *) arg;
if ((err != ERR_OK) || (p == NULL) || (wss == NULL)) {
// error or closed by client
if (p != NULL) {
// inform TCP that we have taken the data
altcp_recved(pcb, p->tot_len);
pbuf_free(p);
}
if (wss == NULL) {
// should not occur
LWIP_DEBUGF(WS_DEBUG, ("Error, ws_recv: wss is NULL, close\n"));
}
ws_close_conn(pcb, wss);
return ERR_OK;
}
if (wss->active) {
// process websocket message
err = ws_read(pcb, wss, p);
} else {
// init websocket connection
LWIP_DEBUGF(WS_DEBUG, ("ws_recv: websocket inactive, checking for handshake\n"));
err = ws_handshake(pcb, wss, p);
}
// inform TCP that we have taken the data.
altcp_recved(pcb, p->tot_len);
pbuf_free(p);
if (err == ERR_CLSD) {
ws_close_conn(pcb, wss);
}
return ERR_OK;
}
// called when data has been sent over the websocket
static err_t ws_sent(void *arg, struct altcp_pcb *pcb, uint16_t len) {
struct ws_state *wss = (struct ws_state *)arg;
LWIP_DEBUGF(WS_DEBUG | LWIP_DBG_TRACE, ("ws_sent %p\n", (void*) pcb));
LWIP_UNUSED_ARG(len);
if (wss == NULL) {
return ERR_OK;
}
wss->retries = 0;
return ERR_OK;
}
// called when there is a websocket error
static void ws_err (void *arg, err_t err) {
struct ws_state *wss = (struct ws_state *) arg;
LWIP_UNUSED_ARG(err);
LWIP_DEBUGF(WS_DEBUG, ("ws_err: %s", lwip_strerr(err)));
if (wss != NULL) {
ws_state_free(wss);
}
}
// initiate close of connection
static err_t ws_close_conn(struct altcp_pcb *pcb, struct ws_state *wss) {
return ws_close_or_abort_conn(pcb, wss, 0);
}
// call when closing connection or connection was aborted
static err_t ws_close_or_abort_conn(struct altcp_pcb *pcb, struct ws_state *wss,
uint8_t abort_conn) {
err_t err;
LWIP_DEBUGF(WS_DEBUG, ("Closing connection %p\n", (void *)pcb));
// clear callbacks
altcp_arg(pcb, NULL);
altcp_recv(pcb, NULL);
altcp_sent(pcb, NULL);
altcp_poll(pcb, NULL, 0);
altcp_err(pcb, NULL);
// remove and free memory from ws_state object
if (wss != NULL) {
ws_state_free(wss);
}
if (abort_conn) {
altcp_abort(pcb);
return ERR_OK;
}
err = altcp_close(pcb);
if (err != ERR_OK) {
LWIP_DEBUGF(WS_DEBUG, ("Error %d closing %p\n", err, (void *)pcb));
// error closing, try again later in poll
altcp_poll(pcb, ws_poll, WS_POLL_INTERVAL);
}
return err;
}
// callback for polling process
static err_t ws_poll(void *arg, struct altcp_pcb *pcb) {
struct ws_state *wss = (struct ws_state *) arg;
if (wss == NULL) {
err_t closed;
LWIP_DEBUGF(WS_DEBUG, ("ws_poll: arg is NULL, close\n"));
closed = ws_close_conn(pcb, NULL);
LWIP_UNUSED_ARG(closed);
if (closed == ERR_MEM) {
altcp_abort(pcb);
return ERR_ABRT;
}
return ERR_OK;
} else {
wss->retries++;
if (wss->retries == WS_MAX_RETRIES) {
LWIP_DEBUGF(WS_DEBUG, ("ws_poll: too may retries, close\n"));
ws_close_conn(pcb, wss);
return ERR_OK;
}
}
return ERR_OK;
}
// check for and complete handshake with client
static err_t ws_handshake(struct altcp_pcb *pcb, struct ws_state *wss, struct pbuf *p){
uint8_t *data = (uint8_t *) p->payload;
uint16_t len = p->len;
// check if client is initiating a websocket connecttion
if (strstr(data, "Upgrade: websocket")) {
LWIP_DEBUGF(WS_DEBUG, ("ws_handshake: received websocket upgrade request\n"));
// search for websocket security key
char *key_start = strstr(data, "Sec-WebSocket-Key: ");
if (key_start) {
key_start += 19;
const char *key_end = strstr(key_start, "\r\n");
if (key_end) {
char key[64];
uint16_t key_len = key_end-key_start;
if ( (key_len>0) && (key_len + sizeof(WS_GUID) < sizeof(key)) ) {
// create response key by concatenating with websocket GUID,
// taking SHA1 hash, then encoding in base 64
strncpy(key, key_start, key_len);
strlcpy(&key[key_len], WS_GUID, sizeof(key)-key_len);
key_len += sizeof(WS_GUID)-1;
unsigned char key_sha1[20];
unsigned char key_base64[29];
size_t encoded_len;
mbedtls_sha1( (unsigned char *) key, key_len, key_sha1);
mbedtls_base64_encode( key_base64, 29, &encoded_len, key_sha1, 20);
// create response packet with encoded response key
unsigned char response[sizeof(WS_RESPONSE) + sizeof(key_base64)+3];
//strncpy(response, WS_RESPONSE, sizeof(WS_RESPONSE));
//strlcpy(&response[sizeof(WS_RESPONSE)-1], key_base64, strlen(key_base64));
size_t count = sprintf(response, "%s%s\r\n\r\n", WS_RESPONSE, key_base64);
// send completed data packet
LWIP_DEBUGF(WS_DEBUG, ("ws_handshake: sending response\n"));
if(altcp_write(pcb, response, strlen(response), TCP_WRITE_FLAG_COPY) == ERR_OK) {
wss->active = true;
}
if (ws_open_cb != NULL) {
ws_open_cb(wss);
}
return ERR_OK;
}
}
LWIP_DEBUGF(WS_DEBUG, ("ws_handshake: key overflow\n"));
return ERR_MEM;
} else {
LWIP_DEBUGF(WS_DEBUG, ("ws_handshake: key not received\n"));
return ERR_ARG;
}
}
LWIP_DEBUGF(WS_DEBUG, ("ws_handshake: not a websocket request\n"));
return ERR_ARG;
}
// handle reading of websocket data and pass to ws_receive_cb
static err_t ws_read(struct altcp_pcb *pcb, struct ws_state *wss, struct pbuf *p) {
uint8_t *data = (uint8_t *) p->payload;
uint16_t len = p->len;
if (data != NULL && len > 1) {
// successful read, reset timeout
wss->retries = 0;
uint8_t fin = data[0] & 0x80;
uint8_t opcode = data[0] & 0x0F;
uint8_t masked = data[1] & 0x80;
uint16_t msg_len = data[1] & 0x7F;
uint8_t *msg;
switch (msg_len) {
case 126: // next two bytes are length
memcpy(&msg_len, &data[2], 2);
if (len >= 8) {
msg = &data[8];
}
break;
case 127: // next four bytes are length
// lwIP's pbuf only handles 16-bit lengths, so error
LWIP_DEBUGF(WS_DEBUG, ("ws_read: received 64-bit length %u\n", msg_len));
return ERR_MEM;
//memcpy(&msg_len, &data[2], 4);
//if (len >= 10) {
// msg = &data[10];
//}
//break;
default:
if (len >= 6) {
msg = &data[6];
}
break;
}
switch (opcode) {
case OP_CONT:
LWIP_DEBUGF(WS_DEBUG, ("ws_read: received continuation frame\n"));
case OP_TEXT:
LWIP_DEBUGF(WS_DEBUG, ("ws_read: received text data\n"));
case OP_BINARY:
LWIP_DEBUGF(WS_DEBUG, ("ws_read: decoding data, len=%u\n", msg_len));
if (msg && ws_receive_cb != NULL) {
// unmask the data if mask bit is received
if (masked) {
uint8_t *mask = &data[2];
for (int i=0; i<msg_len; i++) {
msg[i] ^= mask[i % 4];
}
} else {
// messages from client must be masked - disconnect
LWIP_DEBUGF(WS_DEBUG, ("ws_read: received unmasked message"));
return ERR_CLSD;
}
msg[msg_len]=0;
if (opcode != OP_CONT) { // not a continuation frame, reset buffer
buf_len=0;
memset(buf, 0x00, sizeof(buf));
}
if (buf_len + msg_len > WS_BUFFER_SIZE) {
LWIP_DEBUGF(WS_DEBUG, ("ws_read: message exceeds buffer size %u+%u\n", buf_len, msg_len));
return ERR_MEM;
}
memcpy(&buf[buf_len], msg, msg_len);
buf_len += msg_len;
if (fin) { // last packet in message, process completed message
ws_receive_cb(buf, buf_len);
}
}
break;
case OP_CLOSE:
LWIP_DEBUGF(WS_DEBUG, ("ws_read: close request"));
return ERR_CLSD;
case OP_PING:
// control frames cannot exceed 125 bytes in length
if (msg && msg_len <= 125) {
// send back a pong
uint8_t pong[2+msg_len];
pong[0]=0x8A;
pong[1]=msg_len;
memcpy(&pong[2], msg, msg_len);
return ws_send(wss, pong, msg_len+2);
}
return ERR_ARG;
case OP_PONG: // no response required for pong
return ERR_OK;
default:
LWIP_DEBUGF(WS_DEBUG, ("ws_read: invalid opcode %02X\n", opcode));
return ERR_ARG;
}
return ERR_OK;
}
LWIP_DEBUGF(WS_DEBUG, ("ws_read: received empty payload\n"));
return ERR_VAL;
}
static err_t ws_send(struct ws_state *wss, uint8_t *data, uint16_t len) {
uint8_t buf[128];
buf[0] = 0x81;
buf[1] = len & 0x7F;
memcpy(&buf[2], data, len);
err_t err;
err = altcp_write(wss->pcb, buf, len+2, TCP_WRITE_FLAG_COPY);
if (err == ERR_OK) {
altcp_output(wss->pcb);
}
return err;
}
void ws_send_all(uint8_t *data, uint16_t len) {
// send message to all connections
if (ws_connections != NULL) {
struct ws_state *wss;
err_t err;
for (wss=ws_connections; wss != NULL; wss=wss->next) {
err = ws_send(wss, data, len);
if (err != ERR_OK ) {
LWIP_DEBUGF(WS_DEBUG, ("ws_send_all: error sending to %p\n", wss));
}
}
}
}
+36
View File
@@ -0,0 +1,36 @@
#ifndef WEBSOCKET_H_
#define WEBSOCKET_H_
#define WS_PORT 8080
#define WS_TIMEOUT 10
#define WS_DEBUG LWIP_DBG_ON
#define WS_MAX_RETRIES 10
#define WS_POLL_INTERVAL 60 // WS_POLL_INTERVAL/2 seconds
#define WS_MAX_CONN 4
#define WS_BUFFER_SIZE 512
#define OP_CONT 0x00
#define OP_TEXT 0x01
#define OP_BINARY 0x02
#define OP_CLOSE 0x08
#define OP_PING 0x09
#define OP_PONG 0x0A
struct ws_state {
bool active;
uint8_t retries;
struct altcp_pcb *pcb;
struct ws_state *next;
};
#define WS_ALLOC_WS_STATE() (struct ws_state *)mem_malloc(sizeof(struct ws_state))
typedef void (* tWSHandler ) (uint8_t *data, uint16_t len);
typedef void (* tWSOpenHandler ) (struct ws_state * wss);
void ws_server_init(void);
void ws_send_all(uint8_t *data, uint16_t len);
void ws_set_receive_handler( tWSHandler ws_handler);
void ws_set_open_handler( tWSOpenHandler ws_handler);
#endif