Browse files

added GoBlink, a command line example in Go

  • Loading branch information...
1 parent 1ea8e45 commit 2eddad00a87ef8dea7138bfaae6a70483987e232 @GaryBoone GaryBoone committed Jan 21, 2013
Showing with 496 additions and 0 deletions.
  1. +35 −0 go/GoBlink/README.md
  2. +35 −0 go/GoBlink/goBlink.go
  3. +185 −0 go/GoBlink/hid.go
  4. +171 −0 go/GoBlink/hiddata.c
  5. +70 −0 go/GoBlink/hiddata.h
View
35 go/GoBlink/README.md
@@ -0,0 +1,35 @@
+GoBlink, A Go interface to the Blink(1) USB device
+=======
+
+This project demonstrates intefacing to a USB device in Go, the [ThingM blink(1)](http://thingm.com/products/blink-1.html). The original interfacing examples are written in C, so this project uses [CGO](http://golang.org/cmd/cgo/) to integrate C code into Go.
+
+## Set up ##
+
+The code was developed and tested on Mac OSX Mountain Lion, but uses nothing OS-specific, so should also work on Linux.
+
+To run, you'll first need to install *libusb*. The easiest way to so is via [MacPorts](https://www.macports.org).
+
+1. Install [Xcode](https://developer.apple.com/xcode/) and the [Command Line Tools for Xcode](https://developer.apple.com/downloads/index.action)
+2. Install [MacPorts](https://www.macports.org/install.php)
+3. Now use MacPorts to install *libusb*:
+
+ $ sudo port install libusb-compat
+
+ If your MacPorts installation does not install the *libusb* files into */opt/local/{include | lib}*, then adjust the *#cgo* compiler directives in *hid.go*.
+
+4. *cd* to the directory with this project's files, then to build and run
+
+ $ go build goBlink.go && ./goBlink
+
+
+## TODOs ##
+
+Several functions are shown, including blink, set, and random. Others can be added such as read and write.
+
+
+## Sources ##
+The code is based on the C code in the [blink1 repository](https://github.com/todbot/blink1), particularly the [blink1-mini-tool](https://github.com/todbot/blink1/tree/master/commandline/blink1-mini-tool). Christian Starkjohann's *hiddata.c* is used almost as is. The modifications are to use *enum*s instead of *#define*s to make the values available in Go. Also, the single-inclusion *#define*s needed for C are not needed for Go and excised.
+
+See also the other examples in the [command line directory](https://github.com/todbot/blink1/tree/master/commandline).
+
+
View
35 go/GoBlink/goBlink.go
@@ -0,0 +1,35 @@
+//
+// goBlink.go
+//
+// blink(1) USB interface library and demo
+//
+// See https://github.com/GaryBoone/GoBlink for more information
+//
+// (c) Gary Boone, 2012.
+//
+// To build and run:
+// $ go build goBlink.go && ./goBlink
+//
+// Build processing command; do not delete unless you change the build layout:
+// +build ignore
+//
+
+package main
+
+import (
+ "."
+ "time"
+)
+
+func main() {
+
+ b := blink.NewBlink()
+
+ b.Blink(10, 250*time.Millisecond)
+ time.Sleep(1 * time.Second)
+
+ b.SetRGB(10, 45, 233)
+ time.Sleep(1 * time.Second)
+
+ b.Random(10, 250*time.Millisecond)
+}
View
185 go/GoBlink/hid.go
@@ -0,0 +1,185 @@
+//
+// hid.go
+//
+// Go interface for C HID USB driver (usblib)
+//
+// See https://github.com/GaryBoone/GoBlink for more information
+//
+// (c) Gary Boone, 2012.
+//
+// NOTE: For CGO, there should be no comments before the // #include lines.
+// Also, there should be no space between // #include lines and import line!
+
+package blink
+
+// #include "hiddata.h"
+// #include <stdlib.h>
+// #cgo CFLAGS: -I/opt/local/include
+// #cgo LDFLAGS: -L/opt/local/lib -lusb
+import "C"
+
+// import "unsafe"
+
+import (
+ "fmt"
+ "log"
+ "math/rand"
+ "runtime"
+ "time"
+)
+
+// taken from blink1/hardware/firmware/usbconfig.h
+var (
+ IDENT_VENDOR_NUM C.int
+ IDENT_PRODUCT_NUM C.int
+ IDENT_VENDOR_STRING *C.char
+ IDENT_PRODUCT_STRING *C.char
+)
+
+const fadeMs = 100
+
+func init() {
+ IDENT_VENDOR_NUM = C.int(0x27B8)
+ IDENT_PRODUCT_NUM = C.int(0x01ED)
+ IDENT_VENDOR_STRING = C.CString("ThingM") // C strings need to be freed
+ IDENT_PRODUCT_STRING = C.CString("blink(1)")
+
+ // Those C strings could be freed using
+ // C.free(unsafe.Pointer(IDENT_VENDOR_STRING))
+ // C.free(unsafe.Pointer(IDENT_PRODUCT_STRING))
+}
+
+type Blink struct {
+ dev *C.struct_usbDevice_t
+}
+
+func NewBlink() *Blink {
+ blink := Blink{}
+ blink.open()
+ return &blink
+}
+
+// blink white num times: on for blinkMs milliseconds then off for blinkMs milliseconds
+func (b *Blink) Blink(num int, blinkMs time.Duration) {
+ blink(b.dev, num, blinkMs)
+}
+
+func (bl *Blink) SetRGB(r, g, b int) {
+ setRGB(bl.dev, r, g, b)
+}
+
+// show num random colors, each for blinkMs milliseconds
+func (b *Blink) Random(num int, blinkMs time.Duration) {
+ random(b.dev, num, blinkMs)
+}
+
+func (b *Blink) open() {
+ if !openBlink1(&b.dev) {
+ log.Print("error: couldn't open blink1.")
+ return
+ }
+ runtime.SetFinalizer(b, (*Blink).close)
+}
+
+func (b *Blink) close() {
+ C.usbhidCloseDevice(b.dev)
+}
+
+func blink(dev *C.struct_usbDevice_t, num int, blinkMs time.Duration) {
+ v := [2]int{0, 255}
+ for i := 0; i < num*2; i++ {
+ rc := fadeToRgbBlink1(dev, fadeMs, v[i%2], v[i%2], v[i%2])
+ if rc != 0 { // on error, do something, anything. come on.
+ log.Print("error in blink: couldn't open blink1. Error: ", errorMsgBlink1(rc))
+ }
+ // log.Printf("%d: %d, %x,%x,%x \n", i, millis, v[i%2], v[i%2], v[i%2])
+ time.Sleep(blinkMs)
+ }
+}
+
+func random(dev *C.struct_usbDevice_t, num int, blinkMs time.Duration) {
+ for i := 0; i < num; i++ {
+ r := rand.Intn(255)
+ g := rand.Intn(255)
+ b := rand.Intn(255)
+ rc := setRGB(dev, r, g, b)
+ if rc != 0 { // on error, do something, anything. come on.
+ log.Print("error in blink: couldn't open blink1. Error: ", errorMsgBlink1(rc))
+ }
+ // log.Printf("%d: %d, %x,%x,%x \n", i, millis, v[i%2], v[i%2], v[i%2])
+ time.Sleep(blinkMs)
+ }
+}
+
+// Open up a blink(1) for transactions.
+// returns 0 on success, and opened device in "dev"
+// or returns non-zero error that can be decoded with blink1_error_msg()
+// FIXME: what happens when multiple are plugged in?
+func openBlink1(dev **C.struct_usbDevice_t) bool {
+
+ rc := C.usbhidOpenDevice(dev,
+ IDENT_VENDOR_NUM, IDENT_VENDOR_STRING,
+ IDENT_PRODUCT_NUM, IDENT_PRODUCT_STRING,
+ 1) // NOTE: '0' means "not using report IDs"
+
+ if rc != 0 {
+ log.Print("error in open: couldn't open blink1. Error: ", errorMsgBlink1(rc))
+ return false
+ }
+
+ return true
+}
+
+//
+// Close a Blink1
+//
+func closeBlink1(dev *C.usbDevice_t) {
+ C.usbhidCloseDevice(dev)
+}
+
+//
+// Decode the error messages
+//
+func errorMsgBlink1(errCode C.int) string {
+
+ switch errCode {
+ case C.USBOPEN_ERR_ACCESS:
+ return "Access to device denied"
+ case C.USBOPEN_ERR_NOTFOUND:
+ return "The specified device was not found"
+ case C.USBOPEN_ERR_IO:
+ return "Communication error with device"
+ default:
+ return fmt.Sprintf("Unknown USB error %d", errCode)
+ }
+ return "" /* not reached */
+}
+
+func setRGB(dev *C.usbDevice_t, r, g, b int) C.int {
+ return fadeToRgbBlink1(dev, fadeMs, r, g, b)
+}
+
+func fadeToRgbBlink1(dev *C.usbDevice_t, fadeMillis, r, g, b int) C.int {
+ var buf [9]C.char
+
+ if dev == nil {
+ return -1 // BLINK1_ERR_NOTOPEN;
+ }
+
+ dms := C.char(fadeMillis / 10) // millis_divided_by_10
+
+ buf[0] = C.char(0)
+ buf[1] = C.char('c')
+ buf[2] = C.char(r)
+ buf[3] = C.char(g)
+ buf[4] = C.char(b)
+ buf[5] = C.char((dms >> 8))
+ buf[6] = C.char(dms % 127)
+ buf[7] = 0
+
+ err := C.usbhidSetReport(dev, &buf[0], C.int(8))
+ if err != 0 {
+ log.Printf("error writing data: %s\n", errorMsgBlink1(err))
+ }
+ return err // FIXME: remove fprintf
+}
View
171 go/GoBlink/hiddata.c
@@ -0,0 +1,171 @@
+/* Name: hiddata.c
+ * Author: Christian Starkjohann
+ * Creation Date: 2008-04-11
+ * Tabsize: 4
+ * Copyright: (c) 2008 by OBJECTIVE DEVELOPMENT Software GmbH
+ * License: GNU GPL v2 (see License.txt), GNU GPL v3 or proprietary (CommercialLicense.txt)
+ * This Revision: $Id: hiddata.c 743 2009-04-15 15:00:49Z cs $
+ */
+
+/* 2012-12: Gary Boone: removed win32 section */
+
+#include <stdio.h>
+#include "hiddata.h"
+#include <string.h>
+#include <usb.h>
+
+#define usbDevice usb_dev_handle /* use libusb's device structure */
+
+/* ------------------------------------------------------------------------- */
+
+#define USBRQ_HID_GET_REPORT 0x01
+#define USBRQ_HID_SET_REPORT 0x09
+
+#define USB_HID_REPORT_TYPE_FEATURE 3
+
+
+static int usesReportIDs;
+
+/* ------------------------------------------------------------------------- */
+
+static int usbhidGetStringAscii(usb_dev_handle *dev, int index, char *buf, int buflen)
+{
+char buffer[256];
+int rval, i;
+
+ if((rval = usb_get_string_simple(dev, index, buf, buflen)) >= 0) /* use libusb version if it works */
+ return rval;
+ if((rval = usb_control_msg(dev, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) + index, 0x0409, buffer, sizeof(buffer), 5000)) < 0)
+ return rval;
+ if(buffer[1] != USB_DT_STRING){
+ *buf = 0;
+ return 0;
+ }
+ if((unsigned char)buffer[0] < rval)
+ rval = (unsigned char)buffer[0];
+ rval /= 2;
+ /* lossy conversion to ISO Latin1: */
+ for(i=1;i<rval;i++){
+ if(i > buflen) /* destination buffer overflow */
+ break;
+ buf[i-1] = buffer[2 * i];
+ if(buffer[2 * i + 1] != 0) /* outside of ISO Latin1 range */
+ buf[i-1] = '?';
+ }
+ buf[i-1] = 0;
+ return i-1;
+}
+
+int usbhidOpenDevice(usbDevice_t **device, int vendor, char *vendorName, int product, char *productName, int _usesReportIDs)
+{
+struct usb_bus *bus;
+struct usb_device *dev;
+usb_dev_handle *handle = NULL;
+int errorCode = USBOPEN_ERR_NOTFOUND;
+static int didUsbInit = 0;
+
+ if(!didUsbInit){
+ usb_init();
+ didUsbInit = 1;
+ }
+ usb_find_busses();
+ usb_find_devices();
+ for(bus=usb_get_busses(); bus; bus=bus->next){
+ for(dev=bus->devices; dev; dev=dev->next){
+ if(dev->descriptor.idVendor == vendor && dev->descriptor.idProduct == product){
+ char string[256];
+ int len;
+ handle = usb_open(dev); /* we need to open the device in order to query strings */
+ if(!handle){
+ errorCode = USBOPEN_ERR_ACCESS;
+ fprintf(stderr, "Warning: cannot open USB device: %s\n", usb_strerror());
+ continue;
+ }
+ if(vendorName == NULL && productName == NULL){ /* name does not matter */
+ break;
+ }
+ /* now check whether the names match: */
+ len = usbhidGetStringAscii(handle, dev->descriptor.iManufacturer, string, sizeof(string));
+ if(len < 0){
+ errorCode = USBOPEN_ERR_IO;
+ fprintf(stderr, "Warning: cannot query manufacturer for device: %s\n", usb_strerror());
+ }else{
+ errorCode = USBOPEN_ERR_NOTFOUND;
+ /* fprintf(stderr, "seen device from vendor ->%s<-\n", string); */
+ if(strcmp(string, vendorName) == 0){
+ len = usbhidGetStringAscii(handle, dev->descriptor.iProduct, string, sizeof(string));
+ if(len < 0){
+ errorCode = USBOPEN_ERR_IO;
+ fprintf(stderr, "Warning: cannot query product for device: %s\n", usb_strerror());
+ }else{
+ errorCode = USBOPEN_ERR_NOTFOUND;
+ /* fprintf(stderr, "seen product ->%s<-\n", string); */
+ if(strcmp(string, productName) == 0)
+ break;
+ }
+ }
+ }
+ usb_close(handle);
+ handle = NULL;
+ }
+ }
+ if(handle)
+ break;
+ }
+ if(handle != NULL){
+ errorCode = 0;
+ *device = (void *)handle;
+ usesReportIDs = _usesReportIDs;
+ }
+ return errorCode;
+}
+
+/* ------------------------------------------------------------------------- */
+
+void usbhidCloseDevice(usbDevice_t *device)
+{
+ if(device != NULL)
+ usb_close((void *)device);
+}
+
+/* ------------------------------------------------------------------------- */
+
+int usbhidSetReport(usbDevice_t *device, char *buffer, int len)
+{
+ int bytesSent;
+
+ if(!usesReportIDs){
+ buffer++; /* skip dummy report ID */
+ len--;
+ }
+ bytesSent = usb_control_msg((void *)device, USB_TYPE_CLASS | USB_RECIP_DEVICE | USB_ENDPOINT_OUT, USBRQ_HID_SET_REPORT, USB_HID_REPORT_TYPE_FEATURE << 8 | (buffer[0] & 0xff), 0, buffer, len, 5000);
+ if(bytesSent != len){
+ if(bytesSent < 0)
+ fprintf(stderr, "Error sending message: %s\n", usb_strerror());
+ return USBOPEN_ERR_IO;
+ }
+ return 0;
+}
+
+/* ------------------------------------------------------------------------- */
+
+int usbhidGetReport(usbDevice_t *device, int reportNumber, char *buffer, int *len)
+{
+int bytesReceived, maxLen = *len;
+
+ if(!usesReportIDs){
+ buffer++; /* make room for dummy report ID */
+ maxLen--;
+ }
+ bytesReceived = usb_control_msg((void *)device, USB_TYPE_CLASS | USB_RECIP_DEVICE | USB_ENDPOINT_IN, USBRQ_HID_GET_REPORT, USB_HID_REPORT_TYPE_FEATURE << 8 | reportNumber, 0, buffer, maxLen, 5000);
+ if(bytesReceived < 0){
+ fprintf(stderr, "Error sending message: %s\n", usb_strerror());
+ return USBOPEN_ERR_IO;
+ }
+ *len = bytesReceived;
+ if(!usesReportIDs){
+ buffer[-1] = reportNumber; /* add dummy report ID */
+ (*len)++;
+ }
+ return 0;
+}
View
70 go/GoBlink/hiddata.h
@@ -0,0 +1,70 @@
+/* Name: hiddata.h
+ * Author: Christian Starkjohann
+ * Creation Date: 2008-04-11
+ * Tabsize: 4
+ * Copyright: (c) 2008 by OBJECTIVE DEVELOPMENT Software GmbH
+ * License: GNU GPL v2 (see License.txt), GNU GPL v3 or proprietary (CommercialLicense.txt)
+ * This Revision: $Id: hiddata.h 692 2008-11-07 15:07:40Z cs $
+ */
+
+/*
+General Description:
+This module implements an abstraction layer for data transfer over HID feature
+requests. The implementation uses native Windows functions on Windows so that
+no driver installation is required and libusb on Unix. You must link the
+appropriate libraries in either case: "-lhid -lusb -lsetupapi" on Windows and
+`libusb-config --libs` on Unix.
+*/
+
+/* 2012-12: Gary Boone: changed #defines to enum {} */
+
+/* ------------------------------------------------------------------------ */
+
+enum {
+ USBOPEN_SUCCESS = 0, /* no error */
+ USBOPEN_ERR_ACCESS = 1, /* not enough permissions to open device */
+ USBOPEN_ERR_IO = 2, /* I/O error */
+ USBOPEN_ERR_NOTFOUND = 3 /* device not found */
+};
+
+/* ------------------------------------------------------------------------ */
+
+typedef struct usbDevice usbDevice_t;
+/* Opaque data type representing the USB device. This can be a Windows handle
+ * or a libusb handle, depending on the backend implementation.
+ */
+
+/* ------------------------------------------------------------------------ */
+
+int usbhidOpenDevice(usbDevice_t **device, int vendorID, char *vendorName, int productID, char *productName, int usesReportIDs);
+/* This function opens a USB device. 'vendorID' and 'productID' are the numeric
+ * Vendor-ID and Product-ID of the device we want to open. If 'vendorName' and
+ * 'productName' are both not NULL, only devices with matching manufacturer-
+ * and product name strings are accepted. If the device uses report IDs,
+ * 'usesReportIDs' must be set to a non-zero value.
+ * Returns: If a matching device has been found, USBOPEN_SUCCESS is returned
+ * and '*device' is set to an opaque pointer representing the device. The
+ * device must be closed with usbhidCloseDevice(). If the device has not been
+ * found or opening failed, an error code is returned.
+ */
+void usbhidCloseDevice(usbDevice_t *device);
+/* Every device opened with usbhidOpenDevice() must be closed with this function.
+ */
+int usbhidSetReport(usbDevice_t *device, char *buffer, int len);
+/* This function sends a feature report to the device. The report ID must be
+ * in the first byte of buffer and the length 'len' of the report is specified
+ * including this report ID. If no report IDs are used, buffer[0] must be set
+ * to 0 (dummy report ID).
+ * Returns: 0 on success, an error code otherwise.
+ */
+int usbhidGetReport(usbDevice_t *device, int reportID, char *buffer, int *len);
+/* This function obtains a feature report from the device. The requested
+ * report-ID is passed in 'reportID'. The caller must pass a buffer of the size
+ * of the expected report in 'buffer' and initialize the variable pointed to by
+ * 'len' to the total size of this buffer. Upon successful return, the report
+ * (prefixed with the report-ID) is in 'buffer' and the actual length of the
+ * report is returned in '*len'.
+ * Returns: 0 on success, an error code otherwise.
+ */
+
+/* ------------------------------------------------------------------------ */

0 comments on commit 2eddad0

Please sign in to comment.