Loading doc.go 0 → 100644 +18 −0 Original line number Diff line number Diff line /* Package joysticks, provides simplified event routing, by channels, from the Linux joystick driver File-like interface. events can be listened for from any thread, re-routed and simulated. */ package joysticks /* usage details. connect to a joystick by index number then use functions to make event servicing channels, one for each button or hat. event channels provide at least time. hat channel event provides current position, (x,y) (with only one axis changing per event.) */ joysticks_linux.go 0 → 100644 +216 −0 Original line number Diff line number Diff line // +build linux package joysticks import ( "encoding/binary" "fmt" "io" "os" "time" ) // see; https://www.kernel.org/doc/Documentation/input/joystick-api.txt type osEventRecord struct { Time uint32 // event timestamp in milliseconds 32bit so, about a month Value int16 // value Type uint8 // event type Index uint8 // axis/button } const maxValue = 1<<15 - 1 type hatAxis struct { number uint8 axis uint8 time time.Duration value float32 } type button struct { number uint8 time time.Duration value bool } type state struct { osEvent chan osEventRecord buttons map[uint8]button hatAxes map[uint8]hatAxis buttonCloseEvents map[uint8]chan event buttonOpenEvents map[uint8]chan event hatChangeEvents map[uint8]chan event } type event interface { Moment() time.Duration } type hatChangeEvent struct { time time.Duration x, y float32 } func (b hatChangeEvent) Moment() time.Duration { return b.time } type buttonChangeEvent struct { time time.Duration } func (b buttonChangeEvent) Moment() time.Duration { return b.time } type channel struct { number uint8 channelFn func(state, uint8) chan event } // Capture arranges for the registrees (channels) to get particular events. // Intended for bacic use since doesn't return state object. func Capture(registrees ...channel) []chan event { js, err := Connect(1) if err != nil { return nil } go js.ProcessEvents() chans := make([]chan event, len(registrees)) for i, fns := range registrees { chans[i] = fns.channelFn(js, fns.number) } return chans } // Connect sets up a go routine that puts a joysticks events onto registered channels. // to register channels use the returned state object's On<xxx>(index) methods. func Connect(index int) (js state, e error) { r, e := os.OpenFile(fmt.Sprintf("/dev/input/js%d", index-1), os.O_RDONLY, 0666) if e != nil { return } js = state{make(chan osEventRecord), make(map[uint8]button), make(map[uint8]hatAxis), make(map[uint8]chan event), make(map[uint8]chan event), make(map[uint8]chan event)} // start thread to read joystick eventd to the joystick.state osEvent channel go eventPipe(r, js.osEvent) js.populate() return js, nil } // fill in the joysticks available events from the synthetic state events burst produced initially by the driver. func (js state) populate() { for buttonNumber, hatNumber, axisNumber := 1, 1, 1; ; { evt := <-js.osEvent switch evt.Type { case 0x81: js.buttons[evt.Index] = button{uint8(buttonNumber), toDuration(evt.Time), evt.Value != 0} buttonNumber += 1 case 0x82: js.hatAxes[evt.Index] = hatAxis{uint8(hatNumber), uint8(axisNumber), toDuration(evt.Time), float32(evt.Value) / maxValue} axisNumber += 1 if axisNumber > 2 { axisNumber = 1 hatNumber += 1 } default: go func() { js.osEvent <- evt }() // put the consumed, first, after end of synthetic burst, real event, back on channel. return } } return } // pipe any readable events onto channel. func eventPipe(r io.Reader, c chan osEventRecord) { var evt osEventRecord for { err := binary.Read(r, binary.LittleEndian, &evt) if err != nil { close(c) return } c <- evt } } // interpret whats appearing on osEvent channel, then put, on any required out channel(s), the requisite event. func (js state) ProcessEvents() { for { evt, ok := <-js.osEvent if !ok { break } switch evt.Type { case 1: if evt.Value == 0 { if c, ok := js.buttonOpenEvents[js.buttons[evt.Index].number]; ok { c <- buttonChangeEvent{toDuration(evt.Time)} } } if evt.Value == 1 { if c, ok := js.buttonCloseEvents[js.buttons[evt.Index].number]; ok { c <- buttonChangeEvent{toDuration(evt.Time)} } } js.buttons[evt.Index] = button{js.buttons[evt.Index].number, toDuration(evt.Time), evt.Value != 0} case 2: if c, ok := js.hatChangeEvents[js.hatAxes[evt.Index].number]; ok { switch js.hatAxes[evt.Index].axis { case 1: c <- hatChangeEvent{toDuration(evt.Time), float32(evt.Value) / maxValue, js.hatAxes[evt.Index+1].value} case 2: c <- hatChangeEvent{toDuration(evt.Time), js.hatAxes[evt.Index-1].value, float32(evt.Value) / maxValue} } } js.hatAxes[evt.Index] = hatAxis{js.hatAxes[evt.Index].number, js.hatAxes[evt.Index].axis, toDuration(evt.Time), float32(evt.Value) / maxValue} default: // log.Println("unknown input type. ",evt.Type & 0x7f) } } } func toDuration(m uint32) time.Duration { return time.Duration(m) * 1000000 } func (js state) OnOpen(button uint8) (c chan event) { c = make(chan event) js.buttonOpenEvents[button] = c return c } func (js state) OnClose(button uint8) (c chan event) { c = make(chan event) js.buttonCloseEvents[button] = c return c } func (js state) OnMove(hatAsix uint8) (c chan event) { c = make(chan event) js.hatChangeEvents[hatAsix] = c return c } func (js state) ButtonExists(button uint8) (ok bool) { for _, v := range js.buttons { if v.number == button { return true } } return } func (js state) HatExists(hatAxis uint8) (ok bool) { for _, v := range js.hatAxes { if v.number == hatAxis { return true } } return } func (js state) InsertSyntheticEvent(v int16, t uint8, i uint8) { js.osEvent <- osEventRecord{Value: v, Type: t, Index: i} } joysticks_test.go 0 → 100644 +94 −0 Original line number Diff line number Diff line package joysticks import ( "fmt" "testing" ) import . "github.com/splace/sounds" import ( "io" "os/exec" "time" ) import "math" func TestJoysticksCapture(t *testing.T) { events := Capture( channel{10, state.OnOpen}, // event[0] button #10 opens channel{1, state.OnClose}, // event[1] button #1 closes channel{1, state.OnMove}, // event[2] hat #1 moves ) var x float32 = .5 var f time.Duration = time.Second / 440 for { select { case <-events[0]: return case <-events[1]: play(NewSound(NewTone(f, float64(x)), time.Second/3)) case h := <-events[2]: x = h.(hatChangeEvent).x/2 + .5 f = time.Duration(100*math.Pow(2, float64(h.(hatChangeEvent).y))) * time.Second / 44000 } } } func TestJoysticksAdvanced(t *testing.T) { js1, err := Connect(1) if err != nil { panic(err) } if len(js1.buttons) < 10 || len(js1.hatAxes) < 6 { t.Errorf("joystick#1, available buttons %d, Hats %d\n", len(js1.buttons), len(js1.hatAxes)/2) } b1 := js1.OnClose(1) b2 := js1.OnClose(2) b3 := js1.OnClose(3) b4 := js1.OnClose(4) quit := js1.OnOpen(10) h1 := js1.OnMove(1) h2 := js1.OnMove(2) h3 := js1.OnMove(3) go js1.ProcessEvents() time.AfterFunc(time.Second*10, func() { js1.InsertSyntheticEvent(1, 1, 1) }) // value=1 (close),type=1 (button), index=1, so fires b1 after 10 seconds for { select { case <-quit: return case <-b1: play(NewSound(NewTone(time.Second/440, 1), time.Second/3)) case <-b2: play(NewSound(NewTone(time.Second/660, 1), time.Second/3)) case <-b3: play(NewSound(NewTone(time.Second/250, 1), time.Second/3)) case <-b4: play(NewSound(NewTone(time.Second/150, 1), time.Second/3)) case h := <-h1: fmt.Println("hat 1 moved", h) case h := <-h2: fmt.Println("hat 2 moved", h) case h := <-h3: fmt.Println("hat 3 moved", h) } } } func play(s Sound) { out, in := io.Pipe() go func() { Encode(in, 2, 44100, s) in.Close() }() cmd := exec.Command("aplay") cmd.Stdin = out err := cmd.Run() if err != nil { panic(err) } } Loading
doc.go 0 → 100644 +18 −0 Original line number Diff line number Diff line /* Package joysticks, provides simplified event routing, by channels, from the Linux joystick driver File-like interface. events can be listened for from any thread, re-routed and simulated. */ package joysticks /* usage details. connect to a joystick by index number then use functions to make event servicing channels, one for each button or hat. event channels provide at least time. hat channel event provides current position, (x,y) (with only one axis changing per event.) */
joysticks_linux.go 0 → 100644 +216 −0 Original line number Diff line number Diff line // +build linux package joysticks import ( "encoding/binary" "fmt" "io" "os" "time" ) // see; https://www.kernel.org/doc/Documentation/input/joystick-api.txt type osEventRecord struct { Time uint32 // event timestamp in milliseconds 32bit so, about a month Value int16 // value Type uint8 // event type Index uint8 // axis/button } const maxValue = 1<<15 - 1 type hatAxis struct { number uint8 axis uint8 time time.Duration value float32 } type button struct { number uint8 time time.Duration value bool } type state struct { osEvent chan osEventRecord buttons map[uint8]button hatAxes map[uint8]hatAxis buttonCloseEvents map[uint8]chan event buttonOpenEvents map[uint8]chan event hatChangeEvents map[uint8]chan event } type event interface { Moment() time.Duration } type hatChangeEvent struct { time time.Duration x, y float32 } func (b hatChangeEvent) Moment() time.Duration { return b.time } type buttonChangeEvent struct { time time.Duration } func (b buttonChangeEvent) Moment() time.Duration { return b.time } type channel struct { number uint8 channelFn func(state, uint8) chan event } // Capture arranges for the registrees (channels) to get particular events. // Intended for bacic use since doesn't return state object. func Capture(registrees ...channel) []chan event { js, err := Connect(1) if err != nil { return nil } go js.ProcessEvents() chans := make([]chan event, len(registrees)) for i, fns := range registrees { chans[i] = fns.channelFn(js, fns.number) } return chans } // Connect sets up a go routine that puts a joysticks events onto registered channels. // to register channels use the returned state object's On<xxx>(index) methods. func Connect(index int) (js state, e error) { r, e := os.OpenFile(fmt.Sprintf("/dev/input/js%d", index-1), os.O_RDONLY, 0666) if e != nil { return } js = state{make(chan osEventRecord), make(map[uint8]button), make(map[uint8]hatAxis), make(map[uint8]chan event), make(map[uint8]chan event), make(map[uint8]chan event)} // start thread to read joystick eventd to the joystick.state osEvent channel go eventPipe(r, js.osEvent) js.populate() return js, nil } // fill in the joysticks available events from the synthetic state events burst produced initially by the driver. func (js state) populate() { for buttonNumber, hatNumber, axisNumber := 1, 1, 1; ; { evt := <-js.osEvent switch evt.Type { case 0x81: js.buttons[evt.Index] = button{uint8(buttonNumber), toDuration(evt.Time), evt.Value != 0} buttonNumber += 1 case 0x82: js.hatAxes[evt.Index] = hatAxis{uint8(hatNumber), uint8(axisNumber), toDuration(evt.Time), float32(evt.Value) / maxValue} axisNumber += 1 if axisNumber > 2 { axisNumber = 1 hatNumber += 1 } default: go func() { js.osEvent <- evt }() // put the consumed, first, after end of synthetic burst, real event, back on channel. return } } return } // pipe any readable events onto channel. func eventPipe(r io.Reader, c chan osEventRecord) { var evt osEventRecord for { err := binary.Read(r, binary.LittleEndian, &evt) if err != nil { close(c) return } c <- evt } } // interpret whats appearing on osEvent channel, then put, on any required out channel(s), the requisite event. func (js state) ProcessEvents() { for { evt, ok := <-js.osEvent if !ok { break } switch evt.Type { case 1: if evt.Value == 0 { if c, ok := js.buttonOpenEvents[js.buttons[evt.Index].number]; ok { c <- buttonChangeEvent{toDuration(evt.Time)} } } if evt.Value == 1 { if c, ok := js.buttonCloseEvents[js.buttons[evt.Index].number]; ok { c <- buttonChangeEvent{toDuration(evt.Time)} } } js.buttons[evt.Index] = button{js.buttons[evt.Index].number, toDuration(evt.Time), evt.Value != 0} case 2: if c, ok := js.hatChangeEvents[js.hatAxes[evt.Index].number]; ok { switch js.hatAxes[evt.Index].axis { case 1: c <- hatChangeEvent{toDuration(evt.Time), float32(evt.Value) / maxValue, js.hatAxes[evt.Index+1].value} case 2: c <- hatChangeEvent{toDuration(evt.Time), js.hatAxes[evt.Index-1].value, float32(evt.Value) / maxValue} } } js.hatAxes[evt.Index] = hatAxis{js.hatAxes[evt.Index].number, js.hatAxes[evt.Index].axis, toDuration(evt.Time), float32(evt.Value) / maxValue} default: // log.Println("unknown input type. ",evt.Type & 0x7f) } } } func toDuration(m uint32) time.Duration { return time.Duration(m) * 1000000 } func (js state) OnOpen(button uint8) (c chan event) { c = make(chan event) js.buttonOpenEvents[button] = c return c } func (js state) OnClose(button uint8) (c chan event) { c = make(chan event) js.buttonCloseEvents[button] = c return c } func (js state) OnMove(hatAsix uint8) (c chan event) { c = make(chan event) js.hatChangeEvents[hatAsix] = c return c } func (js state) ButtonExists(button uint8) (ok bool) { for _, v := range js.buttons { if v.number == button { return true } } return } func (js state) HatExists(hatAxis uint8) (ok bool) { for _, v := range js.hatAxes { if v.number == hatAxis { return true } } return } func (js state) InsertSyntheticEvent(v int16, t uint8, i uint8) { js.osEvent <- osEventRecord{Value: v, Type: t, Index: i} }
joysticks_test.go 0 → 100644 +94 −0 Original line number Diff line number Diff line package joysticks import ( "fmt" "testing" ) import . "github.com/splace/sounds" import ( "io" "os/exec" "time" ) import "math" func TestJoysticksCapture(t *testing.T) { events := Capture( channel{10, state.OnOpen}, // event[0] button #10 opens channel{1, state.OnClose}, // event[1] button #1 closes channel{1, state.OnMove}, // event[2] hat #1 moves ) var x float32 = .5 var f time.Duration = time.Second / 440 for { select { case <-events[0]: return case <-events[1]: play(NewSound(NewTone(f, float64(x)), time.Second/3)) case h := <-events[2]: x = h.(hatChangeEvent).x/2 + .5 f = time.Duration(100*math.Pow(2, float64(h.(hatChangeEvent).y))) * time.Second / 44000 } } } func TestJoysticksAdvanced(t *testing.T) { js1, err := Connect(1) if err != nil { panic(err) } if len(js1.buttons) < 10 || len(js1.hatAxes) < 6 { t.Errorf("joystick#1, available buttons %d, Hats %d\n", len(js1.buttons), len(js1.hatAxes)/2) } b1 := js1.OnClose(1) b2 := js1.OnClose(2) b3 := js1.OnClose(3) b4 := js1.OnClose(4) quit := js1.OnOpen(10) h1 := js1.OnMove(1) h2 := js1.OnMove(2) h3 := js1.OnMove(3) go js1.ProcessEvents() time.AfterFunc(time.Second*10, func() { js1.InsertSyntheticEvent(1, 1, 1) }) // value=1 (close),type=1 (button), index=1, so fires b1 after 10 seconds for { select { case <-quit: return case <-b1: play(NewSound(NewTone(time.Second/440, 1), time.Second/3)) case <-b2: play(NewSound(NewTone(time.Second/660, 1), time.Second/3)) case <-b3: play(NewSound(NewTone(time.Second/250, 1), time.Second/3)) case <-b4: play(NewSound(NewTone(time.Second/150, 1), time.Second/3)) case h := <-h1: fmt.Println("hat 1 moved", h) case h := <-h2: fmt.Println("hat 2 moved", h) case h := <-h3: fmt.Println("hat 3 moved", h) } } } func play(s Sound) { out, in := io.Pipe() go func() { Encode(in, 2, 44100, s) in.Close() }() cmd := exec.Command("aplay") cmd.Stdin = out err := cmd.Run() if err != nil { panic(err) } }