Vanilla javascript example
karelv opened this issue · 16 comments
Hello, I'm trying to use this package to have my website communicate with my arduino sketch. On desktop chrome I have it working with USB Web Serial from 'navigator'. However in Android this is not made available.
This is where this lib comes useful.
I have found the old demo.html file and the dist/serial.js. However it shows me only the USB_VID/PID I can't get the send/receive function to work.
Would it be possible to share an example on how to use this library?
I like jquery, but plain vanilla javascript would be very helpful.
I have never used .ts files, but if know this is the way to use this lib, I'm open to learn that file format as well. (or at least how this concept is working).
Thanks in advance.
If you're using vanilla JavaScript (rather than building your app with a bundler that can import the NPM module), do this:
<script type="module">
import { serial as webSerialPolyfill } from "https://cdn.jsdelivr.net/npm/web-serial-polyfill@1.0.15";
// Use webSerialPolyfill as you would navigator.serial.
</script>
Awesome, that works!
At least for 99.9% of the cases.
After a while(some seconds) I got an error:
NetworkError: Failed to execute 'transferIn' on 'USBDevice': A transfer error has occurred.
I have quite some data coming in over the CDC UART, but I have not seen this on a desktop with navigator.serial
.
Any hints?
Open chrome://device-log
on the device to see the low-level USB error message. It may also be helpful to collect a bug report from the device in order to look at the kernel log. When running on a phone it's possible there are novel issues like insufficient power to reliably run the device.
Sorry, the screenshot shows a little big... also note that I got the 'transferIn' error only once on my webpage. It disconnects.
When the error arrives on my web-page, it is displayed in textarea, the datetime was "2023/11/21 11:17:06.862"
Some other datapoints:
1] I'm trying to discern what happens on the MCU-side. After the error message appears, I clear the device-log, then I reload the page(app), and re-connect to the serial port, while keeping the MCU in the USB port of the phone. This works correctly, until that error appears again. Therefore I believe the MCU keeps running in case of that event. (Also I need to sent something to the MCU before it sends on a regular basis something back).
2] I have tried to send less often (every 2 seconds), but that does not help either.
3] I don't know how big the data-chunks are that the MCU is sending over, is there a limit of what web-serial-polyfill
can receive at once?
3bis] I changed the firmware to perform a 'flush' every 32 bytes, no change.
4] Same behavior without the 'Generic Billboard Device' (usb-hub device), and thus using a direct connection between MCU and the phone (USBC to USBA).
I think the issue is that the Linux kernel imposes a limit on the total size of outstanding USB transfers in order to limit the amount of kernel memory that is consumed. It's not about how much data the device is sending but how much the page is asking for at once. If it ends up creating too many transferIn()
calls or specifies too large a transfer size then you'll get this error. As written the polyfill should only be issuing one transferIn()
call at once (having multiple would be a nice performance optimization) and the transfer size is based on the desiredSize
reported by the ReadableStream
, which comes from the bufferSize
parameter passed in when opening the port. What do you have this set to?
I guess you mean this parameter:
https://developer.mozilla.org/en-US/docs/Web/API/SerialPort/open#buffersize
I'm trying to increase it, I was using the default, which is 255 bytes, but I can confirm my chunks are larger!
I keep you posted.
Okay here it is:
I have increased the bufferSize to 10000 ==> after first datachunk received, it failed
Then I changed it to 32 ==> similar results as before
Then I changed it to 5000 ==> it failed after the 2nd datachunk.
Note: A datachuck is 4635 bytes long.
Is there something I can do in the webpage, to always empty the buffer; and thus buffer it inside the browser in stead of inside the kernel?
Note2: I checked 'device-log', same error.
Is there something I can do in the webpage, to always empty the buffer; and thus buffer it inside the browser in stead of inside the kernel?
USB is a pull-based transport rather than a push-based transport. This means that the device never sends data unless asked. So data can't get buffered in the kernel. The site asks for N bytes of data, the kernel sets aside N bytes of memory and starts asking the device for data. When it gets some that completes the transaction and the data is sent to the site. If the device sent more than N bytes that's called a "babble" error and doesn't seem to be the problem you're seeing here.
The "out of memory" error is the kernel complaining that the sum of all the transfers Chrome has requested is greater than an internal limit it has set. Given how the polyfill is implemented this should't happen because there's only ever one IN transfer requested at a time and the size is based on the requested buffer size, which you've said is very small.
My best guess of what's going on here is that there is a bug in the polyfill causing it to not wait for each transfer to complete and is instead submitting lots of transfers at once. The larger the buffer size the more quickly that will cause problems. Either that or you are also trying to send a very large chunk of data to the device.
To debug further you'll need to put some debug logging into the polyfill itself to understand when and how often it is calling transferIn()
. You can rebuild the library by checking out this repository and running,
npm install # One-time setup
npm run build # Generates dist/serial.js
This is again very helpful!
I have build the dist/serial.js
And have since I have no console.log
, I added a div on the page and updated the serial.js file as follows:
try {
let elem = document.querySelector('#console');
my_counter = my_counter + 1;
let local_counter = my_counter;
elem.innerHTML = elem.innerHTML + "<hr>START["+local_counter.toString()+"]: chunkSize: "+chunkSize.toString()+" -- this.endpoint_.endpointNumber: "+this.endpoint_.endpointNumber.toString()+" <br>";
const result = await this.device_.transferIn(this.endpoint_.endpointNumber, chunkSize);
elem.innerHTML = elem.innerHTML + "RESULT["+local_counter.toString()+"]: "+result.status+"<br>";
if (result.status != 'ok') {
controller.error(`USB error: ${result.status}`);
this.onError_();
}
if ((_a = result.data) === null || _a === void 0 ? void 0 : _a.buffer) {
elem.innerHTML = elem.innerHTML + "DATA["+local_counter.toString()+"]: byteOffset: "+result.data.byteOffset.toString()+" | byteLength: "+result.data.byteLength.toString()+" <hr>";
const chunk = new Uint8Array(result.data.buffer, result.data.byteOffset, result.data.byteLength);
controller.enqueue(chunk);
}
Note that my_counter
is a global variable, while local_counter
has a scope only for within the function.
Now, I must say that my knowledge about await
and asynch
programming is limited. So I hope you can make sense of the log it has generated. (see below)
console:
START[1]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[1]: ok
DATA[1]: byteOffset: 0 | byteLength: 38
START[2]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[3]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[2]: ok
DATA[2]: byteOffset: 0 | byteLength: 2
START[4]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[5]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[3]: ok
DATA[3]: byteOffset: 0 | byteLength: 109
START[6]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[7]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[4]: ok
DATA[4]: byteOffset: 0 | byteLength: 1
START[8]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[9]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[5]: ok
DATA[5]: byteOffset: 0 | byteLength: 2
START[10]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[11]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[6]: ok
DATA[6]: byteOffset: 0 | byteLength: 1
START[12]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[13]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[7]: ok
DATA[7]: byteOffset: 0 | byteLength: 1
START[14]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[15]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[8]: ok
DATA[8]: byteOffset: 0 | byteLength: 1
START[16]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[17]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[9]: ok
DATA[9]: byteOffset: 0 | byteLength: 24
START[18]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[19]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[10]: ok
DATA[10]: byteOffset: 0 | byteLength: 1
START[20]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[21]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[11]: ok
DATA[11]: byteOffset: 0 | byteLength: 1
START[22]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[23]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[12]: ok
DATA[12]: byteOffset: 0 | byteLength: 2
START[24]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[25]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[13]: ok
DATA[13]: byteOffset: 0 | byteLength: 2
START[26]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[27]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[14]: ok
DATA[14]: byteOffset: 0 | byteLength: 1
START[28]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[29]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[15]: ok
DATA[15]: byteOffset: 0 | byteLength: 1
START[30]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[31]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[16]: ok
DATA[16]: byteOffset: 0 | byteLength: 1
START[32]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[33]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[17]: ok
DATA[17]: byteOffset: 0 | byteLength: 1
START[34]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[35]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[18]: ok
DATA[18]: byteOffset: 0 | byteLength: 1
START[36]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[37]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[19]: ok
DATA[19]: byteOffset: 0 | byteLength: 1
START[38]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[39]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[20]: ok
DATA[20]: byteOffset: 0 | byteLength: 2
START[40]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[41]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[21]: ok
DATA[21]: byteOffset: 0 | byteLength: 1
START[42]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[43]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[22]: ok
DATA[22]: byteOffset: 0 | byteLength: 2
START[44]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[45]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[23]: ok
DATA[23]: byteOffset: 0 | byteLength: 1
START[46]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[47]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[24]: ok
DATA[24]: byteOffset: 0 | byteLength: 1
START[48]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[49]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[25]: ok
DATA[25]: byteOffset: 0 | byteLength: 1
START[50]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[51]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[26]: ok
DATA[26]: byteOffset: 0 | byteLength: 1
START[52]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[53]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[27]: ok
DATA[27]: byteOffset: 0 | byteLength: 1
START[54]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[55]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[28]: ok
DATA[28]: byteOffset: 0 | byteLength: 1
START[56]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[57]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[29]: ok
DATA[29]: byteOffset: 0 | byteLength: 2
START[58]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[59]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[30]: ok
DATA[30]: byteOffset: 0 | byteLength: 1
START[60]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[61]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[31]: ok
DATA[31]: byteOffset: 0 | byteLength: 1
START[62]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[63]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[32]: ok
DATA[32]: byteOffset: 0 | byteLength: 1
START[64]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[65]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[33]: ok
DATA[33]: byteOffset: 0 | byteLength: 1
START[66]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[67]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[34]: ok
DATA[34]: byteOffset: 0 | byteLength: 1
START[68]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[69]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[35]: ok
DATA[35]: byteOffset: 0 | byteLength: 2
START[70]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[71]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[36]: ok
DATA[36]: byteOffset: 0 | byteLength: 1
START[72]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[73]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[37]: ok
DATA[37]: byteOffset: 0 | byteLength: 1
START[74]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[75]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[38]: ok
DATA[38]: byteOffset: 0 | byteLength: 1
START[76]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[77]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[39]: ok
DATA[39]: byteOffset: 0 | byteLength: 55
START[78]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[79]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[40]: ok
DATA[40]: byteOffset: 0 | byteLength: 161
START[80]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[81]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[41]: ok
DATA[41]: byteOffset: 0 | byteLength: 1
START[82]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[83]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[42]: ok
DATA[42]: byteOffset: 0 | byteLength: 1
START[84]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[85]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[43]: ok
DATA[43]: byteOffset: 0 | byteLength: 1
START[86]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[87]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[44]: ok
DATA[44]: byteOffset: 0 | byteLength: 2
START[88]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[89]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[45]: ok
DATA[45]: byteOffset: 0 | byteLength: 1
START[90]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[91]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[46]: ok
DATA[46]: byteOffset: 0 | byteLength: 1
START[92]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[93]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[47]: ok
DATA[47]: byteOffset: 0 | byteLength: 1
START[94]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[95]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[48]: ok
DATA[48]: byteOffset: 0 | byteLength: 1
START[96]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[97]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[49]: ok
DATA[49]: byteOffset: 0 | byteLength: 1
START[98]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[99]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[50]: ok
DATA[50]: byteOffset: 0 | byteLength: 1
START[100]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[101]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[51]: ok
DATA[51]: byteOffset: 0 | byteLength: 2
START[102]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[103]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[52]: ok
DATA[52]: byteOffset: 0 | byteLength: 1
START[104]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[105]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[53]: ok
DATA[53]: byteOffset: 0 | byteLength: 1
START[106]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[107]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[54]: ok
DATA[54]: byteOffset: 0 | byteLength: 3
START[108]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[109]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[55]: ok
DATA[55]: byteOffset: 0 | byteLength: 2
START[110]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[111]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[56]: ok
DATA[56]: byteOffset: 0 | byteLength: 2
START[112]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[113]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
RESULT[57]: ok
DATA[57]: byteOffset: 0 | byteLength: 2
START[114]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
START[115]: chunkSize: 5056 -- this.endpoint_.endpointNumber: 2
As you can see it looks like there are more than one entry into the transferIn
function...
Not sure what the next steps should be...
Hey reillyeon, Many thanks for your help!
I have tried to isolate the issue by making a small web-page demonstrator for web-serial (both -api and -polyfill).
Have a look here: https://github.com/karelv/web-serial-example and here https://karelv.github.io/web-serial-example/
It seems to keep working, meaning I need to review my more complex project, I guess.
Note: when use the serial.js with my_counter & local_counter, it still indicates there are several transferIn called at the same time.
Actually it still happens... (it takes just a whole lot longer!)
Let's close this one, I will make another ticket, as the example script is clear:
https://github.com/karelv/web-serial-example