diff --git a/usb/debug.go b/usb/debug.go new file mode 100644 index 0000000..3e482f5 --- /dev/null +++ b/usb/debug.go @@ -0,0 +1,22 @@ +package usb + +// To enable internal debugging: +// -ldflags "-X github.com/kylelemons/gousb/usb.debugInternal true" + +import ( + "io" + "io/ioutil" + "log" // TODO(kevlar): make a logger + "os" +) + +var debug *log.Logger +var debugInternal string + +func init() { + var out io.Writer = ioutil.Discard + if debugInternal != "" { + out = os.Stderr + } + debug = log.New(out, "usb", log.LstdFlags|log.Lshortfile) +} diff --git a/usb/device.go b/usb/device.go index bd39ce1..2bfd334 100644 --- a/usb/device.go +++ b/usb/device.go @@ -5,7 +5,6 @@ import "C" import ( "fmt" - //"log" // TODO(kevlar): make a logger "reflect" "sync" "time" @@ -112,23 +111,26 @@ func (d *Device) OpenEndpoint(conf, iface, setup, epoint uint8) (Endpoint, error Device: d, } + var setAlternate bool for _, c := range d.Configs { if c.Config != conf { continue } - fmt.Printf("found conf: %#v\n", c) + debug.Printf("found conf: %#v\n", c) for _, i := range c.Interfaces { if i.Number != iface { continue } - fmt.Printf("found iface: %#v\n", i) - for _, s := range i.Setups { + debug.Printf("found iface: %#v\n", i) + for i, s := range i.Setups { if s.Alternate != setup { continue } - fmt.Printf("found setup: %#v\n", s) + setAlternate = i != 0 + + debug.Printf("found setup: %#v [default: %v]\n", s, !setAlternate) for _, e := range s.Endpoints { - fmt.Printf("ep %02x search: %#v\n", epoint, s) + debug.Printf("ep %02x search: %#v\n", epoint, s) if e.Address != epoint { continue } @@ -178,10 +180,11 @@ found: d.lock.Unlock() // unlock immediately because the next calls may block // Choose the alternate - // This doesn't seem to work... - if errno := C.libusb_set_interface_alt_setting(d.handle, C.int(iface), C.int(setup)); errno < 0 { - //log.Printf("ignoring altsetting error: %s", usbError(errno)) - return nil, fmt.Errorf("usb: setalt: %s", usbError(errno)) + if setAlternate { + if errno := C.libusb_set_interface_alt_setting(d.handle, C.int(iface), C.int(setup)); errno < 0 { + debug.Printf("altsetting error: %s", usbError(errno)) + return nil, fmt.Errorf("usb: setalt: %s", usbError(errno)) + } } return end, nil diff --git a/usb/error.go b/usb/error.go index d97b685..c240141 100644 --- a/usb/error.go +++ b/usb/error.go @@ -1,12 +1,16 @@ package usb +import ( + "fmt" +) + // #include import "C" type usbError C.int func (e usbError) Error() string { - return "libusb: " + usbErrorString[e] + return fmt.Sprintf("libusb: %s [code %d]", usbErrorString[e], int(e)) } const ( diff --git a/xbox/xbox b/xbox/xbox new file mode 100755 index 0000000..311d621 Binary files /dev/null and b/xbox/xbox differ diff --git a/xbox/xbox.go b/xbox/xbox.go new file mode 100644 index 0000000..638bbb6 --- /dev/null +++ b/xbox/xbox.go @@ -0,0 +1,316 @@ +package main + +import ( + "log" + "math" + "time" + + "github.com/kylelemons/gousb/usb" +) + +func main() { + ctx := usb.NewContext() + defer ctx.Close() + + //ctx.Debug(10) + + type modelInfo struct { + config, iface, setup, endIn, endOut uint8 + } + var model modelInfo + + devs, err := ctx.ListDevices(func(desc *usb.Descriptor) bool { + switch { + case desc.Vendor == 0x045e && desc.Product == 0x028e: + log.Printf("Found standard Microsoft controller") + /* + 250.006 045e:028e Xbox360 Controller (Microsoft Corp.) + Protocol: Vendor Specific Class (Vendor Specific Subclass) Vendor Specific Protocol + Config 01: + -------------- + Interface 00 Setup 00 + Vendor Specific Class + Endpoint 1 IN interrupt - unsynchronized data [32 0] + Endpoint 1 OUT interrupt - unsynchronized data [32 0] + -------------- + Interface 01 Setup 00 + Vendor Specific Class + Endpoint 2 IN interrupt - unsynchronized data [32 0] + Endpoint 2 OUT interrupt - unsynchronized data [32 0] + Endpoint 3 IN interrupt - unsynchronized data [32 0] + Endpoint 3 OUT interrupt - unsynchronized data [32 0] + -------------- + Interface 02 Setup 00 + Vendor Specific Class + Endpoint 0 IN interrupt - unsynchronized data [32 0] + -------------- + Interface 03 Setup 00 + Vendor Specific Class + -------------- + */ + model = modelInfo{1, 0, 0, 1, 1} + case desc.Vendor == 0x1689 && desc.Product == 0xfd00: + log.Printf("Found Razer Onza Tournament controller") + /* + 250.006 1689:fd00 Unknown 1689:fd00 + Protocol: Vendor Specific Class (Vendor Specific Subclass) Vendor Specific Protocol + Config 01: + -------------- + Interface 00 Setup 00 + Vendor Specific Class + Endpoint 1 IN interrupt - unsynchronized data [32 0] + Endpoint 2 OUT interrupt - unsynchronized data [32 0] + -------------- + Interface 01 Setup 00 + Vendor Specific Class + Endpoint 3 IN interrupt - unsynchronized data [32 0] + Endpoint 0 OUT interrupt - unsynchronized data [32 0] + Endpoint 1 IN interrupt - unsynchronized data [32 0] + Endpoint 1 OUT interrupt - unsynchronized data [32 0] + -------------- + Interface 02 Setup 00 + Vendor Specific Class + Endpoint 2 IN interrupt - unsynchronized data [32 0] + -------------- + Interface 03 Setup 00 + Vendor Specific Class + -------------- + */ + model = modelInfo{1, 0, 0, 1, 2} + default: + return false + } + return true + }) + if err != nil { + log.Fatalf("listdevices: %s", err) + } + defer func() { + for _, d := range devs { + d.Close() + } + }() + if len(devs) != 1 { + log.Fatalf("found %d devices, want 1", len(devs)) + } + controller := devs[0] + + if err := controller.Reset(); err != nil { + log.Fatalf("reset: %s", err) + } + + in, err := controller.OpenEndpoint( + model.config, + model.iface, + model.setup, + model.endIn|uint8(usb.ENDPOINT_DIR_IN)) + if err != nil { + log.Fatalf("in: openendpoint: %s", err) + } + + out, err := controller.OpenEndpoint( + model.config, + model.iface, + model.setup, + model.endOut|uint8(usb.ENDPOINT_DIR_OUT)) + if err != nil { + log.Fatalf("out: openendpoint: %s", err) + } + + // https://github.com/Grumbel/xboxdrv/blob/master/PROTOCOL + + var b [32]byte + for { + n, err := in.Read(b[:]) + log.Printf("read %d bytes: % x [err: %v]", n, b[:n], err) + if err != nil { + break + } + } + + const ( + Empty byte = iota // 00000000 ( 0) no LEDs + WarnAll // 00000001 ( 1) flash all briefly + NewPlayer1 // 00000010 ( 2) p1 flash then solid + NewPlayer2 // 00000011 + NewPlayer3 // 00000100 + NewPlayer4 // 00000101 + Player1 // 00000110 ( 6) p1 solid + Player2 // 00000111 + Player3 // 00001000 + Player4 // 00001001 + Waiting // 00001010 (10) empty w/ loops + WarnPlayer // 00001011 (11) flash active + _ // 00001100 (12) empty + Battery // 00001101 (13) squiggle + Searching // 00001110 (14) slow flash + Booting // 00001111 (15) solid then flash + ) + + led := func(b byte) { + out.Write([]byte{0x01, 0x03, b}) + } + + setPlayer := func(player byte) { + spin := []byte{ + Player1, Player2, Player4, Player3, + } + spinIdx := 0 + spinDelay := 100 * time.Millisecond + + led(Booting) + time.Sleep(100 * time.Millisecond) + for spinDelay > 20*time.Millisecond { + led(spin[spinIdx]) + time.Sleep(spinDelay) + spinIdx = (spinIdx + 1) % len(spin) + spinDelay -= 5 * time.Millisecond + } + for i := 0; i < 40; i++ { // just for safety + cur := spin[spinIdx] + led(cur) + time.Sleep(spinDelay) + spinIdx = (spinIdx + 1) % len(spin) + if cur == player { + break + } + } + } + + led(Empty) + time.Sleep(1 * time.Second) + setPlayer(Player1) + + /* + time.Sleep(1 * time.Second) + setPlayer(Player2) + time.Sleep(1 * time.Second) + setPlayer(Player3) + time.Sleep(1 * time.Second) + setPlayer(Player4) + time.Sleep(5 * time.Second) + led(Waiting) + */ + + var last, cur [32]byte + decode := func() { + n, err := in.Read(cur[:]) + if err != nil || n != 20 { + log.Printf("ignoring read: %d bytes, err = %v", n, err) + return + } + + // 1-bit values + for _, v := range []struct { + idx int + bit uint + name string + }{ + {2, 0, "DPAD U"}, + {2, 1, "DPAD D"}, + {2, 2, "DPAD L"}, + {2, 3, "DPAD R"}, + {2, 4, "START"}, + {2, 5, "BACK"}, + {2, 6, "THUMB L"}, + {2, 7, "THUMB R"}, + {3, 0, "LB"}, + {3, 1, "RB"}, + {3, 2, "GUIDE"}, + {3, 4, "A"}, + {3, 5, "B"}, + {3, 6, "X"}, + {3, 7, "Y"}, + } { + c := cur[v.idx] & (1 << v.bit) + l := last[v.idx] & (1 << v.bit) + if c == l { + continue + } + switch { + case c != 0: + log.Printf("Button %q pressed", v.name) + case l != 0: + log.Printf("Button %q released", v.name) + } + } + + // 8-bit values + for _, v := range []struct { + idx int + name string + }{ + {4, "LT"}, + {5, "RT"}, + } { + c := cur[v.idx] + l := last[v.idx] + if c == l { + continue + } + log.Printf("Trigger %q = %v", v.name, c) + } + + dword := func(hi, lo byte) int16 { + return int16(hi)<<8 | int16(lo) + } + + // +y + // N + // -x W-|-E +x + // S + // -y + dirs := [...]string{ + "W", "SW", "S", "SE", "E", "NE", "N", "NW", "W", + } + dir := func(x, y int16) (string, int32) { + // Direction + rad := math.Atan2(float64(y), float64(x)) + dir := 4 * rad / math.Pi + card := int(dir + math.Copysign(0.5, dir)) + + // Magnitude + mag := math.Sqrt(float64(x)*float64(x) + float64(y)*float64(y)) + return dirs[card+4], int32(mag) + } + + // 16-bit values + for _, v := range []struct { + hiX, loX int + hiY, loY int + name string + }{ + {7, 6, 9, 8, "LS"}, + {11, 10, 13, 12, "RS"}, + } { + c, cmag := dir( + dword(cur[v.hiX], cur[v.loX]), + dword(cur[v.hiY], cur[v.loY]), + ) + l, lmag := dir( + dword(last[v.hiX], last[v.loX]), + dword(last[v.hiY], last[v.loY]), + ) + ccenter := cmag < 10240 + lcenter := lmag < 10240 + if ccenter && lcenter { + continue + } + if c == l && cmag == lmag { + continue + } + if cmag > 10240 { + log.Printf("Stick %q = %v x %v", v.name, c, cmag) + } else { + log.Printf("Stick %q centered", v.name) + } + } + + last, cur = cur, last + } + + controller.ReadTimeout = 60 * time.Second + for { + decode() + } +}