5 Commits

Author SHA1 Message Date
Sebastian Zagrodzki
e840be9d06 Do not assume interface numbers follow the slice indices. (#134)
Do not assume interface numbers follow the slice indices.

This is a continuation of
9ad54830f4
which tried to solve the problem of non-contiguous interface indices;
this commit modifies another code path that had the same assumption.
2024-09-13 20:43:01 +02:00
Kuba Raczkowski
606016adee Android support via fileDescriptor (#130)
Adds a new way to initialize the context, `ContextOptions`, and a new way to open devices, `OpenDeviceWithFileDescriptor`.

Combined they can be used to support Android devices, where device enumeration is not supported (can be now disabled through `ContextOptions`) and where USB devices can be opened in the Android SDK and passed over to gousb through a file descriptor.

Co-authored-by: Juan <994594+juaoose@users.noreply.github.com>
Co-authored-by: Kuba Raczkowski <kuba.raczkowski@spectricity.com>
2024-09-08 21:40:30 +02:00
Sebastian Zagrodzki
8c7a7eb841 Upgrade build/test environment
Upgrading Linux build env to Ubuntu 24.04, Go 1.23
2024-09-08 18:32:06 +02:00
Jamil Djadala
2f9ed92cb8 suppress 'interrupted [code -10]' log message. resolve #87 (#121) 2024-02-24 22:08:57 +01:00
Sebastian Zagrodzki
5514912016 Go 1.22 (#122)
Update Go & Ubuntu versions in GitHub CI workflows. Pass files through a new version of gofmt. Allow running CI workflow manually.
2024-02-24 21:48:06 +01:00
13 changed files with 283 additions and 54 deletions

View File

@@ -1,4 +1,4 @@
#!/bin/sh
echo 'mode: count' > coverage.merged &&\
go list -f '{{.Dir}}' ./... | xargs -n1 -I'{}' sh -c ': > coverage.tmp; go test -v -covermode=count -coverprofile=coverage.tmp {} && tail -n +2 coverage.tmp >> coverage.merged' &&\
go list -f '{{.Dir}}' ./... | xargs -I'{}' sh -c ': > coverage.tmp; go test -v -covermode=count -coverprofile=coverage.tmp {} && tail -n +2 coverage.tmp >> coverage.merged' &&\
rm coverage.tmp

View File

@@ -1,13 +1,13 @@
name: build-and-test
on: [pull_request, push]
on: [pull_request, push, workflow_dispatch]
jobs:
linux:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.23
- run: DIFF="$( find . -name '*.go' -print0 | xargs -0 gofmt -l )"; if [ -n "$DIFF" ]; then echo "Files not formatted, run gofmt:"; echo "$DIFF"; exit 1; fi
- run: sudo apt-get install libusb-1.0-0-dev
- run: go install golang.org/x/tools/cmd/cover@latest
@@ -24,7 +24,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: 1.17
go-version: 1.22
- uses: msys2/setup-msys2@v2
with:
install: |-

View File

@@ -49,25 +49,17 @@ func (c ConfigDesc) String() string {
return fmt.Sprintf("Configuration %d", c.Number)
}
func (c ConfigDesc) intfDesc(num, alt int) (*InterfaceSetting, error) {
func (c ConfigDesc) intfDesc(num int) (*InterfaceDesc, error) {
// In an ideal world, interfaces in the descriptor would be numbered
// contiguously starting from 0, as required by the specification. In the
// real world however the specification is sometimes ignored:
// https://github.com/google/gousb/issues/65
ifs := make([]int, len(c.Interfaces))
for i := range c.Interfaces {
ifs[i] = c.Interfaces[i].Number
if ifs[i] != num {
continue
for i, desc := range c.Interfaces {
if desc.Number == num {
return &desc, nil
}
alts := make([]int, len(c.Interfaces[i].AltSettings))
for a := range c.Interfaces[i].AltSettings {
alts[a] = c.Interfaces[i].AltSettings[a].Alternate
if alts[a] == alt {
return &c.Interfaces[i].AltSettings[a], nil
}
}
return nil, fmt.Errorf("alternate setting %d not found for %s, available alt settings: %v", alt, c.Interfaces[i], alts)
ifs[i] = desc.Number
}
return nil, fmt.Errorf("interface %d not found, available interface numbers: %v", num, ifs)
}
@@ -120,9 +112,13 @@ func (c *Config) Interface(num, alt int) (*Interface, error) {
return nil, fmt.Errorf("Interface(%d, %d) called on %s after Close", num, alt, c)
}
altInfo, err := c.Desc.intfDesc(num, alt)
intf, err := c.Desc.intfDesc(num)
if err != nil {
return nil, fmt.Errorf("descriptor of interface (%d, %d) in %s: %v", num, alt, c, err)
return nil, fmt.Errorf("descriptor of interface %d in %s: %v", num, c, err)
}
altInfo, err := intf.altSetting(alt)
if err != nil {
return nil, fmt.Errorf("descriptor of alternate setting %d of interface %d in %s: %v", alt, num, c, err)
}
c.mu.Lock()
@@ -137,7 +133,7 @@ func (c *Config) Interface(num, alt int) (*Interface, error) {
}
// Select an alternate setting if needed (device has multiple alternate settings).
if len(c.Desc.Interfaces[num].AltSettings) > 1 {
if len(intf.AltSettings) > 1 {
if err := c.dev.ctx.libusb.setAlt(c.dev.handle, uint8(num), uint8(alt)); err != nil {
c.dev.ctx.libusb.release(c.dev.handle, uint8(num))
return nil, fmt.Errorf("failed to set alternate config %d on interface %d of %s: %v", alt, num, c, err)

View File

@@ -276,9 +276,13 @@ func (d *Device) InterfaceDescription(cfgNum, intfNum, altNum int) (string, erro
if err != nil {
return "", fmt.Errorf("%s: %v", d, err)
}
alt, err := cfg.intfDesc(intfNum, altNum)
intf, err := cfg.intfDesc(intfNum)
if err != nil {
return "", fmt.Errorf("%s, configuration %d: %v", d, cfgNum, err)
return "", fmt.Errorf("%s, configuration %d interface %d: %v", d, cfgNum, intfNum, err)
}
alt, err := intf.altSetting(altNum)
if err != nil {
return "", fmt.Errorf("%s, configuration %d interface %d alternate setting %d: %v", d, cfgNum, intfNum, altNum, err)
}
return d.GetStringDescriptor(alt.iInterface)
}

View File

@@ -117,18 +117,18 @@ func TestClaimAndRelease(t *testing.T) {
t.Errorf("%s.InEndpoint(%d): got %+v, want %+v", intf, ep1Addr, got, want)
}
if _, err := cfg.Interface(1, 0); err == nil {
if _, err := cfg.Interface(if1Num, 0); err == nil {
t.Fatalf("%s.Interface(1, 0): got nil, want non nil, because Interface 1 is already claimed.", cfg)
}
// intf2 is interface #0, not claimed yet.
intf2, err := cfg.Interface(0, 0)
intf2, err := cfg.Interface(if2Num, alt2Num)
if err != nil {
t.Fatalf("%s.Interface(0, 0): got %v, want nil", cfg, err)
}
if err := cfg.Close(); err == nil {
t.Fatalf("%s.Close(): got nil, want non nil, because the Interface was not released.", cfg)
t.Fatalf("%s.Close(): got nil, want non nil, because the Interfaces #1/2 was not released.", cfg)
}
if err := dev.Close(); err == nil {
t.Fatalf("%s.Close(): got nil, want non nil, because the Config was not released.", cfg)
@@ -136,7 +136,7 @@ func TestClaimAndRelease(t *testing.T) {
intf.Close()
if err := cfg.Close(); err == nil {
t.Fatalf("%s.Close(): got nil, want non nil, because the Interface was not released.", cfg)
t.Fatalf("%s.Close(): got nil, want non nil, because the Interface #2 was not released.", cfg)
}
if err := dev.Close(); err == nil {
t.Fatalf("%s.Close(): got nil, want non nil, because the Config was not released.", dev)
@@ -244,3 +244,61 @@ func TestAutoDetachFailure(t *testing.T) {
t.Fatalf("%s.Config(1) got nil, but want no nil because interface fails to detach", dev)
}
}
func TestInterface(t *testing.T) {
t.Parallel()
c := newContextWithImpl(newFakeLibusb())
defer func() {
if err := c.Close(); err != nil {
t.Errorf("Context.Close: %v", err)
}
}()
for _, tc := range []struct {
name string
vid, pid ID
cfgNum, ifNum, altNum int
}{{
name: "simple",
vid: 0x8888,
pid: 0x0002,
cfgNum: 1,
ifNum: 0,
altNum: 0,
}, {
name: "alt_setting",
vid: 0x8888,
pid: 0x0002,
cfgNum: 1,
ifNum: 1,
altNum: 1,
}, {
name: "noncontiguous_interfaces",
vid: 0x8888,
pid: 0x0002,
cfgNum: 1,
ifNum: 3,
altNum: 2,
}} {
t.Run(tc.name, func(t *testing.T) {
dev, err := c.OpenDeviceWithVIDPID(0x8888, 0x0002)
if err != nil {
t.Fatalf("OpenDeviceWithVIDPID(0x8888, 0x0002): %v", err)
}
if dev == nil {
t.Fatal("OpenDeviceWithVIDPID(0x8888, 0x0002): got nil device, need non-nil")
}
defer dev.Close()
cfg, err := dev.Config(tc.cfgNum)
if err != nil {
t.Fatalf("%s.Config(%d): %v", dev, tc.cfgNum, err)
}
defer cfg.Close()
intf, err := cfg.Interface(tc.ifNum, tc.altNum)
if err != nil {
t.Fatalf("%s.Interface(%d, %d): %v", cfg, tc.ifNum, tc.altNum, err)
}
intf.Close()
})
}
}

View File

@@ -16,9 +16,10 @@ package gousb
// fake devices connected through the fakeLibusb stack.
type fakeDevice struct {
devDesc *DeviceDesc
strDesc map[int]string
alt uint8
devDesc *DeviceDesc
strDesc map[int]string
alt uint8
sysDevPtr uintptr
}
var fakeDevices = []fakeDevice{
@@ -64,6 +65,7 @@ var fakeDevices = []fakeDevice{
}},
}},
},
sysDevPtr: 78,
},
// Bus 001 Device 002: ID 8888:0002
// One config, two interfaces. interface #0 with no endpoints,
@@ -186,6 +188,7 @@ var fakeDevices = []fakeDevice{
8: "Slower streaming",
9: "Interface for https://github.com/google/gousb/issues/65",
},
sysDevPtr: 94,
},
// Bus 001 Device 003: ID 9999:0002
// One config, one interface, one setup,

View File

@@ -85,8 +85,10 @@ func (t *fakeTransfer) setStatus(st TransferStatus) {
// that allows the test to explicitly control individual transfer behavior.
type fakeLibusb struct {
mu sync.Mutex
// fakeDevices has a map of devices and their descriptors.
fakeDevices map[*libusbDevice]*fakeDevice
// devices has a map of devices and their descriptors.
devices map[*libusbDevice]*fakeDevice
// sysDevices keeps the order of devices to be accessd by wrapSysDevice
sysDevices map[uintptr]*libusbDevice
// ts has a map of all allocated transfers, indexed by the pointer of
// underlying libusbTransfer.
ts map[*libusbTransfer]*fakeTransfer
@@ -102,12 +104,28 @@ func (f *fakeLibusb) init() (*libusbContext, error) { retu
func (f *fakeLibusb) handleEvents(c *libusbContext, done <-chan struct{}) { <-done }
func (f *fakeLibusb) getDevices(*libusbContext) ([]*libusbDevice, error) {
ret := make([]*libusbDevice, 0, len(fakeDevices))
for d := range f.fakeDevices {
for d := range f.devices {
ret = append(ret, d)
}
return ret, nil
}
func (f *fakeLibusb) wrapSysDevice(ctx *libusbContext, systemDeviceHandle uintptr) (*libusbDevHandle, error) {
dev, ok := f.sysDevices[systemDeviceHandle]
if !ok {
return nil, fmt.Errorf("the passed file descriptor %d does not point to a valid device", systemDeviceHandle)
}
h := newDevHandlePointer()
f.mu.Lock()
defer f.mu.Unlock()
f.handles[h] = dev
return h, nil
}
func (f *fakeLibusb) getDevice(handle *libusbDevHandle) *libusbDevice {
return f.handles[handle]
}
func (f *fakeLibusb) exit(*libusbContext) error {
close(f.submitted)
if got := len(f.ts); got > 0 {
@@ -122,7 +140,7 @@ func (f *fakeLibusb) exit(*libusbContext) error {
func (f *fakeLibusb) setDebug(*libusbContext, int) {}
func (f *fakeLibusb) dereference(d *libusbDevice) {}
func (f *fakeLibusb) getDeviceDesc(d *libusbDevice) (*DeviceDesc, error) {
if dev, ok := f.fakeDevices[d]; ok {
if dev, ok := f.devices[d]; ok {
return dev.devDesc, nil
}
return nil, fmt.Errorf("invalid USB device %p", d)
@@ -158,7 +176,7 @@ func (f *fakeLibusb) setConfig(d *libusbDevHandle, cfg uint8) error {
return nil
}
func (f *fakeLibusb) getStringDesc(d *libusbDevHandle, index int) (string, error) {
dev, ok := f.fakeDevices[f.handles[d]]
dev, ok := f.devices[f.handles[d]]
if !ok {
return "", fmt.Errorf("invalid USB device %p", d)
}
@@ -201,7 +219,7 @@ func (f *fakeLibusb) setAlt(d *libusbDevHandle, intf, alt uint8) error {
if !f.claims[f.handles[d]][intf] {
return fmt.Errorf("interface %d must be claimed before alt setup can be set", intf)
}
f.fakeDevices[f.handles[d]].alt = alt
f.devices[f.handles[d]].alt = alt
return nil
}
@@ -288,11 +306,12 @@ func (f *fakeLibusb) empty() bool {
func newFakeLibusb() *fakeLibusb {
fl := &fakeLibusb{
fakeDevices: make(map[*libusbDevice]*fakeDevice),
ts: make(map[*libusbTransfer]*fakeTransfer),
submitted: make(chan *fakeTransfer, 10),
handles: make(map[*libusbDevHandle]*libusbDevice),
claims: make(map[*libusbDevice]map[uint8]bool),
devices: make(map[*libusbDevice]*fakeDevice),
sysDevices: make(map[uintptr]*libusbDevice),
ts: make(map[*libusbTransfer]*fakeTransfer),
submitted: make(chan *fakeTransfer, 10),
handles: make(map[*libusbDevHandle]*libusbDevice),
claims: make(map[*libusbDevice]map[uint8]bool),
}
for _, d := range fakeDevices {
// libusb does not export a way to allocate a new libusb_device struct
@@ -301,7 +320,11 @@ func newFakeLibusb() *fakeLibusb {
// The contents of these pointers is never accessed.
fd := new(fakeDevice)
*fd = d
fl.fakeDevices[newDevicePointer()] = fd
devPointer := newDevicePointer()
fl.devices[devPointer] = fd
if fd.sysDevPtr != 0 { // the sysDevPtr not being set in the fakeDevices list
fl.sysDevices[fd.sysDevPtr] = devPointer
}
}
return fl
}

View File

@@ -29,6 +29,17 @@ type InterfaceDesc struct {
AltSettings []InterfaceSetting
}
func (i *InterfaceDesc) altSetting(alt int) (*InterfaceSetting, error) {
alts := make([]int, len(i.AltSettings))
for a, s := range i.AltSettings {
if s.Alternate == alt {
return &s, nil
}
alts[a] = s.Alternate
}
return nil, fmt.Errorf("alternate setting %d not found for %s, available alt settings: %v", alt, i, alts)
}
// String returns a human-readable description of the interface descriptor and
// its alternate settings.
func (i InterfaceDesc) String() string {

View File

@@ -143,6 +143,7 @@ type libusbIntf interface {
dereference(*libusbDevice)
getDeviceDesc(*libusbDevice) (*DeviceDesc, error)
open(*libusbDevice) (*libusbDevHandle, error)
wrapSysDevice(*libusbContext, uintptr) (*libusbDevHandle, error)
close(*libusbDevHandle)
reset(*libusbDevHandle) error
@@ -152,6 +153,7 @@ type libusbIntf interface {
getStringDesc(*libusbDevHandle, int) (string, error)
setAutoDetach(*libusbDevHandle, int) error
detachKernelDriver(*libusbDevHandle, uint8) error
getDevice(*libusbDevHandle) *libusbDevice
// interface
claim(*libusbDevHandle, uint8) error
@@ -169,13 +171,24 @@ type libusbIntf interface {
}
// libusbImpl is an implementation of libusbIntf using real CGo-wrapped libusb.
type libusbImpl struct{}
type libusbImpl struct {
discovery DeviceDiscovery
}
func (libusbImpl) init() (*libusbContext, error) {
func (impl libusbImpl) init() (*libusbContext, error) {
var ctx *C.libusb_context
if err := fromErrNo(C.libusb_init(&ctx)); err != nil {
var libusbOpts [4]C.struct_libusb_init_option // fixed to 4 - there are maximum 4 options
nOpts := 0
if impl.discovery == DisableDeviceDiscovery {
libusbOpts[nOpts].option = C.LIBUSB_OPTION_NO_DEVICE_DISCOVERY
nOpts++
}
if err := fromErrNo(C.libusb_init_context(&ctx, &(libusbOpts[0]), C.int(nOpts))); err != nil {
return nil, err
}
return (*libusbContext)(ctx), nil
}
@@ -188,7 +201,10 @@ func (libusbImpl) handleEvents(c *libusbContext, done <-chan struct{}) {
default:
}
if errno := C.libusb_handle_events_timeout_completed((*C.libusb_context)(c), &tv, nil); errno < 0 {
log.Printf("handle_events: error: %s", Error(errno))
// handler can be interrupted by a signal and this doesn't indicate an error, we'll retry on the next loop iteration
if Error(errno) != ErrorInterrupted {
log.Printf("handle_events: error: %s", Error(errno))
}
}
}
}
@@ -214,6 +230,20 @@ func (libusbImpl) getDevices(ctx *libusbContext) ([]*libusbDevice, error) {
return ret, nil
}
func (libusbImpl) wrapSysDevice(ctx *libusbContext, fd uintptr) (*libusbDevHandle, error) {
var handle *C.libusb_device_handle
if ret := C.libusb_wrap_sys_device((*C.libusb_context)(ctx), C.intptr_t(fd), &handle); ret < 0 {
return nil, fromErrNo(C.int(ret))
}
return (*libusbDevHandle)(handle), nil
}
func (libusbImpl) getDevice(d *libusbDevHandle) *libusbDevice {
device := C.libusb_get_device((*C.libusb_device_handle)(d))
return (*libusbDevice)(device)
}
func (libusbImpl) exit(c *libusbContext) error {
C.libusb_exit((*C.libusb_context)(c))
return nil

71
usb.go
View File

@@ -16,7 +16,7 @@
/*
Package gousb provides an low-level interface to attached USB devices.
A Short Tutorial
# A Short Tutorial
A Context manages all resources necessary for communicating with USB
devices.
@@ -117,11 +117,10 @@ standard for control commands though, and many devices implement their custom co
Control commands can be issued through Device.Control().
See Also
# See Also
For more information about USB protocol and handling USB devices,
see the excellent "USB in a nutshell" guide: http://www.beyondlogic.org/usbnutshell/
*/
package gousb
@@ -164,9 +163,36 @@ func newContextWithImpl(impl libusbIntf) *Context {
return ctx
}
// NewContext returns a new Context instance.
// NewContext returns a new Context instance with default ContextOptions.
func NewContext() *Context {
return newContextWithImpl(libusbImpl{})
return ContextOptions{}.New()
}
// DeviceDiscovery controls USB device discovery.
type DeviceDiscovery int
const (
// EnableDeviceDiscovery means the connected USB devices will be enumerated
// on Context initialization. This enables the use of OpenDevices and
// OpenWithVIDPID. This is the default.
EnableDeviceDiscovery = iota
// DisableDeviceDiscovery means the USB devices are not enumerated and
// OpenDevices will not return any devices.
// Without device discovery, OpenDeviceWithFileDescriptor can be used
// to open devices.
DisableDeviceDiscovery
)
// ContextOptions holds parameters for Context initialization.
type ContextOptions struct {
DeviceDiscovery DeviceDiscovery
}
// New creates a Context, taking into account the optional flags contained in ContextOptions
func (o ContextOptions) New() *Context {
return newContextWithImpl(libusbImpl{
discovery: o.DeviceDiscovery,
})
}
// OpenDevices calls opener with each enumerated device.
@@ -211,6 +237,41 @@ func (c *Context) OpenDevices(opener func(desc *DeviceDesc) bool) ([]*Device, er
return ret, reterr
}
// OpenDeviceWithFileDescriptor takes a (Unix) file descriptor of an opened USB
// device and wraps the library around it.
// This is particularly useful when working on Android, where the USB device can be
// opened by the SDK (Java), giving access to the device through the file descriptor
// (https://developer.android.com/reference/android/hardware/usb/UsbDeviceConnection#getFileDescriptor()).
//
// Do note that for this to work the automatic device discovery must be disabled
// at the time when the new Context is created, through the use of
// ContextOptions.DeviceDiscovery.
//
// Example:
//
// ctx := ContextOptions{DeviceDiscovery: DisableDeviceDiscovery}.New()
// device, err := ctx.OpenDeviceWithFileDescriptor(fd)
//
// An error is returned in case the file descriptor is not valid.
func (c *Context) OpenDeviceWithFileDescriptor(fd uintptr) (*Device, error) {
handle, err := c.libusb.wrapSysDevice(c.ctx, fd)
if err != nil {
return nil, err
}
dev := c.libusb.getDevice(handle)
desc, err := c.libusb.getDeviceDesc(dev)
if err != nil {
return nil, fmt.Errorf("device was opened, but getting device descriptor failed: %v", err)
}
o := &Device{handle: handle, ctx: c, Desc: desc}
c.mu.Lock()
c.devices[o] = true
c.mu.Unlock()
return o, nil
}
// OpenDeviceWithVIDPID opens Device from specific VendorId and ProductId.
// If none is found, it returns nil and nil error. If there are multiple devices
// with the same VID/PID, it will return one of them, picked arbitrarily.

View File

@@ -94,3 +94,44 @@ func TestOpenDeviceWithVIDPID(t *testing.T) {
}
}
}
func TestOpenDeviceWithFileDescriptor(t *testing.T) {
ctx := newContextWithImpl(newFakeLibusb())
defer ctx.Close()
// file descriptor is an index to the FakeDevices array
for _, d := range []struct {
vid, pid ID
sysDevPtr uintptr
}{
{0x9999, 0x0001, 78},
{0x8888, 0x0002, 94},
} {
dev, err := ctx.OpenDeviceWithFileDescriptor(d.sysDevPtr)
if err != nil {
t.Fatalf("OpenDeviceWithFileDescriptor(%d): err != nil for a valid device: %v", d.sysDevPtr, err)
}
if dev == nil {
t.Fatalf("OpenDeviceWithFileDescriptor(%d): device == nil for a valid device", d.sysDevPtr)
}
if dev != nil && (dev.Desc.Vendor != ID(d.vid) || dev.Desc.Product != ID(d.pid)) {
t.Errorf("OpenDeviceWithFileDescriptor(%d): device's VID/PID %s/%s don't match expected: %s/%s", d.sysDevPtr, dev.Desc.Vendor, dev.Desc.Product, ID(d.vid), ID(d.pid))
}
}
}
func TestOpenDeviceWithFileDescriptorOnMissingDevice(t *testing.T) {
ctx := newContextWithImpl(newFakeLibusb())
defer ctx.Close()
for _, sysDevPtr := range []uintptr{
7, // set, but does not exist in the fakeDevices array
0, // unset
} {
if _, err := ctx.OpenDeviceWithFileDescriptor(sysDevPtr); err == nil {
t.Errorf("OpenDeviceWithFileDescriptor(%d): got nil error for invalid device", sysDevPtr)
}
}
}

View File

@@ -20,8 +20,9 @@
// a reader.
//
// The bread and butter of this package are the following two functions:
// Describe - Pretty-print the vendor and product of a device descriptor
// Classify - Pretty-print the class/protocol info for a device/interface
//
// Describe - Pretty-print the vendor and product of a device descriptor
// Classify - Pretty-print the class/protocol info for a device/interface
package usbid
import (

View File

@@ -20,7 +20,8 @@ import "time"
// LastUpdate stores the latest time that the library was updated.
//
// The baked-in data was last generated:
// 2017-03-10 09:09:14.940548227 -0500 EST
//
// 2017-03-10 09:09:14.940548227 -0500 EST
var LastUpdate = time.Unix(0, 1489154954940548227)
const usbIDListData = `#