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