Making WebUSB Work with the Teensy - What is PluggableUSB?

Status
Not open for further replies.
webHID feasibility experiments

I did a few feasibility experiments with the webHID interface of chrome. This interface allows a web page to communicate with a Teensy over its HID interface. Since firmware uploading is also done over HID, the webHID interface would also allow firmware uploads through a web browser. If someone is interested, here some information and a example which shows how to exchange hid reports between a webpage and a Teensy. (I only tested the Chrome implementation of the interface. No idea if other browsers support it as well).

Here instructions for a quick start
  • To enable the Interface in Chrome browse to chrome://flags/ , search for "Experimental Web Platform features" and change the flag to "Enabled"
  • Compile the following test firmware with usb option "RAW HID".

    Code:
    #include "Arduino.h"
    
    void setup()
    {
      pinMode(LED_BUILTIN, OUTPUT);
    }
    
    uint8_t cnt = 0; 
    uint8_t outgoingReport[64];
    uint8_t incommingReport[64];
    
    void loop()
    {
      // sending report
      outgoingReport[0] = cnt++;  
      usb_rawhid_send(outgoingReport, 100);
      
      // receiving report
      if (usb_rawhid_recv(incommingReport, 100) > 0) // Wait for a received raw hid report
      {
         if (incommingReport[0] == 0xAA )
         {
            digitalToggleFast(LED_BUILTIN);
         }
      }
    
      delay(100);
    }
    The firmware sends a simple report with an incrementing byte in position 0. Additionally it reads incoming reports. If it finds 0xAA in the first byte it toggles the LED. I tested it with a T3.2 but it should work with any T3x or T4x.
  • Browse to the test web page: https://lunoptics.com/webHID/ where you can clearly see that I'm not a web designer :)

Anmerkung 2020-07-23 235059.jpg

The connect button opens a browser generated list box which lists all raw hid Teensies. I.e. all boards with vid: 0x16C0, pid: 0x486, usagepage: 0xFFAB, usage: 0x200. Select the the one you want to connect to.

Anmerkung 2020-07-23 235240.jpg

If everything works you should see the incrementing bytes as they are sent from the Teensy. You can toggle the LED on your board by pressing the Toggle button.


index.html
Code:
<!DOCTYPE html>
<html>

<head>
    <title>Page Title</title>
</head>

<body>    

    <h1>WebHID Tester</h1>
    
    <button type="button" id="connectButton">Connect to Teensy</button>

    <p id="inputReport">no data</p>

    <button type="button" id="sendButton">Toggle LED</button>

    <script src="js/main.js"></script>
</body>

</html>

And here the corresponding java script. Please note that I and java script probably won't get friends in this life. So, this probably can be done much better and more elegant. For the time being I'm happy that it works at all :)
Code:
let requestButton = document.getElementById('connectButton');
let ledButton = document.getElementById('sendButton');
let inputReportDisplay = document.getElementById('inputReport')

let rawHidFilter = { vendorId: 0x16C0, productId: 0x0486, usagePage: 0xFFAB, usage: 0x0200 }; // RawHID interface
let serEmuFilter = { vendorId: 0x16C0, productId: 0x0486, usagePage: 0xFFC9, usage: 0x0004 }; // SerEMU interface
let rawHidParams = { filters: [rawHidFilter] };
let serEmuParams = { filters: [serEmuFilter] };

let outputReportId = 0x00;
let outputReport = new Uint8Array([64]);

let device;

function handleInputReport(e) {
    let firstByte = e.data.getUint8(0); // read first byte of 64byte report
    inputReportDisplay.innerHTML = "received report (first byte):" + firstByte;   
};

function toggleLED()
{
    outputReport[0] = 0xAA;   // send a report with some data to the Teensy   
    device.sendReport(outputReportId,outputReport);
}

async function connect() {
    let devices = await navigator.hid.requestDevice(rawHidParams); //get allowance to connect to a rawHID Teensy
    if (devices === null || devices.length == 0) return;
    
    device = devices[0];

    console.log('found device: ' + device.productName);
    if (device.open()) {
        console.log('Opened device: ' + device.productName);
        device.addEventListener('inputreport', handleInputReport);        
    };
};


requestButton.addEventListener('click', connect);
ledButton.addEventListener('click', toggleLED);


Github Repo: https://github.com/luni64/webHID
General info about webHID https://github.com/robatwilliams/awesome-webhid
 
Last edited by a moderator:
@LUNI - Works with a Teensy 4.1 on Windows 10 with (new chrome based) EDGE browser!

For EDGE the experimental feature enable is 'url' :: edge://flags/

Note: Edited prior p#1 to add needed closing paren on this line :: if (incommingReport[0] == 0xAA )
 
Very cool that it can used for Teensy I/O.

As far as programming glancing at the linked github developers.chrome.com/apps/hid Not sure if the needed elements are supported? Paul and @koromix with TyCommander know more about telling a Teensy to go into bootloader RawHID - and then transfer the HEX file to complete programming.

@luni - using :: USBDeview >> http://www.nirsoft.net/

The RawHID as coded above versus the RawHID presented in bootloader looks very similar from the descriptor info.

Merging the two HTML reports I saw in the T_4.1 - and hacking the lines into two parts to fit on screen looks like this if saved to HTML:
Code:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html><head><title>USB Devices List</title></head>
<body>

 <h3>USB Devices List</h3>
<br><h4>Created by using <a href="http://www.nirsoft.net/" target="newwin">USBDeview</a></h4><p><table border="1" cellpadding="5"><tr bgcolor="E0E0E0">
<th>Device Name
<th>Description
<th>Device Type
<th>Connected
<th>Safe To Unplug
<th>Disabled
<th>USB Hub
<th>Drive Letter
<th>Serial Number
<th>Created Date
<th>Last Plug/Unplug Date
<th>VendorID
<th>ProductID
<th>Firmware Revision
<th>USB Class
<th>USB SubClass
<th>USB Protocol
<th>Hub / Port

<tr><td bgcolor=#FFFFF0 nowrap>Port_#0001.Hub_#0008<td bgcolor=#FFFEF0 nowrap>USB Composite Device<td bgcolor=#FFFDF0 nowrap>Unknown<td bgcolor=#FFFCF0 nowrap>No<td bgcolor=#FFFBF0 nowrap>Yes<td bgcolor=#FFFAF0 nowrap>No<td bgcolor=#FFF9F0 nowrap>No<td bgcolor=#FFF9F0 nowrap> <td bgcolor=#FFF8F0 nowrap>7684130<td bgcolor=#FFF7F0 nowrap>7/23/2020 4:13:55 PM<td bgcolor=#FFF6F0 nowrap>7/7/2020 11:38:52 PM<td bgcolor=#FFF5F0 nowrap>16c0<td bgcolor=#FFF4F0 nowrap>0486<td bgcolor=#FFF3F0 nowrap>2.80<td bgcolor=#FFF3F0 nowrap>00<td bgcolor=#FFF2F0 nowrap>00<td bgcolor=#FFF1F0 nowrap>00<td bgcolor=#FFF0F0 nowrap> 
</table>

 <h3>USB Devices List</h3>
<br><h4>Created by using <a href="http://www.nirsoft.net/" target="newwin">USBDeview</a></h4><p><table border="1" cellpadding="5"><tr bgcolor="E0E0E0">
<th>ParentId Prefix
<th>Service Name
<th>Service Description
<th>Driver Filename
<th>Device Class
<th>Device Mfg
<th>Friendly Name
<th>Power
<th>USB Version
<th>Driver Description
<th>Driver Version
<th>Driver InfSection
<th>Driver InfPath
<th>Instance ID
<th>Capabilities
<tr><td bgcolor=#FCF0F3 nowrap>8&275aac33&0<td bgcolor=#FBF0F3 nowrap>usbccgp<td bgcolor=#FAF0F4 nowrap>@usb.inf,%GenericParent.SvcDesc%;Microsoft USB Generic Parent Driver<td bgcolor=#F9F0F5 nowrap>usbccgp.sys<td bgcolor=#F8F0F6 nowrap> <td bgcolor=#F7F0F7 nowrap>(Standard USB Host Controller)<td bgcolor=#F6F0F8 nowrap> <td bgcolor=#F6F0F9 nowrap> <td bgcolor=#F5F0F9 nowrap>2.00<td bgcolor=#F4F0FA nowrap>USB Composite Device<td bgcolor=#F3F0FB nowrap>10.0.19041.1<td bgcolor=#F2F0FC nowrap>Composite.Dev.NT<td bgcolor=#F1F0FD nowrap>usb.inf<td bgcolor=#F0F0FE nowrap>USB\VID_16C0&PID_0486\7684130<td bgcolor=#F0F0FF nowrap>Removable, UniqueID, SurpriseRemovalOK
</table>





 <h3>USB Devices List</h3>
<br><h4>Created by using <a href="http://www.nirsoft.net/" target="newwin">USBDeview</a></h4><p><table border="1" cellpadding="5"><tr bgcolor="E0E0E0">
<th>Device Name
<th>Description
<th>Device Type
<th>Connected
<th>Safe To Unplug
<th>Disabled
<th>USB Hub
<th>Drive Letter
<th>Serial Number
<th>Created Date
<th>Last Plug/Unplug Date
<th>VendorID
<th>ProductID
<th>Firmware Revision
<th>USB Class
<th>USB SubClass
<th>USB Protocol
<th>Hub / Port
<tr><td bgcolor=#FFFFF0 nowrap>Port_#0001.Hub_#0008<td bgcolor=#FFFEF0 nowrap>USB Composite Device<td bgcolor=#FFFDF0 nowrap>Unknown<td bgcolor=#FFFCF0 nowrap>No<td bgcolor=#FFFBF0 nowrap>Yes<td bgcolor=#FFFAF0 nowrap>No<td bgcolor=#FFF9F0 nowrap>No<td bgcolor=#FFF9F0 nowrap> <td bgcolor=#FFF8F0 nowrap>7684130<td bgcolor=#FFF7F0 nowrap>7/23/2020 4:13:55 PM<td bgcolor=#FFF6F0 nowrap>7/7/2020 11:38:52 PM<td bgcolor=#FFF5F0 nowrap>16c0<td bgcolor=#FFF4F0 nowrap>0486<td bgcolor=#FFF3F0 nowrap>2.80<td bgcolor=#FFF3F0 nowrap>00<td bgcolor=#FFF2F0 nowrap>00<td bgcolor=#FFF1F0 nowrap>00<td bgcolor=#FFF0F0 nowrap> 
</table>

<br><h4>Created by using <a href="http://www.nirsoft.net/" target="newwin">USBDeview</a></h4><p><table border="1" cellpadding="5"><tr bgcolor="E0E0E0">
<th>ParentId Prefix
<th>Service Name
<th>Service Description
<th>Driver Filename
<th>Device Class
<th>Device Mfg
<th>Friendly Name
<th>Power
<th>USB Version
<th>Driver Description
<th>Driver Version
<th>Driver InfSection
<th>Driver InfPath
<th>Instance ID
<th>Capabilities
<tr><td bgcolor=#FCF0F3 nowrap>8&275aac33&0<td bgcolor=#FBF0F3 nowrap>usbccgp<td bgcolor=#FAF0F4 nowrap>@usb.inf,%GenericParent.SvcDesc%;Microsoft USB Generic Parent Driver<td bgcolor=#F9F0F5 nowrap>usbccgp.sys<td bgcolor=#F8F0F6 nowrap> <td bgcolor=#F7F0F7 nowrap>(Standard USB Host Controller)<td bgcolor=#F6F0F8 nowrap> <td bgcolor=#F6F0F9 nowrap> <td bgcolor=#F5F0F9 nowrap>2.00<td bgcolor=#F4F0FA nowrap>USB Composite Device<td bgcolor=#F3F0FB nowrap>10.0.19041.1<td bgcolor=#F2F0FC nowrap>Composite.Dev.NT<td bgcolor=#F1F0FD nowrap>usb.inf<td bgcolor=#F0F0FE nowrap>USB\VID_16C0&PID_0486\7684130<td bgcolor=#F0F0FF nowrap>Removable, UniqueID, SurpriseRemovalOK
</table>

</body></html>
 
As far as programming glancing at the linked github developers.chrome.com/apps/hid Not sure if the needed elements are supported? Paul and @koromix with TyCommander know more about telling a Teensy to go into bootloader RawHID - and then transfer the HEX file to complete programming.

Starting the bootloader is quite simple. You'd just send some magic bytes to the feature report of the serEmu interface which will restart the Teensy in bootloader mode. In bootloader mode the Teensy is still a HID device with another PID. The actual programming requires nothing but sending reports to this device so this is also not difficult to do. I did this in c# (TeensySharp https://github.com/luni64/TeensySharp) and there are a couple of posts in this forum using various other languages. So translating to javascript shouldn't be too difficult for anyone fluent in that language ( which I'm not ).

BUT: You'll face the following problems:

  • For security reasons you can not automatically connect to the device. You need to do this by some manual dialog (like the one which is opened by my connect button)
  • After switching to bootloader mode the Teensy reconnects as bootloader device. For the browser this is a different device and you have to manually connect to this "new" device.
  • If the Teensy doesn't implement the SerEmu interface but Serial instead, you can not activate the bootloader mode by sending magic bytes to SerEmu. In this case you'd use the Serial interface and set its baudrate to 136 to start the bootloader. But this can not be done from within webHID.
So, one can probably get uploading going using webHID without much difficulties but I doubt that the user experience will be satisfactory since it requires those manual reconnecting steps. Of course, if one can tell users to press the programming button prior to uploading things get much easier... (for the programmer, not for the user...)

----
BTW: thanks for trying and fixing the sketch. Always the same with last minute changes done directly in the post... :-/
 
Last edited:
That was the odd note in the HTML posted - both showed the same VID and PID :: USB\VID_16C0&PID_0486\7684130 , But in bootloader mode it didn't come up in the 'manual dialog'.
>> Though I just noticed HACKED HTML ended up with the Bootloader code twice :(

Oh - yeah forgot you did the upload with TeensySharp and know the upload process steps.
 
VID and PID the same but some other diffs here if they get presented in browser: SKETCJ>bcdDevice: 0x0280 versus bootloader>bcdDevice: 0x0105

Here it is as RawHID with sketch - info from MSFT USB View
Code:
Device Descriptor:
bcdUSB:             0x0200
bDeviceClass:         0x00
bDeviceSubClass:      0x00
bDeviceProtocol:      0x00
bMaxPacketSize0:      0x40 (64)
[B]idVendor:           0x16C0
idProduct:          0x0486
bcdDevice:          0x0280[/B]
iManufacturer:        0x01
0x0409: "Teensyduino"
iProduct:             0x02
0x0409: "Teensyduino RawHID"
iSerialNumber:        0x03
0x0409: "7684130"
bNumConfigurations:   0x01

ConnectionStatus: DeviceConnected
Current Config Value: 0x01
Device Bus Speed:     High
Device Address:       0x19
Open Pipes:              4

Endpoint Descriptor:
bEndpointAddress:     0x83  IN
Transfer Type:   Interrupt
wMaxPacketSize:     0x0040 (64)
bInterval:            0x01

Endpoint Descriptor:
bEndpointAddress:     0x04  OUT
Transfer Type:   Interrupt
wMaxPacketSize:     0x0040 (64)
bInterval:            0x01

Endpoint Descriptor:
bEndpointAddress:     0x82  IN
Transfer Type:   Interrupt
wMaxPacketSize:     0x0040 (64)
bInterval:            0x01

Endpoint Descriptor:
bEndpointAddress:     0x02  OUT
Transfer Type:   Interrupt
wMaxPacketSize:     0x0020 (32)
bInterval:            0x02

Configuration Descriptor:
wTotalLength:       0x0049
bNumInterfaces:       0x02
bConfigurationValue:  0x01
iConfiguration:       0x00
bmAttributes:         0xC0 (Bus Powered Self Powered )
MaxPower:             0x32 (100 Ma)

Interface Descriptor:
bInterfaceNumber:     0x00
bAlternateSetting:    0x00
bNumEndpoints:        0x02
bInterfaceClass:      0x03 (HID)
bInterfaceSubClass:   0x00
bInterfaceProtocol:   0x00
iInterface:           0x00

HID Descriptor:
bcdHID:             0x0111
bCountryCode:         0x00
bNumDescriptors:      0x01
bDescriptorType:      0x22
wDescriptorLength:  0x001C

Endpoint Descriptor:
bEndpointAddress:     0x83  IN
Transfer Type:   Interrupt
wMaxPacketSize:     0x0040 (64)
bInterval:            0x01

Endpoint Descriptor:
bEndpointAddress:     0x04  OUT
Transfer Type:   Interrupt
wMaxPacketSize:     0x0040 (64)
bInterval:            0x01

Interface Descriptor:
bInterfaceNumber:     0x01
bAlternateSetting:    0x00
bNumEndpoints:        0x02
bInterfaceClass:      0x03 (HID)
bInterfaceSubClass:   0x00
bInterfaceProtocol:   0x00
iInterface:           0x00

HID Descriptor:
bcdHID:             0x0111
bCountryCode:         0x00
bNumDescriptors:      0x01
bDescriptorType:      0x22
wDescriptorLength:  0x0021

Endpoint Descriptor:
bEndpointAddress:     0x82  IN
Transfer Type:   Interrupt
wMaxPacketSize:     0x0040 (64)
bInterval:            0x01

Endpoint Descriptor:
bEndpointAddress:     0x02  OUT
Transfer Type:   Interrupt
wMaxPacketSize:     0x0020 (32)
bInterval:            0x02

And the same in Bootloader:
Code:
Device Descriptor:
bcdUSB:             0x0200
bDeviceClass:         0x00
bDeviceSubClass:      0x00
bDeviceProtocol:      0x00
bMaxPacketSize0:      0x40 (64)
[B]idVendor:           0x16C0
idProduct:          0x0478
bcdDevice:          0x0105[/B]
iManufacturer:        0x00
iProduct:             0x00
iSerialNumber:        0x01
0x0409: "000BB99D"
bNumConfigurations:   0x01

ConnectionStatus: DeviceConnected
Current Config Value: 0x01
Device Bus Speed:     High
Device Address:       0x1A
Open Pipes:              1

Endpoint Descriptor:
bEndpointAddress:     0x81  IN
Transfer Type:   Interrupt
wMaxPacketSize:     0x0008 (8)
bInterval:            0x10

Configuration Descriptor:
wTotalLength:       0x0022
bNumInterfaces:       0x01
bConfigurationValue:  0x01
iConfiguration:       0x00
bmAttributes:         0xC0 (Bus Powered Self Powered )
MaxPower:             0x32 (100 Ma)

Interface Descriptor:
bInterfaceNumber:     0x00
bAlternateSetting:    0x00
bNumEndpoints:        0x01
bInterfaceClass:      0x03 (HID)
bInterfaceSubClass:   0x00
bInterfaceProtocol:   0x00
iInterface:           0x00

HID Descriptor:
bcdHID:             0x0111
bCountryCode:         0x00
bNumDescriptors:      0x01
bDescriptorType:      0x22
wDescriptorLength:  0x0016

Endpoint Descriptor:
bEndpointAddress:     0x81  IN
Transfer Type:   Interrupt
wMaxPacketSize:     0x0008 (8)
bInterval:            0x10
 
In non bootloader mode the bcdDevice identifies the board type. See here for a list: https://github.com/luni64/TeensySha...13/src/TeensySharp/TeensyWatcher.cs#L141-L165. So, you were obviously connecting a T4.1 (which is not yet in the list I just noticed...)

In bootloader mode it identifies something else (maybe the bootloader version?). In bootloader mode you can find out the board type by looking at the HID usage: https://github.com/luni64/TeensySha...13/src/TeensySharp/TeensyWatcher.cs#L195-L209
 
Indeed a T_4.1 as noted in p#27.

Saw USAGE noted in the 'filters' - but neither of the win USB viewers showed that name on a value to compare.

The first nirsoft tool shows 'Firmware Revision' in both cases as 2.80 as a separate entry on this T_4.1, and that not exposed as that in the MSFT USB View.

'Capabilities' - shown also in first tool only - but they looked to have the same value in either sketch or bnootloader
 
Saw USAGE noted in the 'filters' - but neither of the win USB viewers showed that name on a value to compare.
The HID usage and usage page are defined in the HID descriptor. You are looking at the device descriptor.

'Capabilities' - shown also in first tool only - but they looked to have the same value in either sketch or bootloader
Capability is a completely different story and has nothing to to with HID usage pages. Actually, I never understood what kind of capabilities are stored there.
 
I just came across this thread, I've been messing with WebHID as well but to simulate the Serial Monitor, i'm able to easily read from the serial monitor and print out the text but i'm still trying to figure out how to send data to the serial monitor as if i was typing it and hitting send.

have you guys done this?
 
I'm not sure if I understand what you mean by "sending data to the Serial monitor"?. Do you want to send some data from your web site through WebHID to the Teensy and the Teensy then takes this data and sends it via Serial to the SerMon?

If so, the user WIKI has a page showing how to receive HID reports and send them to SerMon. https://github.com/TeensyUser/doc/wiki/Raw-HID#ReceivingdataonthePC (chapter sending reports to the teensy).
Just replace the PC side code by some java script on your web page. The example code from #26 should show you how this works.

Have you been able to connect to your teensy from my web site https://lunoptics.com/webHID (again using the teensy firmware from #26)
 
So yeah basically i can connect from my web app uing WebHID and get any data that is printed on the serial monitor to my app, so lets say i have an sketch that just prints out "hello" every 10 seconds, my app will receive that hello and print it out on the screen, now i want to also send strings just like on teh serial monitor you can send a string and your sketch can read it using Serial.read().

Right now my app will mirror the Serial Monitor, anything thats printed on it will be printed on my app.

I'll try your website later today but likely it will work fine, i can easily connect from chrome to the teensy via WebHID


Screen Shot 2020-08-13 at 2.17.45 PM.jpg
 
now i want to also send strings just like on the serial monitor
In your Web app you just copy your string to the 64 byte report and send it. On the teensy you receive the report, convert (basically cast it) to a cstr and send it to Serial with the usual printXX functions. The linked user WIKI page shows how to receive the reports on the Teensy. Actually, this is all very easy.

This WebHID interface would be good to quickly hack some platform independent Teensy GUI by simply using a browser. Obviously there is a huge pool of graphical controls available for js which could be utilized easily. If I find some time I'll do a quick proof of principle example.

I'll try your website later today but likely it will work fine, i can easily connect from chrome to the teensy via WebHID
Since you can already connect you won't find anything new on this page. It simple sends and receives frames to and from the connected Teensy.
 
the WebHID provides a way to send a report, the sendReport(reportId, byteArray) so far no luck sending to the teensy, is the bytearray supposed to always be 64bytes? i wasn't sure about that, i'm converting a string to a bytearray on javascript and sending it out with a 0 report id, the issue is i'm not sure what this report id is, i gotta dig deeper
 
awesome thank you, I was using 0 as the reportId, i'm guessing when you SEND you have to include a EOL at the end or is there anything is that the has to be part of the payload? just a string casted to bytes?
 
It just sends the 64 bytes. It doesn't care or understand what those bytes are. So it is up to you to send eol if you want or need it.
 
weird, it will not send 64 byte arrays but it will send 32 byte arrays, i managed now to send it data! I was thinking about release a web app that emulates the teensy serial monitor tho i'm not sure how many users will want to set the chrome flag for experimental features
 
weird, it will not send 64 byte arrays but it will send 32 byte arrays, i managed now to send it data! I was thinking about release a web app that emulates the teensy serial monitor tho i'm not sure how many users will want to set the chrome flag for experimental features

Always a fun option - enabled the EXP here in the new chrome MSFT Edge to work - so it is an option that might come in handy or help with future development.
 
it turned out that i was sending data to my teensy the whole time but in my serial input code i was looking for an '\n' character and for some reason when ascii 10 is sent from the WebHID API it wouldn't be read the same by the teensy, so i changed my code to look for ascii 10 as the end of the serial input and it worked just fine

I put together a simple Web App to do test it out and works like a charm so far

If you guys wanna try it

https://www.roxxxtar.com/apps/teensysm/
 
nope, it's just a standalone serial monitor just like arduino, if your sketch does any Serial.print the emulator will display it, if your sketch reads from the serial monitor, you can send it data from the input
 
Status
Not open for further replies.
Back
Top