0
votes

I am trying to use WebUSB to read GPS coordinates from a USB-connected GPS receiver from within javascript.

I have been able to connect to the receiver; however, I am unsure how to use WebUSB to access the NMEA messages.

So far, I have the following proof-of-concept code:

<html>
  <head>
    <title>WebUSB Serial Sample Application</title>
  </head>


<body>
 <a href="#" id = "click">Connect</a><br>
 <a href="#" id = "send">Send Data</a><br>
 <a href="#" id = "read">Read Data</a><br>
 <script>
 
let y;
let device;

var connectButton = document.getElementById('click')
connectButton.addEventListener('click', function() {
    navigator.usb.requestDevice({
        filters: [{}]
    }).then((selectedDevice) => {
        device = selectedDevice;
        return device.open()
            .then(() => device.selectConfiguration(1))
            .then(() => device.claimInterface(device.configuration.interfaces[0].interfaceNumber))
            .then(() => device.selectAlternateInterface(device.configuration.interfaces[0].interfaceNumber, 0))
            .then(() => {
                y = device;
            })
    });
})

var sendButton = document.getElementById('send')    
var sendDecoder = new TextDecoder()
sendButton.addEventListener('click', async () => {
y.controlTransferOut({
    requestType: 'class',
    recipient: 'interface',
    request: 0x22,
    value: 0x01,
    index: 0x00});
    
y.controlTransferIn({
        requestType: 'standard',
        recipient: 'device',
        request: 0x06,
        value: 0x0302,
        index: 0x409
    }, 255)
        .then(result => {
            let decoder = new TextDecoder()
            console.log(sendDecoder.decode(result.data));
            console.log('sent req');
        }).catch(error => {
            console.log(error);
        });
});

 </script>
 </body>
 </html>

The transferOut statement allows me to read the Vendor name from the device. So, I know I'm connected and can communicate with it. I just don't know what specific command(s) are needed to access the lat/lon from the device.

UPDATE: Below is a snapshot of the device config info enter image description here

2

2 Answers

1
votes

There are a couple of layers you'll need to understand in order to accomplish your goal. The first is the USB interface implemented by the device.

In the example code you've posted there are two calls which send commands to the device. The first is a controlTransferOut() which sends the standard USB CDC-ACM SET_CONTROL_LINE_STATE request (0x22) to the device to enable the DTR signal (0x01). If your device implements the USB CDC-ACM protocol then this is the write thing to do as it signals to the device that the host software is ready to receive data. If your device doesn't implement this protocol then the command will be ignored or fail. You're already claiming the first interface, to understand USB protocol that interface implements you should check device.configuration.interfaces[0].alternates[0].interfaceClass and device.configuration.interfaces[1].alternates[0].interfaceClass. A USB CDC-ACM device will have one interface with class 2 and one with class 10. Class 2 is for the control interface while class 10 is the data interface. If your device doesn't have these two interfaces then it probably isn't implementing the USB CDC-ACM protocol and you'll have to figure out what protocol it uses first.

The second call you make is a controlTransferIn() which sends the standard USB GET_DESCRIPTOR request (0x06) to read a string descriptor from the device. Passing 0x0302 asks it to read the string descriptor (type 3) at index 2. This will work for any USB device (assuming the index is right) and so doesn't tell you whether you've figured out what protocol the interface supports.

Assuming this is a USB CDC-ACM interface then you'll want to look at device.configuration.interfaces[1].alternates[0].endpoints and figure out the endpoint numbers for the IN and OUT endpoints. These are what you'll pass to transferIn() and transferOut() to send and receive data from the device.

Once you have all that figured out you'll need to figure out how to get the device to start sending NMEA messages. If you are lucky then in its default mode it will automatically send them and you can just start calling transferIn() to receive them. Otherwise you will have to figure out what command to send to the device to put it in a the right mode. If you have and documentation for the device or example code written in other languages that supports this device then that will be very helpful for figuring this out.

0
votes

I have finally solved this problem. The answer (for me, anyway) was to purchase a different GPS receiver that implemented the CDC-ACM interface since there seems to be more examples and better docs for this protocol.

The following proof-of-concept code is working:

<html>
  <head>
    <title>WebUSB Serial Sample Application</title>
  </head>


<body>
 <a href="#" id = "click">Connect</a><br>
 <a href="#" id = "read">Read Data</a><br>
 <script>
 
let y;
let device;

var connectButton = document.getElementById('click')
connectButton.addEventListener('click', function() {
    navigator.usb.requestDevice({
        filters: [{}]
    })
    .then((selectedDevice) => {
        device = selectedDevice;
        return device.open();
    })
    .then(() => device.selectConfiguration(1))
    .then(() => device.claimInterface(device.configuration.interfaces[0].interfaceNumber))
    .then(() => device.claimInterface(device.configuration.interfaces[1].interfaceNumber))
    .then(() => device.selectAlternateInterface(device.configuration.interfaces[0].interfaceNumber, 0))
    .then(() => device.selectAlternateInterface(device.configuration.interfaces[1].interfaceNumber, 0))
    .then(() => {
        y = device;
    })
})

var readButton = document.getElementById('read')
var readDecoder = new TextDecoder()
var readLoop = () => {
    y.transferIn(2,64)
        .then(result => {
            let decoder = new TextDecoder()
            console.log(readDecoder.decode(result.data.buffer));
            readLoop();
        }).catch(error => {
            console.log(error);
        });
};

readButton.addEventListener('click', async () => {
    readLoop();
});


 </script>
 </body>
 </html>