diff --git a/.vscode/settings.json b/.vscode/settings.json
index 2e786ef..bcea061 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -6,5 +6,8 @@
"editor.quickSuggestions": {
"strings": "on"
},
- "tailwindCSS.classAttributes": ["class", "className", ".*Styles", ".*Class"]
+ "tailwindCSS.classAttributes": ["class", "className", ".*Styles", ".*Class"],
+ "files.associations": {
+ "main.h": "c"
+ }
}
diff --git a/firmware/.settings/language.settings.xml b/firmware/.settings/language.settings.xml
index 0a9e24f..af0d254 100644
--- a/firmware/.settings/language.settings.xml
+++ b/firmware/.settings/language.settings.xml
@@ -5,7 +5,7 @@
-
+
@@ -16,7 +16,7 @@
-
+
diff --git a/firmware/Core/Inc/main.h b/firmware/Core/Inc/main.h
index 0ed6c47..bf7361f 100644
--- a/firmware/Core/Inc/main.h
+++ b/firmware/Core/Inc/main.h
@@ -130,7 +130,7 @@ struct key {
struct actuation actuation;
};
-struct hid_generic_inout_report_key {
+struct serial_key {
uint8_t row;
uint8_t column;
uint16_t idle_value;
@@ -140,14 +140,6 @@ struct hid_generic_inout_report_key {
enum actuation_status status;
};
-struct hid_generic_inout_report {
- struct hid_generic_inout_report_key keys[6];
- uint8_t duration;
- uint8_t trigger_offset;
- uint8_t reset_threshold;
- uint8_t rapid_trigger_offset;
-};
-
struct user_config {
uint8_t trigger_offset;
uint8_t reset_threshold;
diff --git a/firmware/Core/Inc/stm32f4xx_hal_conf.h b/firmware/Core/Inc/stm32f4xx_hal_conf.h
index 73c6d88..ab78edc 100644
--- a/firmware/Core/Inc/stm32f4xx_hal_conf.h
+++ b/firmware/Core/Inc/stm32f4xx_hal_conf.h
@@ -214,7 +214,7 @@
#define MAC_ADDR5 0U
/* Definition of the Ethernet driver buffers size and count */
-#define ETH_RX_BUF_SIZE /* buffer size for receive */
+#define ETH_RX_BUF_SIZE ETH_MAX_PACKET_SIZE /* buffer size for receive */
#define ETH_TX_BUF_SIZE ETH_MAX_PACKET_SIZE /* buffer size for transmit */
#define ETH_RXBUFNB 4U /* 4 Rx buffers of size ETH_RX_BUF_SIZE */
#define ETH_TXBUFNB 4U /* 4 Tx buffers of size ETH_TX_BUF_SIZE */
diff --git a/firmware/Core/Inc/tusb_config.h b/firmware/Core/Inc/tusb_config.h
index d26c54a..3e450af 100644
--- a/firmware/Core/Inc/tusb_config.h
+++ b/firmware/Core/Inc/tusb_config.h
@@ -88,14 +88,19 @@ extern "C" {
#endif
//------------- CLASS -------------//
-#define CFG_TUD_HID 2
+#define CFG_TUD_HID 1
#define CFG_TUD_CDC 0
#define CFG_TUD_MSC 0
#define CFG_TUD_MIDI 0
-#define CFG_TUD_VENDOR 0
+#define CFG_TUD_VENDOR 1
// HID buffer size Should be sufficient to hold ID (if any) + Data
-#define CFG_TUD_HID_EP_BUFSIZE 64
+#define CFG_TUD_HID_EP_BUFSIZE 16
+
+// Vendor FIFO size of TX and RX
+// If not configured vendor endpoints will not be buffered
+#define CFG_TUD_VENDOR_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_VENDOR_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
#ifdef __cplusplus
}
diff --git a/firmware/Core/Inc/usb_descriptors.h b/firmware/Core/Inc/usb_descriptors.h
index 89d2c93..bd4536f 100644
--- a/firmware/Core/Inc/usb_descriptors.h
+++ b/firmware/Core/Inc/usb_descriptors.h
@@ -25,22 +25,20 @@
#ifndef USB_DESCRIPTORS_H_
#define USB_DESCRIPTORS_H_
-#define HID_GENERIC_INOUT_REPORT_BUFFSIZE 64
-
enum {
REPORT_ID_KEYBOARD = 1,
REPORT_ID_CONSUMER_CONTROL,
};
enum {
- REPORT_ID_ANALOG = 1,
- REPORT_ID_CONFIG,
+ VENDOR_REQUEST_WEBUSB = 1,
+ VENDOR_REQUEST_MICROSOFT = 2
};
enum {
- ITF_NUM_KEYBOARD,
- ITF_NUM_GENERIC_INOUT,
+ ITF_NUM_KEYBOARD = 0,
+ ITF_NUM_VENDOR,
ITF_NUM_TOTAL
};
-#endif /* USB_DESCRIPTORS_H_ */
\ No newline at end of file
+#endif /* USB_DESCRIPTORS_H_ */
diff --git a/firmware/Core/Src/main.c b/firmware/Core/Src/main.c
index 111bf93..9743161 100644
--- a/firmware/Core/Src/main.c
+++ b/firmware/Core/Src/main.c
@@ -89,12 +89,21 @@ static uint8_t key_triggered = 0;
static uint8_t should_send_consumer_report = 0;
static uint8_t should_send_keyboard_report = 0;
-static uint8_t should_send_generic_inout_report = 0;
static uint8_t modifiers = 0;
static uint8_t keycodes[6] = {0};
static uint8_t consumer_report = 0;
-static struct hid_generic_inout_report generic_inout_report = {0};
+
+extern uint8_t const desc_ms_os_20[];
+#define URL "heiso.github.io/macrolev/configurator"
+const tusb_desc_webusb_url_t desc_url =
+ {
+ .bLength = 3 + sizeof(URL) - 1,
+ .bDescriptorType = 3, // WEBUSB URL type
+ .bScheme = 1, // 0: http, 1: https
+ .url = URL};
+static bool web_serial_connected = false;
+
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
@@ -152,6 +161,7 @@ void writeConfig(uint8_t *buffer, uint16_t size) {
* @retval int
*/
int main(void) {
+
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
@@ -187,8 +197,7 @@ int main(void) {
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
- uint32_t start = HAL_GetTick();
-
+ // MARK: Main loop
tud_task();
key_triggered = 0;
@@ -237,26 +246,53 @@ int main(void) {
}
}
- if ((should_send_consumer_report || should_send_keyboard_report) && tud_hid_n_ready(ITF_NUM_KEYBOARD)) {
+ // if (mode == USB_MODE_SERIAL) {
+ // if (tud_cdc_connected()) {
+ // if (tud_cdc_available()) {
+ // uint8_t buffer[CFG_TUD_CDC_RX_BUFSIZE] = {0};
+ // uint32_t count = tud_cdc_read(buffer, sizeof(buffer));
+ // // tud_cdc_write(buffer, count);
+ // // tud_cdc_write_str("_cdc_\r\n");
+ // // tud_cdc_write_flush();
+
+ // writeConfig(buffer, count);
+
+ // init_keys();
+ // tud_cdc_write(&user_config, 3);
+ // tud_cdc_write_str('EOT');
+ // tud_cdc_write_flush();
+ // } else {
+ // // tud_cdc_write(&user_config, sizeof(user_config));
+ // // tud_cdc_write_flush();
+ // }
+ // }
+
+ if (web_serial_connected) {
+ if (tud_vendor_available()) {
+ uint8_t buffer[CFG_TUD_VENDOR_RX_BUFSIZE];
+ uint32_t count = tud_vendor_read(buffer, sizeof(buffer));
+ writeConfig(buffer, 3);
+
+ init_keys();
+ tud_vendor_write(&user_config, 3);
+ tud_vendor_write_flush();
+ }
+ }
+ // } else {
+ if ((should_send_consumer_report || should_send_keyboard_report) && tud_hid_ready()) {
if (tud_suspended()) {
tud_remote_wakeup();
} else {
if (should_send_consumer_report) {
should_send_consumer_report = 0;
- tud_hid_n_report(ITF_NUM_KEYBOARD, REPORT_ID_CONSUMER_CONTROL, &consumer_report, 2);
+ tud_hid_report(REPORT_ID_CONSUMER_CONTROL, &consumer_report, 2);
} else if (should_send_keyboard_report) {
should_send_keyboard_report = 0;
- tud_hid_n_keyboard_report(ITF_NUM_KEYBOARD, REPORT_ID_KEYBOARD, modifiers, keycodes);
+ tud_hid_keyboard_report(REPORT_ID_KEYBOARD, modifiers, keycodes);
}
}
- } else if (should_send_generic_inout_report && tud_hid_n_ready(ITF_NUM_GENERIC_INOUT)) {
- should_send_generic_inout_report = 0;
- generic_inout_report.duration = HAL_GetTick() - start;
- generic_inout_report.trigger_offset = user_config.trigger_offset;
- generic_inout_report.reset_threshold = user_config.reset_threshold;
- generic_inout_report.rapid_trigger_offset = user_config.rapid_trigger_offset;
- tud_hid_n_report(ITF_NUM_GENERIC_INOUT, 0, &generic_inout_report, HID_GENERIC_INOUT_REPORT_BUFFSIZE);
}
+ // }
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
@@ -549,58 +585,6 @@ void remove_from_hid_report(struct key *key, uint8_t layer) {
}
}
-void add_to_hid_generic_inout_report(struct key *key) {
- uint8_t added = 0;
- for (uint8_t i = 0; i < 6; i++) {
- if (generic_inout_report.keys[i].row == key->row && generic_inout_report.keys[i].column == key->column) {
- generic_inout_report.keys[i].row = key->row;
- generic_inout_report.keys[i].column = key->column;
- generic_inout_report.keys[i].idle_value = key->calibration.idle_value;
- generic_inout_report.keys[i].max_distance = key->calibration.max_distance;
- generic_inout_report.keys[i].value = key->state.value;
- generic_inout_report.keys[i].distance_8bits = key->state.distance_8bits;
- generic_inout_report.keys[i].status = key->actuation.status;
-
- added = 1;
- should_send_generic_inout_report = 1;
- break;
- }
- }
-
- if (!added) {
- for (uint8_t i = 0; i < 6; i++) {
- // check if value is 0, 0 means the report is empty
- if (generic_inout_report.keys[i].value == 0) {
- generic_inout_report.keys[i].row = key->row;
- generic_inout_report.keys[i].column = key->column;
- generic_inout_report.keys[i].idle_value = key->calibration.idle_value;
- generic_inout_report.keys[i].max_distance = key->calibration.max_distance;
- generic_inout_report.keys[i].value = key->state.value;
- generic_inout_report.keys[i].distance_8bits = key->state.distance_8bits;
- generic_inout_report.keys[i].status = key->actuation.status;
-
- should_send_generic_inout_report = 1;
- break;
- }
- }
- }
-}
-
-void remove_from_hid_generic_inout_report(struct key *key) {
- for (uint8_t i = 0; i < 6; i++) {
- if (generic_inout_report.keys[i].row == key->row && generic_inout_report.keys[i].column == key->column) {
- generic_inout_report.keys[i].row = 0;
- generic_inout_report.keys[i].column = 0;
- generic_inout_report.keys[i].idle_value = 0;
- generic_inout_report.keys[i].max_distance = 0;
- generic_inout_report.keys[i].value = 0;
- generic_inout_report.keys[i].distance_8bits = 0;
- generic_inout_report.keys[i].status = 0;
- break;
- }
- }
-}
-
uint8_t update_key_state(struct key *key) {
struct state state;
@@ -628,7 +612,6 @@ uint8_t update_key_state(struct key *key) {
if (key->state.distance == 0 && state.value >= key->calibration.idle_value - IDLE_VALUE_OFFSET) {
if (key->idle_counter >= IDLE_CYCLES_UNTIL_SLEEP) {
key->is_idle = 1;
- remove_from_hid_generic_inout_report(key);
return 0;
}
key->idle_counter++;
@@ -765,8 +748,6 @@ void update_key_actuation(struct key *key) {
}
break;
}
-
- add_to_hid_generic_inout_report(key);
}
void update_key(struct key *key) {
@@ -777,6 +758,8 @@ void update_key(struct key *key) {
update_key_actuation(key);
}
+// MARK: tud_* functions
+
// Invoked when received SET_PROTOCOL request
// protocol is either HID_PROTOCOL_BOOT (0) or HID_PROTOCOL_REPORT (1)
void tud_hid_set_protocol_cb(uint8_t instance, uint8_t protocol) {
@@ -814,12 +797,85 @@ uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_t
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) {
(void)report_id;
- if (instance == 1 && report_id == 0) {
- writeConfig(buffer, bufsize);
- // tud_hid_n_report(ITF_NUM_GENERIC_INOUT, 0, &user_config, HID_GENERIC_INOUT_REPORT_BUFFSIZE);
+ // if (instance == 1 && report_id == 0) {
+ // writeConfig(buffer, bufsize);
- init_keys();
+ // init_keys();
+ // }
+}
+
+// // Invoked when cdc when line state changed e.g connected/disconnected
+// void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) {
+// (void)itf;
+
+// // connected
+// if (dtr && rts) {
+// // print initial message when connected
+// tud_cdc_write(&user_config, 3);
+// tud_cdc_write_str('\r\n');
+// tud_cdc_write_flush();
+// }
+// }
+
+// // Invoked when CDC interface received data from host
+// void tud_cdc_rx_cb(uint8_t itf) {
+// (void)itf;
+// }
+
+// Invoked when a control transfer occurred on an interface of this class
+// Driver response accordingly to the request and the transfer stage (setup/data/ack)
+// return false to stall control endpoint (e.g unsupported request)
+bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) {
+ // nothing to with DATA & ACK stage
+ if (stage != CONTROL_STAGE_SETUP)
+ return true;
+
+ switch (request->bmRequestType_bit.type) {
+ case TUSB_REQ_TYPE_VENDOR:
+ switch (request->bRequest) {
+ case VENDOR_REQUEST_WEBUSB:
+ // match vendor request in BOS descriptor
+ // Get landing page url
+ return tud_control_xfer(rhport, request, (void *)(uintptr_t)&desc_url, desc_url.bLength);
+
+ case VENDOR_REQUEST_MICROSOFT:
+ if (request->wIndex == 7) {
+ // Get Microsoft OS 2.0 compatible descriptor
+ uint16_t total_len;
+ memcpy(&total_len, desc_ms_os_20 + 8, 2);
+
+ return tud_control_xfer(rhport, request, (void *)(uintptr_t)desc_ms_os_20, total_len);
+ } else {
+ return false;
+ }
+
+ default:
+ break;
+ }
+ break;
+
+ case TUSB_REQ_TYPE_CLASS:
+ if (request->bRequest == 0x22) {
+ // Webserial simulate the CDC_REQUEST_SET_CONTROL_LINE_STATE (0x22) to connect and disconnect.
+ web_serial_connected = (request->wValue != 0);
+
+ if (web_serial_connected) {
+ // MARK: WIP
+ tud_vendor_write(&user_config, 3);
+ tud_vendor_write_flush();
+ }
+
+ // response with status OK
+ return tud_control_status(rhport, request);
+ }
+ break;
+
+ default:
+ break;
}
+
+ // stall unknown request
+ return false;
}
/* USER CODE END 4 */
diff --git a/firmware/Core/Src/stm32f4xx_hal_msp.c b/firmware/Core/Src/stm32f4xx_hal_msp.c
index e8323cd..354018f 100644
--- a/firmware/Core/Src/stm32f4xx_hal_msp.c
+++ b/firmware/Core/Src/stm32f4xx_hal_msp.c
@@ -20,7 +20,6 @@
/* Includes ------------------------------------------------------------------*/
#include "main.h"
-
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
@@ -63,6 +62,7 @@
*/
void HAL_MspInit(void)
{
+
/* USER CODE BEGIN MspInit 0 */
/* USER CODE END MspInit 0 */
diff --git a/firmware/Core/Src/usb_descriptors.c b/firmware/Core/Src/usb_descriptors.c
index 2ffb668..0ab79f9 100644
--- a/firmware/Core/Src/usb_descriptors.c
+++ b/firmware/Core/Src/usb_descriptors.c
@@ -43,14 +43,17 @@ tusb_desc_device_t const desc_device =
{
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
- .bcdUSB = 0x0200,
- .bDeviceClass = 0x00,
- .bDeviceSubClass = 0x00,
- .bDeviceProtocol = 0x00,
+ .bcdUSB = 0x0210, // at least 2.1 or 3.x for BOS & webUSB
+
+ // 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)
+ .bDeviceClass = TUSB_CLASS_MISC,
+ .bDeviceSubClass = MISC_SUBCLASS_COMMON,
+ .bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0xCafe,
- .idProduct = USB_PID + 2,
+ .idProduct = USB_PID + 11,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
@@ -76,19 +79,12 @@ uint8_t const desc_hid_keyboard_report[] =
TUD_HID_REPORT_DESC_CONSUMER(HID_REPORT_ID(REPORT_ID_CONSUMER_CONTROL)),
};
-uint8_t const desc_hid_custom_report[] =
- {
- TUD_HID_REPORT_DESC_GENERIC_INOUT(HID_GENERIC_INOUT_REPORT_BUFFSIZE),
-};
-
// Invoked when received GET HID REPORT DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) {
if (instance == ITF_NUM_KEYBOARD) {
return desc_hid_keyboard_report;
- } else if (instance == ITF_NUM_GENERIC_INOUT) {
- return desc_hid_custom_report;
}
return NULL;
@@ -98,12 +94,11 @@ uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) {
// Configuration Descriptor
//--------------------------------------------------------------------+
-// #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + 2 * TUD_HID_DESC_LEN)
-#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_HID_INOUT_DESC_LEN)
+#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_VENDOR_DESC_LEN)
-#define EPNUM_KEYBOARD 0x81
-#define EPNUM_HID_CUSTOM_IN 0x82 // 0x80 | EPNUM_HID_CUSTOM_OUT
-#define EPNUM_HID_CUSTOM_OUT 0x83
+#define EPNUM_KEYBOARD 1
+#define EPNUM_VENDOR_IN 2
+#define EPNUM_VENDOR_OUT 2
uint8_t const desc_configuration[] =
{
@@ -111,8 +106,10 @@ uint8_t const desc_configuration[] =
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
// Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval
- TUD_HID_DESCRIPTOR(ITF_NUM_KEYBOARD, 4, HID_ITF_PROTOCOL_KEYBOARD, sizeof(desc_hid_keyboard_report), EPNUM_KEYBOARD, CFG_TUD_HID_EP_BUFSIZE, 10),
- TUD_HID_INOUT_DESCRIPTOR(ITF_NUM_GENERIC_INOUT, 5, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_custom_report), EPNUM_HID_CUSTOM_OUT, EPNUM_HID_CUSTOM_IN, HID_GENERIC_INOUT_REPORT_BUFFSIZE, 10),
+ TUD_HID_DESCRIPTOR(ITF_NUM_KEYBOARD, 6, HID_ITF_PROTOCOL_KEYBOARD, sizeof(desc_hid_keyboard_report), 0x80 | EPNUM_KEYBOARD, CFG_TUD_HID_EP_BUFSIZE, 10),
+
+ // Interface number, string index, EP Out & IN address, EP size
+ TUD_VENDOR_DESCRIPTOR(ITF_NUM_VENDOR, 5, EPNUM_VENDOR_OUT, 0x80 | EPNUM_VENDOR_IN, TUD_OPT_HIGH_SPEED ? 512 : 64),
};
// Invoked when received GET CONFIGURATION DESCRIPTOR
@@ -123,6 +120,73 @@ uint8_t const *tud_descriptor_configuration_cb(uint8_t index) {
return desc_configuration;
}
+//--------------------------------------------------------------------+
+// BOS Descriptor
+//--------------------------------------------------------------------+
+
+/* Microsoft OS 2.0 registry property descriptor
+Per MS requirements https://msdn.microsoft.com/en-us/library/windows/hardware/hh450799(v=vs.85).aspx
+device should create DeviceInterfaceGUIDs. It can be done by driver and
+in case of real PnP solution device should expose MS "Microsoft OS 2.0
+registry property descriptor". Such descriptor can insert any record
+into Windows registry per device/configuration/interface. In our case it
+will insert "DeviceInterfaceGUIDs" multistring property.
+
+GUID is freshly generated and should be OK to use.
+
+https://developers.google.com/web/fundamentals/native-hardware/build-for-webusb/
+(Section Microsoft OS compatibility descriptors)
+*/
+
+#define BOS_TOTAL_LEN (TUD_BOS_DESC_LEN + TUD_BOS_WEBUSB_DESC_LEN + TUD_BOS_MICROSOFT_OS_DESC_LEN)
+
+#define MS_OS_20_DESC_LEN 0xB2
+
+// BOS Descriptor is required for webUSB
+uint8_t const desc_bos[] =
+ {
+ // total length, number of device caps
+ TUD_BOS_DESCRIPTOR(BOS_TOTAL_LEN, 2),
+
+ // Vendor Code, iLandingPage
+ TUD_BOS_WEBUSB_DESCRIPTOR(VENDOR_REQUEST_WEBUSB, 1),
+
+ // Microsoft OS 2.0 descriptor
+ TUD_BOS_MS_OS_20_DESCRIPTOR(MS_OS_20_DESC_LEN, VENDOR_REQUEST_MICROSOFT)};
+
+uint8_t const *tud_descriptor_bos_cb(void) {
+ return desc_bos;
+}
+
+uint8_t const desc_ms_os_20[] =
+ {
+ // Set header: length, type, windows version, total length
+ U16_TO_U8S_LE(0x000A), U16_TO_U8S_LE(MS_OS_20_SET_HEADER_DESCRIPTOR), U32_TO_U8S_LE(0x06030000), U16_TO_U8S_LE(MS_OS_20_DESC_LEN),
+
+ // Configuration subset header: length, type, configuration index, reserved, configuration total length
+ U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_CONFIGURATION), 0, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN - 0x0A),
+
+ // Function Subset header: length, type, first interface, reserved, subset length
+ U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_FUNCTION), ITF_NUM_VENDOR, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN - 0x0A - 0x08),
+
+ // MS OS 2.0 Compatible ID descriptor: length, type, compatible ID, sub compatible ID
+ U16_TO_U8S_LE(0x0014), U16_TO_U8S_LE(MS_OS_20_FEATURE_COMPATBLE_ID), 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sub-compatible
+
+ // MS OS 2.0 Registry property descriptor: length, type
+ U16_TO_U8S_LE(MS_OS_20_DESC_LEN - 0x0A - 0x08 - 0x08 - 0x14), U16_TO_U8S_LE(MS_OS_20_FEATURE_REG_PROPERTY),
+ U16_TO_U8S_LE(0x0007), U16_TO_U8S_LE(0x002A), // wPropertyDataType, wPropertyNameLength and PropertyName "DeviceInterfaceGUIDs\0" in UTF-16
+ 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, 'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00, 't', 0x00, 'e', 0x00,
+ 'r', 0x00, 'f', 0x00, 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00, 'U', 0x00, 'I', 0x00, 'D', 0x00, 's', 0x00, 0x00, 0x00,
+ U16_TO_U8S_LE(0x0050), // wPropertyDataLength
+ // bPropertyData: “{975F44D9-0D08-43FD-8B3E-127CA8AFFF9D}”.
+ '{', 0x00, '9', 0x00, '7', 0x00, '5', 0x00, 'F', 0x00, '4', 0x00, '4', 0x00, 'D', 0x00, '9', 0x00, '-', 0x00,
+ '0', 0x00, 'D', 0x00, '0', 0x00, '8', 0x00, '-', 0x00, '4', 0x00, '3', 0x00, 'F', 0x00, 'D', 0x00, '-', 0x00,
+ '8', 0x00, 'B', 0x00, '3', 0x00, 'E', 0x00, '-', 0x00, '1', 0x00, '2', 0x00, '7', 0x00, 'C', 0x00, 'A', 0x00,
+ '8', 0x00, 'A', 0x00, 'F', 0x00, 'F', 0x00, 'F', 0x00, '9', 0x00, 'D', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00};
+
+TU_VERIFY_STATIC(sizeof(desc_ms_os_20) == MS_OS_20_DESC_LEN, "Incorrect size");
+
//--------------------------------------------------------------------+
// String Descriptors
//--------------------------------------------------------------------+
@@ -142,8 +206,8 @@ char const *string_desc_arr[] =
"Heiso", // 1: Manufacturer
"Macrolev", // 2: Product
"345678", // 3: Serials will use unique ID if possible
- "Keyboard Interface", // 4: Interface 1 String
- "Custom Interface", // 5: Interface 2 String
+ "WebUSB Interface", // 5: Interface 2 String
+ "Keyboard Interface", // 6: Interface 3 String
};
static uint16_t _desc_str[32 + 1];
diff --git a/firmware/macrolev.ioc b/firmware/macrolev.ioc
index c531dd3..989f941 100644
--- a/firmware/macrolev.ioc
+++ b/firmware/macrolev.ioc
@@ -44,8 +44,8 @@ Mcu.PinsNb=16
Mcu.ThirdPartyNb=0
Mcu.UserConstants=
Mcu.UserName=STM32F411CEUx
-MxCube.Version=6.10.0
-MxDb.Version=DB.6.0.100
+MxCube.Version=6.11.0
+MxDb.Version=DB.6.0.110
NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
NVIC.ForceEnableDMAVector=true
diff --git a/firmware/tinyusb b/firmware/tinyusb
index 08f9ed6..d816a9b 160000
--- a/firmware/tinyusb
+++ b/firmware/tinyusb
@@ -1 +1 @@
-Subproject commit 08f9ed67c92421cbd0bc09270d2f363886681866
+Subproject commit d816a9bdf892daeb35b88b9a469599fcda432c55
diff --git a/web-app/app/components/serial-provider.tsx b/web-app/app/components/serial-provider.tsx
new file mode 100644
index 0000000..f4c7002
--- /dev/null
+++ b/web-app/app/components/serial-provider.tsx
@@ -0,0 +1,298 @@
+import {
+ createContext,
+ useContext,
+ useEffect,
+ useRef,
+ useState,
+ type PropsWithChildren,
+} from 'react'
+
+// RESOURCES:
+// https://web.dev/serial/
+// https://reillyeon.github.io/serial/#onconnect-attribute-0
+// https://codelabs.developers.google.com/codelabs/web-serial
+
+export const EOT = '\r\n'
+
+export type PortState = 'closed' | 'closing' | 'open' | 'opening'
+
+export type SerialMessage = {
+ value: string
+ timestamp: number
+}
+
+type SerialMessageCallback = (message: SerialMessage) => void
+
+export interface SerialContextValue {
+ canUseSerial: boolean
+ hasTriedAutoconnect: boolean
+ portState: PortState
+ connect(): Promise
+ disconnect(): void
+ subscribe(callback: SerialMessageCallback): () => void
+ write(buffer: Uint8Array): void
+}
+export const SerialContext = createContext({
+ canUseSerial: false,
+ hasTriedAutoconnect: false,
+ connect: () => Promise.resolve(false),
+ disconnect: () => {},
+ portState: 'closed',
+ subscribe: () => () => {},
+ write: (buffer: Uint8Array) => {},
+})
+
+export const useSerial = () => useContext(SerialContext)
+
+interface SerialProviderProps {}
+export const SerialProvider = ({ children }: PropsWithChildren) => {
+ const [canUseSerial] = useState(() => 'serial' in navigator)
+
+ const [portState, setPortState] = useState('closed')
+ const [hasTriedAutoconnect, setHasTriedAutoconnect] = useState(false)
+ const [hasManuallyDisconnected, setHasManuallyDisconnected] = useState(false)
+
+ const portRef = useRef(null)
+ const readerRef = useRef(null)
+ const readerClosedPromiseRef = useRef>(Promise.resolve())
+
+ const currentSubscriberIdRef = useRef(0)
+ const subscribersRef = useRef