gobot.io/x/gobot/v2@v2.1.0/platforms/joystick/joystick_driver.go (about)

     1  package joystick
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"time"
     8  
     9  	"github.com/veandco/go-sdl2/sdl"
    10  	"gobot.io/x/gobot/v2"
    11  )
    12  
    13  const (
    14  	// Dualshock3 joystick configuration.
    15  	Dualshock3 = "dualshock3"
    16  
    17  	// Dualshock4 joystick configuration.
    18  	Dualshock4 = "dualshock4"
    19  
    20  	// Dualsense joystick configuration.
    21  	Dualsense = "dualsense"
    22  
    23  	// TFlightHotasX flight stick configuration.
    24  	TFlightHotasX = "tflightHotasX"
    25  
    26  	// Configuration for Xbox 360 controller.
    27  	Xbox360 = "xbox360"
    28  
    29  	// Xbox360RockBandDrums controller configuration.
    30  	Xbox360RockBandDrums = "xbox360RockBandDrums"
    31  
    32  	// Configuration for the Xbox One controller.
    33  	XboxOne = "xboxOne"
    34  
    35  	// Nvidia Shield TV Controller
    36  	Shield = "shield"
    37  
    38  	// Nintendo Switch Joycon Controller Pair
    39  	NintendoSwitchPair = "joyconPair"
    40  )
    41  
    42  // Driver represents a joystick
    43  type Driver struct {
    44  	name       string
    45  	interval   time.Duration
    46  	connection gobot.Connection
    47  	configPath string
    48  	config     joystickConfig
    49  	poll       func() sdl.Event
    50  	halt       chan bool
    51  	gobot.Eventer
    52  }
    53  
    54  // pair is a JSON representation of name and id
    55  type pair struct {
    56  	Name string `json:"name"`
    57  	ID   int    `json:"id"`
    58  }
    59  
    60  // hat is a JSON representation of hat, name and id
    61  type hat struct {
    62  	Hat  int    `json:"hat"`
    63  	Name string `json:"name"`
    64  	ID   int    `json:"id"`
    65  }
    66  
    67  // joystickConfig is a JSON representation of configuration values
    68  type joystickConfig struct {
    69  	Name    string `json:"name"`
    70  	GUID    string `json:"guid"`
    71  	Axis    []pair `json:"axis"`
    72  	Buttons []pair `json:"buttons"`
    73  	Hats    []hat  `json:"Hats"`
    74  }
    75  
    76  // NewDriver returns a new Driver with a polling interval of
    77  // 10 Milliseconds given a Joystick Adaptor and json button configuration
    78  // file location.
    79  //
    80  // Optionally accepts:
    81  //
    82  //	time.Duration: Interval at which the Driver is polled for new information
    83  func NewDriver(a *Adaptor, config string, v ...time.Duration) *Driver {
    84  	d := &Driver{
    85  		name:       gobot.DefaultName("Joystick"),
    86  		connection: a,
    87  		Eventer:    gobot.NewEventer(),
    88  		configPath: config,
    89  		poll: func() sdl.Event {
    90  			return sdl.PollEvent()
    91  		},
    92  		interval: 10 * time.Millisecond,
    93  		halt:     make(chan bool),
    94  	}
    95  
    96  	if len(v) > 0 {
    97  		d.interval = v[0]
    98  	}
    99  
   100  	d.AddEvent("error")
   101  	return d
   102  }
   103  
   104  // Name returns the Drivers name
   105  func (j *Driver) Name() string { return j.name }
   106  
   107  // SetName sets the Drivers name
   108  func (j *Driver) SetName(n string) { j.name = n }
   109  
   110  // Connection returns the Drivers connection
   111  func (j *Driver) Connection() gobot.Connection { return j.connection }
   112  
   113  // adaptor returns joystick adaptor
   114  func (j *Driver) adaptor() *Adaptor {
   115  	return j.Connection().(*Adaptor)
   116  }
   117  
   118  // Start and polls the state of the joystick at the given interval.
   119  //
   120  // Emits the Events:
   121  //
   122  //	Error error - On button error
   123  //	Events defined in the json button configuration file.
   124  //	They will have the format:
   125  //		[button]_press
   126  //		[button]_release
   127  //		[axis]
   128  func (j *Driver) Start() (err error) {
   129  	switch j.configPath {
   130  	case Dualshock3:
   131  		j.config = dualshock3Config
   132  	case Dualshock4:
   133  		j.config = dualshock4Config
   134  	case Dualsense:
   135  		j.config = dualsenseConfig
   136  	case TFlightHotasX:
   137  		j.config = tflightHotasXConfig
   138  	case Xbox360:
   139  		j.config = xbox360Config
   140  	case Xbox360RockBandDrums:
   141  		j.config = xbox360RockBandDrumsConfig
   142  	case XboxOne:
   143  		j.config = xboxOneConfig
   144  	case Shield:
   145  		j.config = shieldConfig
   146  	case NintendoSwitchPair:
   147  		j.config = joyconPairConfig
   148  	default:
   149  		err := j.loadFile()
   150  		if err != nil {
   151  			return err
   152  		}
   153  	}
   154  
   155  	for _, value := range j.config.Buttons {
   156  		j.AddEvent(fmt.Sprintf("%s_press", value.Name))
   157  		j.AddEvent(fmt.Sprintf("%s_release", value.Name))
   158  	}
   159  	for _, value := range j.config.Axis {
   160  		j.AddEvent(value.Name)
   161  	}
   162  	for _, value := range j.config.Hats {
   163  		j.AddEvent(fmt.Sprintf("%s_press", value.Name))
   164  		j.AddEvent(fmt.Sprintf("%s_release", value.Name))
   165  	}
   166  
   167  	go func() {
   168  		for {
   169  			for event := j.poll(); event != nil; event = j.poll() {
   170  				if errs := j.handleEvent(event); errs != nil {
   171  					j.Publish(j.Event("error"), errs)
   172  				}
   173  			}
   174  			select {
   175  			case <-time.After(j.interval):
   176  			case <-j.halt:
   177  				return
   178  			}
   179  		}
   180  	}()
   181  	return
   182  }
   183  
   184  // Halt stops joystick driver
   185  func (j *Driver) Halt() (err error) {
   186  	j.halt <- true
   187  	return
   188  }
   189  
   190  var previousHat = ""
   191  
   192  // HandleEvent publishes an specific event according to data received
   193  func (j *Driver) handleEvent(event sdl.Event) error {
   194  	switch data := event.(type) {
   195  	case *sdl.JoyAxisEvent:
   196  		if data.Which == j.adaptor().joystick.InstanceID() {
   197  			axis := j.findName(data.Axis, j.config.Axis)
   198  			if axis == "" {
   199  				return fmt.Errorf("Unknown Axis: %v", data.Axis)
   200  			}
   201  			j.Publish(j.Event(axis), data.Value)
   202  		}
   203  	case *sdl.JoyButtonEvent:
   204  		if data.Which == j.adaptor().joystick.InstanceID() {
   205  			button := j.findName(data.Button, j.config.Buttons)
   206  			if button == "" {
   207  				return fmt.Errorf("Unknown Button: %v", data.Button)
   208  			}
   209  			if data.State == 1 {
   210  				j.Publish(j.Event(fmt.Sprintf("%s_press", button)), nil)
   211  			} else {
   212  				j.Publish(j.Event(fmt.Sprintf("%s_release", button)), nil)
   213  			}
   214  		}
   215  	case *sdl.JoyHatEvent:
   216  		if data.Which == j.adaptor().joystick.InstanceID() {
   217  			hat := j.findHatName(data.Value, data.Hat, j.config.Hats)
   218  			if hat == "" {
   219  				return fmt.Errorf("Unknown Hat: %v %v", data.Hat, data.Value)
   220  			} else if hat == "released" {
   221  				hat = previousHat
   222  				j.Publish(j.Event(fmt.Sprintf("%s_release", hat)), true)
   223  			} else {
   224  				previousHat = hat
   225  				j.Publish(j.Event(fmt.Sprintf("%s_press", hat)), true)
   226  			}
   227  		}
   228  	}
   229  	return nil
   230  }
   231  
   232  func (j *Driver) findName(id uint8, list []pair) string {
   233  	for _, value := range list {
   234  		if int(id) == value.ID {
   235  			return value.Name
   236  		}
   237  	}
   238  	return ""
   239  }
   240  
   241  // findHatName returns name from hat found by id in provided list
   242  func (j *Driver) findHatName(id uint8, hat uint8, list []hat) string {
   243  	for _, lHat := range list {
   244  		if int(id) == lHat.ID && int(hat) == lHat.Hat {
   245  			return lHat.Name
   246  		}
   247  	}
   248  	return ""
   249  }
   250  
   251  // loadFile load the joystick config from a .json file
   252  func (j *Driver) loadFile() error {
   253  	file, e := ioutil.ReadFile(j.configPath)
   254  	if e != nil {
   255  		return e
   256  	}
   257  
   258  	var jsontype joystickConfig
   259  	json.Unmarshal(file, &jsontype)
   260  	j.config = jsontype
   261  	return nil
   262  }