Hello all,
I am struggling to utilize WebUSB by having my website connect to Arduino Leonardo (Beetle). I am able to make it connect with Python, so I know there is nothing wrong with the MCU. All I would like to do is have my website be able to turn on an LED light (external or built-in, I have both set up to see if either LED will turn on), but I cannot get past the connection errors that I'm experiencing with it. Any assistance would be greatly appreciated as I have spent countless time on searching Google for answers. I am sure it is something simple that I am overlooking.
The following is my my sketch.
#include <WebUSB.h>
/**
* Creating an instance of WebUSBSerial will add an additional USB interface to
* the device that is marked as vendor-specific (rather than USB CDC-ACM) and
* is therefore accessible to the browser.
*
* The URL here provides a hint to the browser about what page the user should
* navigate to to interact with the device.
*/
WebUSB WebUSBSerial(1, "");
const int ledPin = 13;
void setup() {
WebUSBSerial.begin(9600);
while (!WebUSBSerial) {}
delay(100);
SerialUSB.println("Start!");
WebUSBSerial.write("Sketch begins");
WebUSBSerial.flush();
pinMode(ledPin, OUTPUT);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
if (WebUSBSerial && WebUSBSerial.available()) {
int byte = WebUSBSerial.read();
digitalWrite(LED_BUILTIN, HIGH);
if (byte == 'H') {
SerialUSB.println("LED ON");
WebUSBSerial.write("Turning LED on.");
digitalWrite(ledPin, HIGH);
} else if (byte == 'L') {
SerialUSB.println("LED OFF");
WebUSBSerial.write("Turning LED off.");
digitalWrite(ledPin, LOW);
}
WebUSBSerial.flush();
}
}
The following is my web page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<button id="connect">Connect</button>
<p>
<button id="on">LED ON</button>
<button id="off">LED OFF</button>
</p>
<script src="serial.js"></script>
<script>
var port;
let connectButton = document.querySelector('#connect');
let onButton = document.querySelector('#on');
let offButton = document.querySelector('#off');
let textDecoder = new TextDecoder();
let textEncoder = new TextEncoder();
onButton.addEventListener('click', function(event) {
if (port !== undefined) {
port.send(textEncoder.encode('H')).catch(error => {
console.log('Send error: ' + error);
});
console.log('Sending H');
}
});
offButton.addEventListener('click', function(event) {
if (port !== undefined) {
port.send(textEncoder.encode('L')).catch(error => {
console.log('Send error: ' + error);
});
console.log('Sending L');
}
});
connectButton.addEventListener('click', function() {
if (port) { // If port is already connected, disconnect it
connectButton.textContent = 'Connect';
port.disconnect();
port = null;
console.log('Device is disconnected.');
} else { // If there is no port, then connect to a new port
console.log('Attempting to connect to the device.');
serial.requestPort().then(selectedPort => {
port = selectedPort;
console.log(port);
port.connect().then(() => {
console.log('Device is connected to Product ID: ' + port.device_.productId.toString(16) + ' and Vendor ID: ' + port.device_.vendorId.toString(16));
connectButton.textContent = 'Disconnect';
port.onReceive = data => { console.log(textDecoder.decode(data))};
port.onReceiveError = error => { console.log('Receive error: ' + error)};
}, error => { console.log('Connection error: ' + error) });
}).catch(error => { console.log('Connection error: ' + error) });
}
});
</script>
The following is the serial.js:
var serial = {};
(function() {
'use strict';
serial.getPorts = function() {
return navigator.usb.getDevices().then(devices => {
return devices.map(device => new serial.Port(device));
});
};
serial.requestPort = function() {
const filters = [
{ 'vendorId': 0x2341, 'productId': 0x8036 }, // Arduino Leonardo
{ 'vendorId': 0x2341, 'productId': 0x8037 }, // Arduino Micro
{ 'vendorId': 0x2341, 'productId': 0x804d }, // Arduino/Genuino Zero
{ 'vendorId': 0x2341, 'productId': 0x804e }, // Arduino/Genuino MKR1000
{ 'vendorId': 0x2341, 'productId': 0x804f }, // Arduino MKRZERO
{ 'vendorId': 0x2341, 'productId': 0x8050 }, // Arduino MKR FOX 1200
{ 'vendorId': 0x2341, 'productId': 0x8052 }, // Arduino MKR GSM 1400
{ 'vendorId': 0x2341, 'productId': 0x8053 }, // Arduino MKR WAN 1300
{ 'vendorId': 0x2341, 'productId': 0x8054 }, // Arduino MKR WiFi 1010
{ 'vendorId': 0x2341, 'productId': 0x8055 }, // Arduino MKR NB 1500
{ 'vendorId': 0x2341, 'productId': 0x8056 }, // Arduino MKR Vidor 4000
{ 'vendorId': 0x2341, 'productId': 0x8057 }, // Arduino NANO 33 IoT
{ 'vendorId': 0x239A, 'productId': 0x000E }, // Adafruit ItsyBitsy 32u4
{ 'vendorId': 0x239A, 'productId': 0x800D } // Adafruit ItsyBitsy 32u4
];
return navigator.usb.requestDevice({ 'filters': filters }).then(
device => new serial.Port(device)
);
}
serial.Port = function(device) {
this.device_ = device;console.log('device', device);console.log('interface number', this.interfaceNumber);
this.interfaceNumber_ = 2; // original interface number of WebUSB Arduino demo
this.endpointIn_ = 5; // original in endpoint ID of WebUSB Arduino demo
this.endpointOut_ = 4; // original out endpoint ID of WebUSB Arduino demo
console.log('interfaces', device.configuration.interfaces);
};
serial.Port.prototype.connect = function() {
let readLoop = () => {
this.device_.transferIn(this.endpointIn_, 64).then(result => {
this.onReceive(result.data);console.log('connect result data', result.data);
readLoop();
}, error => {console.log('error in connect', error);
this.onReceiveError(error);
});
};
return this.device_.open()
.then(() => {
if (this.device_.configuration === null) {
return this.device_.selectConfiguration(1);
}
})
.then(() => {
var configurationInterfaces = this.device_.configuration.interfaces;console.log('config ints', configurationInterfaces);
configurationInterfaces.forEach(element => {
element.alternates.forEach(elementalt => {
if (elementalt.interfaceClass==0xff) {
this.interfaceNumber_ = element.interfaceNumber;console.log('intnum', this.interfaceNumber_);
elementalt.endpoints.forEach(elementendpoint => {
if (elementendpoint.direction == "out") {
this.endpointOut_ = elementendpoint.endpointNumber;
}
if (elementendpoint.direction=="in") {
this.endpointIn_ =elementendpoint.endpointNumber;
}
})
}
})
})
})
.then(() => {console.log('clintnum', this.interfaceNumber_); this.device_.claimInterface(this.interfaceNumber_); this.device_.configurations[0].interfaces[2].claimed; console.log('claimed interface', this.device_.configurations[0].interfaces[2].claimed)})
.then(() => this.device_.selectAlternateInterface(this.interfaceNumber_, 0))
.then(() => this.device_.controlTransferOut({
'requestType': 'class',
'recipient': 'interface',
'request': 0x22,
'value': 0x01,
'index': this.interfaceNumber_}))
.then(() => {
readLoop();
});
};
serial.Port.prototype.disconnect = function() {
return this.device_.controlTransferOut({
'requestType': 'class',
'recipient': 'interface',
'request': 0x22,
'value': 0x00,
'index': this.interfaceNumber_})
.then(() => this.device_.close());
};
serial.Port.prototype.send = function(data) {
return this.device_.transferOut(this.endpointOut_, data);
};
})();
The following is the WebUSB.h:
/*
Copyright (c) 2015, Arduino LLC
Original code (pre-library): Copyright (c) 2011, Peter Barrett
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.
*/
#ifndef WebUSB_h
#define WebUSB_h
#include <stdint.h>
#include <Arduino.h>
#ifdef ARDUINO_ARCH_SAMD
#include "USB/PluggableUSB.h"
#else
#include "PluggableUSB.h"
#include <avr/wdt.h>
#include <util/atomic.h>
#endif
#ifndef USBCON
#error "WebUSB requires a board that supports USB client device mode."
#endif
#define USB_BOS_DESCRIPTOR_TYPE 15
#define WEBUSB_REQUEST_GET_URL 0x02
#define MS_OS_20_REQUEST_DESCRIPTOR 0x07
#ifndef WEBUSB_SHORT_NAME
// Max length 20 (ISERIAL_MAX_LEN defined in USBDesc.h)
#define WEBUSB_SHORT_NAME "WUART"
#endif
typedef struct
{
InterfaceDescriptor dif;
EndpointDescriptor in;
EndpointDescriptor out;
} WebUSBDescriptor;
class WebUSB : public PluggableUSBModule, public Stream
{
public:
/*
* Together |landingPageScheme| and |landingPageUrl| tell the browser
* what page the user should visit in order to interact with their
* device. |landingPageScheme| can have any of the following values:
*
* 0x00 -> "http://"
* 0x01 -> "https://"
*
* This prefix is combined with |landingPageUrl| to produce the full
* URL.
*/
WebUSB(uint8_t landingPageScheme, const char* landingPageUrl);
void begin(unsigned long);
void begin(unsigned long, uint8_t);
void end(void);
/*
* Sets the string reported as the USB device serial number.
* This should be called before |begin()|, typically in |setup()|.
* |name| should be a pointer to static char array containing
* a nul-terminated string containing at most 20 characters
* (not counting the final nul character).
*/
void setShortName(const char* name);
virtual int available(void);
virtual int peek(void);
virtual int read(void);
int availableForWrite(void);
virtual void flush(void);
virtual size_t write(uint8_t);
virtual size_t write(const uint8_t*, size_t);
using Print::write; // pull in write(str) and write(buf, size) from Print
operator bool();
volatile uint8_t _rx_buffer_head;
volatile uint8_t _rx_buffer_tail;
unsigned char _rx_buffer[SERIAL_BUFFER_SIZE];
int32_t readBreak();
uint32_t baud();
uint8_t stopbits();
uint8_t paritytype();
uint8_t numbits();
bool dtr();
bool rts();
enum {
ONE_STOP_BIT = 0,
ONE_AND_HALF_STOP_BIT = 1,
TWO_STOP_BITS = 2,
};
enum {
NO_PARITY = 0,
ODD_PARITY = 1,
EVEN_PARITY = 2,
MARK_PARITY = 3,
SPACE_PARITY = 4,
};
protected:
// Implementation of the PluggableUSBModule
int getInterface(uint8_t* interfaceCount);
int getDescriptor(USBSetup& setup);
bool setup(USBSetup& setup);
uint8_t getShortName(char* name);
private:
bool VendorControlRequest(USBSetup& setup);
#ifdef ARDUINO_ARCH_SAMD
uint32_t epType[2];
#else
uint8_t epType[2];
#endif
uint16_t descriptorSize;
uint8_t protocol;
uint8_t idle;
int peek_buffer;
uint8_t landingPageScheme;
const char* landingPageUrl;
const char* shortName;
bool portOpenResult;
uint8_t portOpenPrevLineState;
unsigned long portOpenLineStateChangeMillis;
};
#endif // WebUSB_h
Thank you for any possible feedback!
Regards,
Ryan