Skip to content

Commit b87d7a9

Browse files
committed
init
0 parents  commit b87d7a9

File tree

8 files changed

+305
-0
lines changed

8 files changed

+305
-0
lines changed

.editorconfig

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
; http://editorconfig.org/
2+
3+
root = true
4+
5+
[*]
6+
indent_style = space
7+
indent_size = 2
8+
end_of_line = lf
9+
charset = utf-8
10+
trim_trailing_whitespace = true
11+
insert_final_newline = true
12+
13+
[*.md]
14+
trim_trailing_whitespace = false
15+
16+
[*.php]
17+
indent_size = 4
18+
19+
[*.go]
20+
indent_style = tab
21+
indent_size = 4

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
vendor

.travis.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
language: go
2+
os:
3+
- linux
4+
- osx
5+
go:
6+
- 1.9.x
7+
- 1.10.x
8+
- 1.11.x
9+
- master

LICENSE

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) Frederik Kvartborg Albertsen <[email protected]>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining
6+
a copy of this software and associated documentation files (the
7+
"Software"), to deal in the Software without restriction, including
8+
without limitation the rights to use, copy, modify, merge, publish,
9+
distribute, sublicense, and/or sell copies of the Software, and to
10+
permit persons to whom the Software is furnished to do so, subject to
11+
the following conditions:
12+
13+
The above copyright notice and this permission notice shall be
14+
included in all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# go-dualshock
2+
3+
[![Version](https://img.shields.io/github/release/kvartborg/go-dualshock.svg)](https://github.com/kvartborg/go-dualshock/releases)
4+
[![Build Status](https://travis-ci.org/kvartborg/go-dualshock.svg?branch=master)](https://travis-ci.org/kvartborg/go-dualshock)
5+
[![GoDoc](https://godoc.org/github.com/kvartborg/go-dualshock?status.svg)](https://godoc.org/github.com/kvartborg/go-dualshock)
6+
[![Go Report Card](https://goreportcard.com/badge/github.com/kvartborg/go-dualshock)](https://goreportcard.com/report/github.com/kvartborg/go-dualshock)
7+
8+
9+
Connect a PS4 DualShock controller with your go program.
10+
11+
### Install
12+
```sh
13+
go get github.com/kvartborg/go-dualshock
14+
```
15+
16+
### Example
17+
```go
18+
package main
19+
20+
import (
21+
"github.com/karalabe/hid"
22+
dualshock "github.com/kvartborg/go-dualshock"
23+
)
24+
25+
func main() {
26+
vendorID, productID := uint16(1356), uint16(1476)
27+
devices := hid.Enumerate(vendorID, productID)
28+
29+
if len(devices) == 0 {
30+
log.Fatal("no dualshock controller where found")
31+
}
32+
33+
device, err := devices[0].Open()
34+
35+
if err != nil {
36+
log.Fatal(err)
37+
}
38+
39+
controller := dualshock.New(device)
40+
41+
controller.Listen(func(state dualshock.State) {
42+
fmt.Print(state.Analog.L2)
43+
})
44+
}
45+
```
46+
47+
### License
48+
This project is licensed under the [MIT License](https://github.com/kvartborg/go-dualshock/blob/master/LICENSE).

dualshock.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package dualshock
2+
3+
import (
4+
"encoding/binary"
5+
"io"
6+
"time"
7+
)
8+
9+
// Controller describes the reference to the hardware device
10+
type Controller struct {
11+
reader io.Reader
12+
queue chan State
13+
interrupt chan int
14+
}
15+
16+
// DPad is the data structure describing the joysticks on the controller
17+
type DPad struct {
18+
X, Y int
19+
}
20+
21+
// TrackPad is the touch surface in the middle of the controller
22+
type TrackPad struct {
23+
ID int
24+
Active bool
25+
X, Y int
26+
}
27+
28+
// Motion contains information about acceleration in x, y and z axis
29+
type Motion struct {
30+
X, Y, Z int16
31+
}
32+
33+
// Orientation describes roll, yaw and pith of the controller
34+
type Orientation struct {
35+
Roll, Yaw, Pitch int16
36+
}
37+
38+
// State is the overall state of the controller, the controller will output 254
39+
// states within a second.
40+
type State struct {
41+
L1, L2, L3 bool
42+
R1, R2, R3 bool
43+
Up, Down, Left, Right bool
44+
Cross, Circle, Square, Triangle bool
45+
Share, Options, PSHome bool
46+
Timestamp, BatteryLevel int
47+
LeftDPad DPad
48+
RigthDPad DPad
49+
Motion Motion
50+
Orientation Orientation
51+
52+
// TrackPad is true if its pressed
53+
TrackPad bool
54+
55+
// TrackPad0 contains info relating to a touch event
56+
TrackPad0 TrackPad
57+
58+
// TrackPad1 only register touches if TrackPad0 also does, this enables
59+
// multi touch functionality
60+
TrackPad1 TrackPad
61+
// Analog describes the analog position of buttons, on the PS4 controller its
62+
// only L2 and R2 which has analog output as well as digital.
63+
Analog struct{ L2, R2 int }
64+
}
65+
66+
// transform reads a slice of bytes and turns them into a valid state for the
67+
// controller
68+
func transform(b []byte) State {
69+
return State{
70+
L1: (b[6] & 0x01) != 0,
71+
L2: (b[6] & 0x04) != 0,
72+
L3: (b[6] & 0x40) != 0,
73+
R1: (b[6] & 0x02) != 0,
74+
R2: (b[6] & 0x08) != 0,
75+
R3: (b[6] & 0x80) != 0,
76+
Up: (b[5]&15) == 0 || (b[5]&15) == 1 || (b[5]&15) == 7,
77+
Down: (b[5]&15) == 3 || (b[5]&15) == 4 || (b[5]&15) == 5,
78+
Left: (b[5]&15) == 5 || (b[5]&15) == 6 || (b[5]&15) == 7,
79+
Right: (b[5]&15) == 1 || (b[5]&15) == 2 || (b[5]&15) == 3,
80+
Cross: (b[5] & 32) != 0,
81+
Circle: (b[5] & 64) != 0,
82+
Square: (b[5] & 16) != 0,
83+
Triangle: (b[5] & 128) != 0,
84+
Share: (b[6] & 0x10) != 0,
85+
Options: (b[6] & 0x20) != 0,
86+
PSHome: (b[7] & 1) != 0,
87+
TrackPad: (b[7] & 2) != 0,
88+
TrackPad0: TrackPad{
89+
ID: int(b[35] & 0x7f),
90+
Active: (b[35] >> 7) == 0,
91+
X: int(((b[37] & 0x0f) << 8) | b[36]),
92+
Y: int(b[38]<<4 | ((b[37] & 0xf0) >> 4)),
93+
},
94+
TrackPad1: TrackPad{
95+
ID: int(b[39] & 0x7f),
96+
Active: (b[39] >> 7) == 0,
97+
X: int(((b[41] & 0x0f) << 8) | b[40]),
98+
Y: int(b[42]<<4 | ((b[41] & 0xf0) >> 4)),
99+
},
100+
LeftDPad: DPad{
101+
X: int(b[1]),
102+
Y: int(b[2]),
103+
},
104+
RigthDPad: DPad{
105+
X: int(b[3]),
106+
Y: int(b[4]),
107+
},
108+
Motion: Motion{
109+
Y: int16(binary.LittleEndian.Uint16(b[13:])),
110+
X: -int16(binary.LittleEndian.Uint16(b[15:])),
111+
Z: -int16(binary.LittleEndian.Uint16(b[17:])),
112+
},
113+
Orientation: Orientation{
114+
Roll: -int16(binary.LittleEndian.Uint16(b[19:])),
115+
Yaw: int16(binary.LittleEndian.Uint16(b[21:])),
116+
Pitch: int16(binary.LittleEndian.Uint16(b[23:])),
117+
},
118+
Analog: struct{ L2, R2 int }{
119+
L2: int(b[8]),
120+
R2: int(b[9]),
121+
},
122+
Timestamp: int(b[7] >> 2),
123+
BatteryLevel: int(b[12]),
124+
}
125+
}
126+
127+
// New returns a new controller which transforms input from the device to a valid
128+
// controller state
129+
func New(reader io.Reader) *Controller {
130+
c := &Controller{reader, make(chan State), make(chan int, 2)}
131+
go c.read()
132+
return c
133+
}
134+
135+
// read transforms data from the io.Reader and pushes it to the queue of
136+
// states
137+
func (c *Controller) read() {
138+
b := make([]byte, 64)
139+
140+
for {
141+
select {
142+
case <-c.interrupt:
143+
return
144+
default:
145+
c.reader.Read(b)
146+
c.queue <- transform(b)
147+
time.Sleep((1000 / 254) * time.Millisecond)
148+
}
149+
}
150+
}
151+
152+
// Listen for controller state changes
153+
func (c *Controller) Listen(handler func(State)) {
154+
for {
155+
select {
156+
case <-c.interrupt:
157+
return
158+
default:
159+
handler(<-c.queue)
160+
}
161+
}
162+
}
163+
164+
// Close the listener
165+
func (c *Controller) Close() {
166+
c.interrupt <- 1 // close reader
167+
c.interrupt <- 1 // close listener
168+
close(c.interrupt)
169+
close(c.queue)
170+
}

dualshock_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package dualshock_test
2+
3+
import (
4+
"testing"
5+
6+
dualshock "github.com/kvartborg/go-dualshock"
7+
)
8+
9+
type fakeDevice struct{}
10+
11+
func (f fakeDevice) Read(b []byte) (int, error) {
12+
copy(b, []byte{
13+
1, 134, 127, 128, 126, 8, 4, 88, 255, 0, 141, 219, 9, 188, 255, 4, 0,
14+
167, 255, 250, 6, 212, 31, 51, 254, 0, 0, 0, 0, 0, 27, 0, 0, 1, 252,
15+
129, 115, 70, 27, 130, 62, 97, 32, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0,
16+
128, 0, 0, 0, 128, 0, 0, 0, 0, 128, 0,
17+
})
18+
return 0, nil
19+
}
20+
21+
func TestDualshock(t *testing.T) {
22+
controller := dualshock.New(fakeDevice{})
23+
24+
result := make(chan dualshock.State, 1)
25+
controller.Listen(func(state dualshock.State) {
26+
controller.Close()
27+
result <- state
28+
})
29+
30+
if r := <-result; !r.L2 {
31+
t.Errorf("Invalid state L2 should be true; got %v", r.L2)
32+
}
33+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module github.com/kvartborg/go-dualshock

0 commit comments

Comments
 (0)