add xbox example and make some changes to support it
This commit is contained in:
22
usb/debug.go
Normal file
22
usb/debug.go
Normal file
@@ -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)
|
||||
}
|
@@ -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
|
||||
|
@@ -1,12 +1,16 @@
|
||||
package usb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// #include <libusb-1.0/libusb.h>
|
||||
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 (
|
||||
|
316
xbox/xbox.go
Normal file
316
xbox/xbox.go
Normal file
@@ -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()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user