2014-04-27 18:02:39 -07:00
|
|
|
package joystick
|
2014-04-26 03:11:51 -07:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2023-06-11 02:25:01 +09:00
|
|
|
"os"
|
2014-11-19 15:45:59 -08:00
|
|
|
"time"
|
2014-07-09 18:32:27 -07:00
|
|
|
|
2023-09-23 12:32:31 +02:00
|
|
|
js "github.com/0xcafed00d/joystick"
|
2023-05-20 14:25:21 +02:00
|
|
|
"gobot.io/x/gobot/v2"
|
2014-04-26 03:11:51 -07:00
|
|
|
)
|
|
|
|
|
2018-08-25 18:02:01 +02:00
|
|
|
const (
|
|
|
|
// Dualshock3 joystick configuration.
|
|
|
|
Dualshock3 = "dualshock3"
|
|
|
|
|
|
|
|
// Dualshock4 joystick configuration.
|
|
|
|
Dualshock4 = "dualshock4"
|
|
|
|
|
2022-11-09 18:24:58 +01:00
|
|
|
// Dualsense joystick configuration.
|
|
|
|
Dualsense = "dualsense"
|
|
|
|
|
2018-08-25 18:02:01 +02:00
|
|
|
// TFlightHotasX flight stick configuration.
|
|
|
|
TFlightHotasX = "tflightHotasX"
|
|
|
|
|
2022-10-21 18:57:27 +02:00
|
|
|
// Configuration for Xbox 360 controller.
|
2018-08-25 18:02:01 +02:00
|
|
|
Xbox360 = "xbox360"
|
|
|
|
|
|
|
|
// Xbox360RockBandDrums controller configuration.
|
|
|
|
Xbox360RockBandDrums = "xbox360RockBandDrums"
|
2020-03-13 01:39:48 -07:00
|
|
|
|
2022-10-21 18:57:27 +02:00
|
|
|
// Configuration for the Xbox One controller.
|
|
|
|
XboxOne = "xboxOne"
|
2023-03-23 11:14:02 -07:00
|
|
|
|
|
|
|
// Nvidia Shield TV Controller
|
|
|
|
Shield = "shield"
|
|
|
|
|
|
|
|
// Nintendo Switch Joycon Controller Pair
|
|
|
|
NintendoSwitchPair = "joyconPair"
|
2018-08-25 18:02:01 +02:00
|
|
|
)
|
|
|
|
|
2016-09-25 21:07:36 +02:00
|
|
|
// Driver represents a joystick
|
|
|
|
type Driver struct {
|
2023-09-23 12:32:31 +02:00
|
|
|
name string
|
|
|
|
interval time.Duration
|
|
|
|
connection gobot.Connection
|
|
|
|
configPath string
|
|
|
|
config joystickConfig
|
|
|
|
buttonState map[int]bool
|
|
|
|
axisState map[int]int
|
|
|
|
|
|
|
|
halt chan bool
|
2014-11-28 18:37:03 -08:00
|
|
|
gobot.Eventer
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-10-20 10:46:03 -05:00
|
|
|
// pair is a JSON representation of name and id
|
2014-04-26 03:11:51 -07:00
|
|
|
type pair struct {
|
|
|
|
Name string `json:"name"`
|
2014-06-10 15:16:11 -07:00
|
|
|
ID int `json:"id"`
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
|
2014-10-20 10:46:03 -05:00
|
|
|
// joystickConfig is a JSON representation of configuration values
|
2014-04-26 03:11:51 -07:00
|
|
|
type joystickConfig struct {
|
|
|
|
Name string `json:"name"`
|
2015-01-03 05:06:08 -08:00
|
|
|
GUID string `json:"guid"`
|
2014-04-26 03:11:51 -07:00
|
|
|
Axis []pair `json:"axis"`
|
|
|
|
Buttons []pair `json:"buttons"`
|
|
|
|
}
|
|
|
|
|
2016-09-25 21:07:36 +02:00
|
|
|
// NewDriver returns a new Driver with a polling interval of
|
|
|
|
// 10 Milliseconds given a Joystick Adaptor and json button configuration
|
2015-01-03 05:06:08 -08:00
|
|
|
// file location.
|
2014-10-20 10:46:03 -05:00
|
|
|
//
|
2016-07-13 10:44:47 -06:00
|
|
|
// Optionally accepts:
|
2022-11-09 18:24:58 +01:00
|
|
|
//
|
|
|
|
// time.Duration: Interval at which the Driver is polled for new information
|
2016-09-25 21:07:36 +02:00
|
|
|
func NewDriver(a *Adaptor, config string, v ...time.Duration) *Driver {
|
|
|
|
d := &Driver{
|
2023-09-23 12:32:31 +02:00
|
|
|
name: gobot.DefaultName("Joystick"),
|
|
|
|
connection: a,
|
|
|
|
Eventer: gobot.NewEventer(),
|
|
|
|
configPath: config,
|
|
|
|
buttonState: make(map[int]bool),
|
|
|
|
axisState: make(map[int]int),
|
|
|
|
|
2014-11-28 18:37:03 -08:00
|
|
|
interval: 10 * time.Millisecond,
|
2023-05-19 14:16:22 +02:00
|
|
|
halt: make(chan bool),
|
2014-11-28 18:37:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(v) > 0 {
|
|
|
|
d.interval = v[0]
|
2014-04-27 18:02:39 -07:00
|
|
|
}
|
2014-04-26 03:11:51 -07:00
|
|
|
|
2014-11-19 15:45:59 -08:00
|
|
|
d.AddEvent("error")
|
2014-04-26 03:11:51 -07:00
|
|
|
return d
|
|
|
|
}
|
2015-01-03 05:06:08 -08:00
|
|
|
|
2016-09-25 21:07:36 +02:00
|
|
|
// Name returns the Drivers name
|
|
|
|
func (j *Driver) Name() string { return j.name }
|
2015-01-03 05:06:08 -08:00
|
|
|
|
2016-09-25 21:07:36 +02:00
|
|
|
// SetName sets the Drivers name
|
|
|
|
func (j *Driver) SetName(n string) { j.name = n }
|
|
|
|
|
|
|
|
// Connection returns the Drivers connection
|
|
|
|
func (j *Driver) Connection() gobot.Connection { return j.connection }
|
2014-04-26 03:11:51 -07:00
|
|
|
|
2014-10-20 10:46:03 -05:00
|
|
|
// adaptor returns joystick adaptor
|
2016-09-25 21:07:36 +02:00
|
|
|
func (j *Driver) adaptor() *Adaptor {
|
|
|
|
return j.Connection().(*Adaptor)
|
2014-06-15 17:22:50 -07:00
|
|
|
}
|
|
|
|
|
2015-01-03 05:06:08 -08:00
|
|
|
// Start and polls the state of the joystick at the given interval.
|
|
|
|
//
|
|
|
|
// Emits the Events:
|
2022-11-09 18:24:58 +01:00
|
|
|
//
|
2015-01-03 05:06:08 -08:00
|
|
|
// Error error - On button error
|
|
|
|
// Events defined in the json button configuration file.
|
|
|
|
// They will have the format:
|
|
|
|
// [button]_press
|
|
|
|
// [button]_release
|
|
|
|
// [axis]
|
2023-09-23 12:32:31 +02:00
|
|
|
func (j *Driver) Start() error {
|
|
|
|
if err := j.initConfig(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
j.initEvents()
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
state, err := j.adaptor().joystick.Read()
|
|
|
|
if err != nil {
|
|
|
|
j.Publish(j.Event("error"), err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// might just be missing a button definition, so keep going
|
|
|
|
if err := j.handleButtons(state); err != nil {
|
|
|
|
j.Publish(j.Event("error"), err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// might just be missing an axis definition, so keep going
|
|
|
|
if err := j.handleAxes(state); err != nil {
|
|
|
|
j.Publish(j.Event("error"), err)
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-time.After(j.interval):
|
|
|
|
case <-j.halt:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (j *Driver) initConfig() error {
|
2018-04-14 13:34:28 +02:00
|
|
|
switch j.configPath {
|
2022-10-21 18:57:27 +02:00
|
|
|
case Dualshock3:
|
2018-04-14 13:34:28 +02:00
|
|
|
j.config = dualshock3Config
|
2022-10-21 18:57:27 +02:00
|
|
|
case Dualshock4:
|
2018-04-14 13:34:28 +02:00
|
|
|
j.config = dualshock4Config
|
2022-11-09 18:24:58 +01:00
|
|
|
case Dualsense:
|
|
|
|
j.config = dualsenseConfig
|
2022-10-21 18:57:27 +02:00
|
|
|
case TFlightHotasX:
|
2018-05-08 07:25:37 +01:00
|
|
|
j.config = tflightHotasXConfig
|
2022-10-21 18:57:27 +02:00
|
|
|
case Xbox360:
|
2018-04-14 13:34:28 +02:00
|
|
|
j.config = xbox360Config
|
2022-10-21 18:57:27 +02:00
|
|
|
case Xbox360RockBandDrums:
|
2018-05-07 23:46:17 +02:00
|
|
|
j.config = xbox360RockBandDrumsConfig
|
2022-10-21 18:57:27 +02:00
|
|
|
case XboxOne:
|
|
|
|
j.config = xboxOneConfig
|
2023-03-23 11:14:02 -07:00
|
|
|
case Shield:
|
|
|
|
j.config = shieldConfig
|
|
|
|
case NintendoSwitchPair:
|
|
|
|
j.config = joyconPairConfig
|
2018-04-14 13:34:28 +02:00
|
|
|
default:
|
2018-08-04 23:07:02 -04:00
|
|
|
err := j.loadFile()
|
|
|
|
if err != nil {
|
2023-09-23 12:32:31 +02:00
|
|
|
return fmt.Errorf("loadfile error: %w", err)
|
2018-08-04 23:07:02 -04:00
|
|
|
}
|
2014-11-19 15:45:59 -08:00
|
|
|
}
|
|
|
|
|
2023-09-23 12:32:31 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (j *Driver) initEvents() {
|
2014-11-19 15:45:59 -08:00
|
|
|
for _, value := range j.config.Buttons {
|
|
|
|
j.AddEvent(fmt.Sprintf("%s_press", value.Name))
|
|
|
|
j.AddEvent(fmt.Sprintf("%s_release", value.Name))
|
|
|
|
}
|
|
|
|
for _, value := range j.config.Axis {
|
|
|
|
j.AddEvent(value.Name)
|
|
|
|
}
|
2014-07-22 13:55:19 -07:00
|
|
|
}
|
|
|
|
|
2014-11-19 23:21:19 -08:00
|
|
|
// Halt stops joystick driver
|
2016-11-07 19:32:12 +01:00
|
|
|
func (j *Driver) Halt() (err error) {
|
2014-12-23 01:20:44 -08:00
|
|
|
j.halt <- true
|
|
|
|
return
|
|
|
|
}
|
2014-11-19 23:21:19 -08:00
|
|
|
|
2023-09-23 12:32:31 +02:00
|
|
|
func (j *Driver) handleButtons(state js.State) error {
|
|
|
|
for button := 0; button < j.adaptor().joystick.ButtonCount(); button++ {
|
|
|
|
buttonPressed := state.Buttons&(1<<uint32(button)) != 0
|
|
|
|
if buttonPressed != j.buttonState[button] {
|
|
|
|
j.buttonState[button] = buttonPressed
|
|
|
|
name := j.findName(uint8(button), j.config.Buttons)
|
|
|
|
if name == "" {
|
|
|
|
return fmt.Errorf("Unknown button: %v", button)
|
2014-07-22 13:55:19 -07:00
|
|
|
}
|
2023-09-23 12:32:31 +02:00
|
|
|
|
|
|
|
if buttonPressed {
|
|
|
|
j.Publish(j.Event(fmt.Sprintf("%s_press", name)), nil)
|
2018-04-14 12:33:38 +02:00
|
|
|
} else {
|
2023-09-23 12:32:31 +02:00
|
|
|
j.Publish(j.Event(fmt.Sprintf("%s_release", name)), nil)
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
}
|
2023-09-23 12:32:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (j *Driver) handleAxes(state js.State) error {
|
|
|
|
for axis := 0; axis < j.adaptor().joystick.AxisCount(); axis++ {
|
|
|
|
name := j.findName(uint8(axis), j.config.Axis)
|
|
|
|
if name == "" {
|
|
|
|
return fmt.Errorf("Unknown Axis: %v", axis)
|
|
|
|
}
|
|
|
|
|
|
|
|
if j.axisState[axis] != state.AxisData[axis] {
|
|
|
|
j.axisState[axis] = state.AxisData[axis]
|
|
|
|
j.Publish(name, state.AxisData[axis])
|
2014-07-22 13:55:19 -07:00
|
|
|
}
|
|
|
|
}
|
2023-09-23 12:32:31 +02:00
|
|
|
|
2014-07-22 13:55:19 -07:00
|
|
|
return nil
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
2014-07-22 13:55:19 -07:00
|
|
|
|
2023-09-23 12:32:31 +02:00
|
|
|
// findName returns name from button or axis found by id in provided list
|
2016-09-25 21:07:36 +02:00
|
|
|
func (j *Driver) findName(id uint8, list []pair) string {
|
2014-04-26 03:11:51 -07:00
|
|
|
for _, value := range list {
|
2014-06-10 15:16:11 -07:00
|
|
|
if int(id) == value.ID {
|
2014-04-26 03:11:51 -07:00
|
|
|
return value.Name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2023-09-23 12:32:31 +02:00
|
|
|
// findID returns the ID based on the name from button or axis.
|
|
|
|
func (j *Driver) findID(name string, list []pair) int {
|
|
|
|
for _, value := range list {
|
|
|
|
if name == value.Name {
|
|
|
|
return value.ID
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
|
|
|
}
|
2023-09-23 12:32:31 +02:00
|
|
|
return 0
|
2014-04-26 03:11:51 -07:00
|
|
|
}
|
2018-04-14 13:34:28 +02:00
|
|
|
|
|
|
|
// loadFile load the joystick config from a .json file
|
|
|
|
func (j *Driver) loadFile() error {
|
2023-06-11 02:25:01 +09:00
|
|
|
file, e := os.ReadFile(j.configPath)
|
2018-04-14 13:34:28 +02:00
|
|
|
if e != nil {
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
|
|
|
|
var jsontype joystickConfig
|
2023-06-12 19:51:25 +02:00
|
|
|
if err := json.Unmarshal(file, &jsontype); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-04-14 13:34:28 +02:00
|
|
|
j.config = jsontype
|
|
|
|
return nil
|
|
|
|
}
|