blob: 1c3e63970e757463f785735b9fa3ab8e4fb643fb [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef DEVICE_GAMEPAD_NINTENDO_CONTROLLER_H_
#define DEVICE_GAMEPAD_NINTENDO_CONTROLLER_H_
#include <memory>
#include <vector>
#include "base/cancelable_callback.h"
#include "base/optional.h"
#include "device/gamepad/abstract_haptic_gamepad.h"
#include "device/gamepad/gamepad_id_list.h"
#include "device/gamepad/gamepad_standard_mappings.h"
#include "device/gamepad/public/cpp/gamepad.h"
#include "services/device/public/mojom/hid.mojom.h"
namespace device {
// NintendoController represents a gaming input for a Nintendo console. In some
// cases, multiple discrete devices can be associated to form one logical
// input. A single NintendoController instance may represent a discrete device,
// or may represent two devices associated to form a composite input. (For
// instance, a pair of matched Joy-Cons may be treated as a single gamepad.)
//
// Switch devices must be initialized in order to provide a good experience.
// Devices that connect over Bluetooth (Joy-Cons and the Pro Controller) default
// to a HID interface that exposes only partial functionality. Devices that
// connect over USB (Pro Controller and Charging Grip) send no controller data
// reports until the initialization sequence is started. In both cases,
// initialization is necessary in order to configure device LEDs, enable and
// disable device features, and fetch calibration data.
//
// After initialization, the Joy-Con or Pro Controller should be in the
// following state:
// * Faster baud rate, if connected over USB.
// * Player indicator light 1 is lit, others are unlit.
// * Home light is 100% on.
// * Accelerometer and gyroscope inputs are enabled with default sensitivity.
// * Vibration is enabled.
// * NFC is disabled.
// * Configured to send controller data reports with report ID 0x30.
//
// Joy-Cons and the Pro Controller provide uncalibrated joystick input. The
// devices store factory calibration data for scaling the joystick axes and
// applying deadzones.
//
// Dual-rumble vibration effects are supported for both discrete devices and for
// composite devices. Joy-Cons and the Pro Controller use linear resonant
// actuators (LRAs) instead of the traditional eccentric rotating mass (ERM)
// vibration actuators. The LRAs are controlled using frequency and amplitude
// pairs rather than a single magnitude value. To simulate a dual-rumble effect,
// the left and right LRAs are set to vibrate at different frequencies as though
// they were the strong and weak ERM actuators. The amplitudes are set to the
// strong and weak effect magnitudes. When a vibration effect is played on a
// composite device, the effect is split so that each component receives one
// channel of the dual-rumble effect.
class NintendoController : public AbstractHapticGamepad {
public:
struct SwitchCalibrationData {
SwitchCalibrationData();
~SwitchCalibrationData();
// Analog stick calibration data.
uint16_t lx_center = 0;
uint16_t lx_min = 0;
uint16_t lx_max = 0;
uint16_t ly_center = 0;
uint16_t ly_min = 0;
uint16_t ly_max = 0;
uint16_t rx_center = 0;
uint16_t rx_min = 0;
uint16_t rx_max = 0;
uint16_t ry_center = 0;
uint16_t ry_min = 0;
uint16_t ry_max = 0;
uint16_t dead_zone = 0;
uint16_t range_ratio = 0;
// IMU calibration data.
uint16_t accelerometer_origin_x = 0;
uint16_t accelerometer_origin_y = 0;
uint16_t accelerometer_origin_z = 0;
uint16_t accelerometer_sensitivity_x = 0;
uint16_t accelerometer_sensitivity_y = 0;
uint16_t accelerometer_sensitivity_z = 0;
uint16_t gyro_origin_x = 0;
uint16_t gyro_origin_y = 0;
uint16_t gyro_origin_z = 0;
uint16_t gyro_sensitivity_x = 0;
uint16_t gyro_sensitivity_y = 0;
uint16_t gyro_sensitivity_z = 0;
uint16_t horizontal_offset_x = 0;
uint16_t horizontal_offset_y = 0;
uint16_t horizontal_offset_z = 0;
};
// One frame of accerometer and gyroscope data.
struct SwitchImuData {
SwitchImuData();
~SwitchImuData();
uint16_t accelerometer_x;
uint16_t accelerometer_y;
uint16_t accelerometer_z;
uint16_t gyro_x;
uint16_t gyro_y;
uint16_t gyro_z;
};
~NintendoController() override;
// Create a NintendoController for a newly-connected HID device.
static std::unique_ptr<NintendoController> Create(
int source_id,
mojom::HidDeviceInfoPtr device_info,
mojom::HidManager* hid_manager);
// Create a composite NintendoController from two already-connected HID
// devices.
static std::unique_ptr<NintendoController> CreateComposite(
int source_id,
std::unique_ptr<NintendoController> composite1,
std::unique_ptr<NintendoController> composite2,
mojom::HidManager* hid_manager);
// Return true if |vendor_id| and |product_id| describe a Nintendo controller.
static bool IsNintendoController(uint16_t vendor_id, uint16_t product_id);
// Decompose a composite device and return a vector of its subcomponents.
// Return an empty vector if the device is non-composite.
std::vector<std::unique_ptr<NintendoController>> Decompose();
// Begin the initialization sequence. When the device is ready to report
// controller data, |device_ready_closure| is called.
void Open(base::OnceClosure device_ready_closure);
// Return true if the device has completed initialization and is ready to
// report controller data.
bool IsOpen() const { return is_composite_ || connection_.is_bound(); }
// Return true if the device is ready to be assigned a gamepad slot.
bool IsUsable() const;
// Return true if the device is composed of multiple subdevices.
bool IsComposite() const { return is_composite_; }
// Return the source ID assigned to this device.
int GetSourceId() const { return source_id_; }
// Return the bus type for this controller.
GamepadBusType GetBusType() const { return bus_type_; }
// Return the mapping function for this controller.
GamepadStandardMappingFunction GetMappingFunction() const;
// Return true if |guid| is the device GUID for any of the HID devices
// opened for this controller.
bool HasGuid(const std::string& guid) const;
// Perform one-time initialization for the gamepad data in |pad|.
void InitializeGamepadState(bool has_standard_mapping, Gamepad& pad) const;
// Update the button and axis state in |pad|.
void UpdateGamepadState(Gamepad& pad) const;
// Update the button and axis state in |pad| for a Joy-Con L or for the left
// side of a Pro controller. If |horizontal| is true, also remap buttons and
// axes for a horizontal orientation.
void UpdateLeftGamepadState(Gamepad& pad, bool horizontal) const;
// Update the button and axis state in |pad| for a Joy-Con R or for the right
// side of a Pro controller. If |horizontal| is true, also remap buttons and
// axes for a horizontal orientation.
void UpdateRightGamepadState(Gamepad& pad, bool horizontal) const;
// Return the handedness of the device, or GamepadHand::kNone if the device
// is not intended to be used in a specific hand.
GamepadHand GetGamepadHand() const;
// AbstractHapticGamepad implementation.
void DoShutdown() override;
void SetVibration(double strong_magnitude, double weak_magnitude) override;
double GetMaxEffectDurationMillis() override;
NintendoController(int source_id,
mojom::HidDeviceInfoPtr device_info,
mojom::HidManager* hid_manager);
NintendoController(int source_id,
std::unique_ptr<NintendoController> composite1,
std::unique_ptr<NintendoController> composite2,
mojom::HidManager* hid_manager);
private:
enum InitializationState {
// Switch Pro requires initialization to configure the device for USB mode
// and to read calibration data.
kUninitialized = 0,
// Fetch the MAC address. This allows us to identify when the device is
// double-connected through USB and Bluetooth.
kPendingMacAddress,
// Increase the baud rate to improve latency. This requires a handshake
// before and after the change.
kPendingHandshake1,
kPendingBaudRate,
kPendingHandshake2,
// Disable the USB timeout. This subcommand is not acked, so also send an
// unused subcommand (0x33) which is acked.
kPendingDisableUsbTimeout,
// Set the player lights to the default (player 1).
kPendingSetPlayerLights,
// Disable the accelerometer and gyro.
kPendingEnableImu,
// Configure accelerometer and gyro sensitivity.
kPendingSetImuSensitivity,
// Read the calibration settings for the accelerometer and gyro.
kPendingReadImuCalibration,
// Read the dead zone and range ratio for the analog sticks.
kPendingReadAnalogStickParameters,
// Read the calibration settings for the horizontal orientation.
kPendingReadHorizontalOffsets,
// Read the calibration settings for the analog sticks.
kPendingReadAnalogStickCalibration,
// Enable vibration.
kPendingEnableVibration,
// Turn on the Home light.
kPendingSetHomeLight,
// Set standard full mode (60 Hz).
kPendingSetInputReportMode,
// Wait for controller data to be received.
kPendingControllerData,
// Fully initialized.
kInitialized,
};
// Initiate a connection request to the HID device.
void Connect(mojom::HidManager::ConnectCallback callback);
// Completion callback for the HID connection request.
void OnConnect(mojom::HidConnectionPtr connection);
// Initiate the sequence of exchanges to prepare the device to provide
// controller data.
void StartInitSequence();
// Transition to |state| and makes the request(s) associated with the state.
// May be called repeatedly to retry the current initialization step.
void MakeInitSequenceRequests(InitializationState state);
// Mark the device as initialized.
void FinishInitSequence();
// Mark the device as uninitialized.
void FailInitSequence();
// Handle an input report sent by the device. The first byte of the report
// (the report ID) has been extracted to |report_id| and the remaining bytes
// are in |report_bytes|.
void HandleInputReport(uint8_t report_id,
const std::vector<uint8_t>& report_bytes);
// Handle a USB input report with report ID 0x81. These reports are used
// during device initialization.
void HandleUsbInputReport81(const std::vector<uint8_t>& report_bytes);
// Handle a USB or Bluetooth input report with ID 0x21. These reports carry
// controller data and subcommand responses.
void HandleInputReport21(const std::vector<uint8_t>& report_bytes);
// Handle a USB or Bluetooth input report with ID 0x30. These reports carry
// controller data and IMU data.
void HandleInputReport30(const std::vector<uint8_t>& report_bytes);
// Check the result of a received input report and decide whether to
// transition to the next step in the initialization sequence.
void ContinueInitSequence(uint8_t report_id,
const std::vector<uint8_t>& report_bytes);
// Update |pad_.connected| based on the current device state.
void UpdatePadConnected();
// Register to receive the next input report from the underlying HID device.
void ReadInputReport();
// Callback to be called when an input report is received, or when the read
// has failed.
void OnReadInputReport(
bool success,
uint8_t report_id,
const base::Optional<std::vector<uint8_t>>& report_bytes);
// Request to send an output report to the underlying HID device. If
// |expect_reply| is true, a timeout is armed that will retry the current
// initialization step if no reply is received within the timeout window.
void WriteOutputReport(uint8_t report_id,
const std::vector<uint8_t>& report_bytes,
bool expect_reply);
// Callback to be called when an output report request is complete or has
// failed.
void OnWriteOutputReport(bool success);
// Output reports sent to the device.
void SubCommand(uint8_t sub_command, const std::vector<uint8_t>& bytes);
void RequestMacAddress();
void RequestHandshake();
void RequestBaudRate();
void RequestSubCommand33();
void RequestVibration(double left_frequency,
double left_magnitude,
double right_frequency,
double right_magnitude);
void RequestEnableUsbTimeout(bool enable);
void RequestEnableVibration(bool enable);
void RequestEnableImu(bool enable);
void RequestSetPlayerLights(uint8_t light_pattern);
void RequestSetHomeLight(uint8_t minicycle_count,
uint8_t minicycle_duration,
uint8_t start_intensity,
uint8_t cycle_count,
const std::vector<uint8_t>& minicycle_data);
void RequestSetHomeLightIntensity(double intensity);
void RequestSetImuSensitivity(uint8_t gyro_sensitivity,
uint8_t accelerometer_sensitivity,
uint8_t gyro_performance_rate,
uint8_t accelerometer_filter_bandwidth);
void RequestSetInputReportMode(uint8_t mode);
void ReadSpi(uint16_t address, size_t length);
void RequestImuCalibration();
void RequestHorizontalOffsets();
void RequestAnalogCalibration();
void RequestAnalogParameters();
// Schedule a callback to retry a step during the initialization sequence.
void ArmTimeout();
// Cancel the current timeout, if there is one.
void CancelTimeout();
// Timeout expiration callback.
void OnTimeout();
// An ID value to identify this device among other devices enumerated by the
// data fetcher.
const int source_id_;
// The current step of the initialization sequence, or kInitialized if the
// device is already initialized. Set to kUninitialized if the device is
// in a temporary de-initialized state but is still connected.
InitializationState state_ = kUninitialized;
// The number of times the current initialization step has been retried.
size_t retry_count_ = 0;
// A composite device contains up to two Joy-Cons as sub-devices.
bool is_composite_ = false;
// Left and right sub-devices for a composite device.
std::unique_ptr<NintendoController> composite_left_;
std::unique_ptr<NintendoController> composite_right_;
// Global output report counter. Increments by 1 for each report sent.
uint8_t output_report_counter_ = 0;
// The Bluetooth MAC address of the device.
uint64_t mac_address_ = 0;
// The bus type for the underlying HID device.
GamepadBusType bus_type_ = GAMEPAD_BUS_UNKNOWN;
// The maximum size of an output report for the underlying HID device.
size_t output_report_size_bytes_ = 0;
// 8-bit value representing the device type, as reported by the device when
// connected over USB.
uint8_t usb_device_type_ = 0;
// Calibration data read from the device.
SwitchCalibrationData cal_data_;
// The last collection of IMU data. The device reports three frames of data in
// each update.
SwitchImuData imu_data_[3];
// The most recent gamepad state.
Gamepad pad_;
// A callback to be called once the initialization timeout has expired.
base::CancelableOnceClosure timeout_callback_;
// Information about the underlying HID device.
mojom::HidDeviceInfoPtr device_info_;
GamepadId gamepad_id_;
// HID service manager.
mojom::HidManager* const hid_manager_;
// The open connection to the underlying HID device.
mojom::HidConnectionPtr connection_;
// A closure, provided in the call to Open, to be called once the device
// becomes ready.
base::OnceClosure device_ready_closure_;
base::WeakPtrFactory<NintendoController> weak_factory_;
};
} // namespace device
#endif // DEVICE_GAMEPAD_NINTENDO_CONTROLLER_H_