diff options
| author | Sam Anthony <sam@samanthony.xyz> | 2025-09-17 09:09:05 -0400 |
|---|---|---|
| committer | Sam Anthony <sam@samanthony.xyz> | 2025-09-17 09:09:05 -0400 |
| commit | 8b1ef0ce3a520d994ed73117069cc7a49fb75ca4 (patch) | |
| tree | d81a7c8ebcb8e845fa8f8d9b5042dc2ae81b6428 /sw/usbcom | |
| parent | db3b9ef98ff189abcd08555e3ee30601713ef8f6 (diff) | |
| download | can-gauge-interface-8b1ef0ce3a520d994ed73117069cc7a49fb75ca4.zip | |
usbcom go rewrite
Diffstat (limited to 'sw/usbcom')
| -rw-r--r-- | sw/usbcom/go.mod | 8 | ||||
| -rw-r--r-- | sw/usbcom/go.sum | 8 | ||||
| -rw-r--r-- | sw/usbcom/main.go | 85 | ||||
| -rw-r--r-- | sw/usbcom/usb/usb.go | 161 | ||||
| -rw-r--r-- | sw/usbcom/usbcom.c | 194 |
5 files changed, 262 insertions, 194 deletions
diff --git a/sw/usbcom/go.mod b/sw/usbcom/go.mod new file mode 100644 index 0000000..1870f76 --- /dev/null +++ b/sw/usbcom/go.mod @@ -0,0 +1,8 @@ +module git.samanthony.xyz/can_gauge_interface/sw/usbcom + +go 1.24.6 + +require ( + github.com/google/gousb v1.1.3 // indirect + golang.org/x/sync v0.17.0 // indirect +) diff --git a/sw/usbcom/go.sum b/sw/usbcom/go.sum new file mode 100644 index 0000000..64a2dc3 --- /dev/null +++ b/sw/usbcom/go.sum @@ -0,0 +1,8 @@ +github.com/google/gousb v1.1.3 h1:xt6M5TDsGSZ+rlomz5Si5Hmd/Fvbmo2YCJHN+yGaK4o= +github.com/google/gousb v1.1.3/go.mod h1:GGWUkK0gAXDzxhwrzetW592aOmkkqSGcj5KLEgmCVUg= +github.com/gotmc/libusb/v2 v2.3.4 h1:xDKi52trBIq7QTMxlhHy9zgb+HRmimSBo8yhx+k1NWU= +github.com/gotmc/libusb/v2 v2.3.4/go.mod h1:rU0mvps+snf/CHvEkISbxrwUlWpt6VluOkjqpKo6TFw= +github.com/sam-rba/share v0.3.0 h1:aBVvgSbcBaH43Jrbq++iz15/DAso/D6oJDm+dDM4E40= +github.com/sam-rba/share v0.3.0/go.mod h1:rDx/wFG1Vc6JO1F/F0eBnkcy2wHwV0T9100ig/hu12A= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= diff --git a/sw/usbcom/main.go b/sw/usbcom/main.go new file mode 100644 index 0000000..70661d9 --- /dev/null +++ b/sw/usbcom/main.go @@ -0,0 +1,85 @@ +package main + +import ( + "context" + "fmt" + "io" + "os" + + "golang.org/x/sync/errgroup" + + "git.samanthony.xyz/can_gauge_interface/sw/usbcom/usb" +) + +const bufSize = 512 + +func main() { + exitCode := make(chan int) + go exit(exitCode) + defer close(exitCode) + + dev, err := usb.Connect() + if err != nil { + fmt.Fprintf(os.Stderr, "Error connecting to device: %v\n", err) + exitCode <- 1 + return + } + defer func() { + if err := dev.Close(); err != nil { + fmt.Fprintf(os.Stderr, "Error :%v\n", err) + exitCode <- 1 + } + }() + + var group errgroup.Group + ctx, cancel := context.WithCancel(context.Background()) + + // Host->Device + group.Go(func() error { + err := hostToDevice(dev) + cancel() // device->host routine + return err + }) + + // Device->Host + group.Go(func() error { + return deviceToHost(ctx, dev) + }) + + if err = group.Wait(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + exitCode <- 1 + } +} + +func exit(code <-chan int) { + worst := 0 + for c := range code { + if c > worst { + worst = c + } + } + os.Exit(worst) +} + +func hostToDevice(dev *usb.Device) error { + _, err := io.Copy(dev, os.Stdin) + return err +} + +func deviceToHost(ctx context.Context, dev *usb.Device) error { + buf := make([]byte, bufSize) + for { + select { + case <-ctx.Done(): + return nil + default: + } + + n, err := dev.Read(buf) + if err != nil { + return err + } + fmt.Print(string(buf[:n])) + } +} diff --git a/sw/usbcom/usb/usb.go b/sw/usbcom/usb/usb.go new file mode 100644 index 0000000..1fecf4a --- /dev/null +++ b/sw/usbcom/usb/usb.go @@ -0,0 +1,161 @@ +package usb + +import ( + "fmt" + + "github.com/google/gousb" +) + +const ( + vendorId gousb.ID = 0x04d8 + productId gousb.ID = 0x000a + + inEndpointAddr = 0x82 + outEndpointAddr = 0x02 +) + +type Device struct { + ctx *gousb.Context + dev *gousb.Device + closeIntf func() + in *gousb.ReadStream // IN endpoint (device->host) + out *gousb.WriteStream // OUT endpoint (host->device) +} + +func Connect() (*Device, error) { + ctx := gousb.NewContext() + + dev, err := ctx.OpenDeviceWithVIDPID(vendorId, productId) + if err != nil { + ctx.Close() + return nil, err + } + + intf, closeIntf, err := interfaceByClass(dev, gousb.ClassData) + if err != nil { + dev.Close() + ctx.Close() + return nil, err + } + + inEp, err := intf.InEndpoint(inEndpointAddr) + if err != nil { + closeIntf() + dev.Close() + ctx.Close() + return nil, err + } + + inEpDesc, ok := intf.Setting.Endpoints[inEndpointAddr] + if !ok { + closeIntf() + dev.Close() + ctx.Close() + return nil, fmt.Errorf("no such endpoint: %x", inEndpointAddr) + } + + in, err := inEp.NewStream(inEpDesc.MaxPacketSize, 1) + if err != nil { + closeIntf() + dev.Close() + ctx.Close() + return nil, err + } + + outEp, err := intf.OutEndpoint(outEndpointAddr) + if err != nil { + closeIntf() + dev.Close() + ctx.Close() + return nil, err + } + + outEpDesc, ok := intf.Setting.Endpoints[outEndpointAddr] + if !ok { + closeIntf() + dev.Close() + ctx.Close() + return nil, fmt.Errorf("no such endpoint: %x", outEndpointAddr) + } + + out, err := outEp.NewStream(outEpDesc.MaxPacketSize, 1) + if err != nil { + closeIntf() + dev.Close() + ctx.Close() + return nil, err + } + + return &Device{ + ctx, + dev, + closeIntf, + in, out, + }, nil +} + +func interfaceByClass(dev *gousb.Device, class gousb.Class) (intf *gousb.Interface, close func(), err error) { + cfgNum, err := dev.ActiveConfigNum() + if err != nil { + return nil, nil, fmt.Errorf("failed to get active config number of device %s: %v", dev, err) + } + cfg, err := dev.Config(cfgNum) + if err != nil { + return nil, nil, fmt.Errorf("failed to claim config %d of device %s: %v", cfgNum, dev, err) + } + + intfNum, altNum, ok := func() (int, int, bool) { + for i, intf := range cfg.Desc.Interfaces { + for a, alt := range intf.AltSettings { + if alt.Class == class { + return i, a, true + } + } + } + return -1, -1, false + }() + if !ok { + cfg.Close() + return nil, nil, fmt.Errorf("failed to get %s interface of device %s: %v", class, dev, err) + } + + intf, err = cfg.Interface(intfNum, altNum) + if err != nil { + cfg.Close() + return nil, nil, fmt.Errorf("failed to select interface (%d,%d) of config %d of device %s: %v", intfNum, altNum, cfgNum, dev, err) + } + + return intf, func() { + intf.Close() + cfg.Close() + }, nil +} + +func (dev *Device) Close() error { + outErr := dev.out.Close() + inErr := dev.in.Close() + dev.closeIntf() + devErr := dev.dev.Close() + ctxErr := dev.ctx.Close() + + return nonNil(outErr, inErr, devErr, ctxErr) +} + +func nonNil(errs ...error) error { + for _, err := range errs { + if err != nil { + return err + } + } + return nil +} + +// Read from the IN endpoint (device->host) +func (dev *Device) Read(p []byte) (n int, err error) { + return dev.in.Read(p) +} + +// Write to the OUT endpoint (host->device) +func (dev *Device) Write(p []byte) (n int, err error) { + return dev.out.Write(p) +} diff --git a/sw/usbcom/usbcom.c b/sw/usbcom/usbcom.c deleted file mode 100644 index a374875..0000000 --- a/sw/usbcom/usbcom.c +++ /dev/null @@ -1,194 +0,0 @@ -#include <assert.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include <libusb-1.0/libusb.h> - -#define TIMEOUT_MS 5000 - -#define BUF_SIZE 512 - -#define INTERFACE 0 - -enum { - VENDOR_ID = 0x04d8, - PRODUCT_ID = 0x000a, -}; - -enum { - SET_LINE_CODING = 0x20, - SET_CONTROL_LINE_STATE = 0x22, -}; - -enum { - ACM_CTRL_DTR = 0x01, - ACM_CTRL_RTS = 0x02, -}; - -enum { - EP_OUT_ADDR = 0x02, - EP_IN_ADDR = 0x82, -}; - -// line encoding: 9600 8N1 -// 9600 = 0x2580 LE -static unsigned char encoding[] = {0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08}; - -// Returns NULL on error -static libusb_device_handle * -setup(void) { - int res; - libusb_device_handle *devh; - - res = libusb_init_context(NULL, NULL, 0); - if (res < 0) { - fprintf(stderr, "Error initializing libusb: %s\n", libusb_strerror(res)); - return NULL; - } - - devh = libusb_open_device_with_vid_pid(NULL, VENDOR_ID, PRODUCT_ID); - if (!devh) { - fprintf(stderr, "Error finding USB device\n"); - return NULL; - } - - res = libusb_claim_interface(devh, INTERFACE); - if (res < 0) { - fprintf(stderr, "Failed to claim interface: %s\n", libusb_strerror(res)); - libusb_close(devh); - return NULL; - } - - res = libusb_control_transfer(devh, 0x21, SET_CONTROL_LINE_STATE, ACM_CTRL_DTR | ACM_CTRL_RTS, 0, NULL, 0, TIMEOUT_MS); - if (res < 0) { - fprintf(stderr, "Error during control transfer: %s\n", libusb_strerror(res)); - libusb_close(devh); - return NULL; - } - - res = libusb_control_transfer(devh, 0x21, SET_LINE_CODING, 0, 0, encoding, sizeof(encoding), TIMEOUT_MS); - if (res < 0) { - fprintf(stderr, "Error during control transfer: %s\n", libusb_strerror(res)); - libusb_close(devh); - return NULL; - } - - return devh; -} - -static void -teardown(libusb_device_handle *devh) { - libusb_release_interface(devh, INTERFACE); - libusb_close(devh); - libusb_exit(NULL); -} - -// Send bytes to USB -static void -txChars(libusb_device_handle *devh, unsigned char *data, int n) { - int actualLen, res; - - actualLen = 0; - while (actualLen < n) { - res = libusb_bulk_transfer(devh, EP_OUT_ADDR, data, n, &actualLen, TIMEOUT_MS); - if (res == LIBUSB_ERROR_TIMEOUT) { - printf("Tx timeout (%d)\n", actualLen); - } else if (res < 0) { - fprintf(stderr, "Tx error: %s\n", libusb_strerror(res)); - } - assert(actualLen <= n); - n -= actualLen; - data += actualLen; - } -} - -// Read data from USB -// Returns number of bytes read, or -1 on error -static int -rxChars(libusb_device_handle *devh, unsigned char *data, int size) { - int res, len; - - res = libusb_bulk_transfer(devh, EP_IN_ADDR, data, size, &len, TIMEOUT_MS); - if (res == LIBUSB_ERROR_TIMEOUT) { - fprintf(stderr, "Rx timeout\n"); - return -1; - } else if (res < 0) { - fprintf(stderr, "Rx error: %s\n", libusb_strerror(res)); - return -1; - } - return len; -} - -// Get line from stdin -// Includes '\n' -// Returns number of chars prior to NUL, or EOF on end of file or error -static int -scanline(char *buf, size_t size) { - int c, n; - - c = '\0'; - n = 0; - while (n+1 < size) { - c = getchar(); - if (c == EOF) { - break; - } - buf[n++] = c; - if (c == '\n') { - break; - } - } - - if (size > 0) { - buf[n] = '\0'; - } - - return ((c == EOF) && (n < 1)) ? EOF : n; -} - -static void -sendCommand(libusb_device_handle *devh) { - unsigned char buf[BUF_SIZE]; - int len; - - while ((len = scanline(buf, sizeof(buf))) != EOF) { - txChars(devh, buf, len); - if (len > 0 && buf[len-1] == '\n') { - break; - } - } -} - -static void -printResponse(libusb_device_handle *devh) { - unsigned char buf[BUF_SIZE]; - int len; - - while ((len = rxChars(devh, buf, sizeof(buf)-1)) >= 0) { - buf[len] = '\0'; - printf("%s", buf); - if (len > 0 && buf[len-1] == '\n') { - return; - } - } -} - -int -main(int argc, char *argv[]) { - libusb_device_handle *devh; - - devh = setup(); - if (!devh) { - return 1; - } - - for (;;) { - sendCommand(devh); - printResponse(devh); - } - - teardown(devh); - return 0; -} |