diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c310b33..4a4e235 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,7 +32,7 @@ jobs: enable-deltas-report: true fqbn: esp32:esp32:esp32 sketch-paths: | - # - examples/GamepadExamples/CharacteristicsConfiguration/CharacteristicsConfiguration.ino # disable because esp_base_mac_addr_set is disabled in BleCompositeHID.cpp + - examples/GamepadExamples/CharacteristicsConfiguration/CharacteristicsConfiguration.ino # disable because esp_base_mac_addr_set is disabled in BleCompositeHID.cpp - examples/GamepadExamples/DrivingControllerTest/DrivingControllerTest.ino - examples/GamepadExamples/FlightControllerTest/FlightControllerTest.ino - examples/GamepadExamples/Gamepad/Gamepad.ino @@ -48,6 +48,7 @@ jobs: - examples/GamepadExamples/SpecialButtons/SpecialButtons.ino - examples/GamepadExamples/TestAll/TestAll.ino - examples/XInputExamples/XboxXInputController/XboxXInputController.ino + - examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino - examples/KeyboardExamples/KeyboardLEDs/KeyboardLEDs.ino - examples/KeyboardExamples/MediaKeys/MediaKeys.ino - examples/KeyboardExamples/PressKey/PressKey.ino diff --git a/BLEHostConfiguration.h b/BLEHostConfiguration.h index 103f2a5..857defe 100644 --- a/BLEHostConfiguration.h +++ b/BLEHostConfiguration.h @@ -1,7 +1,6 @@ #ifndef ESP32_BLE_HOST_CONFIG_H #define ESP32_BLE_HOST_CONFIG_H -#include #include // VID Source defines. These determine from which vendor pool the VID should be sourced from diff --git a/BaseCompositeDevice.h b/BaseCompositeDevice.h index d1ab49c..7e0552e 100644 --- a/BaseCompositeDevice.h +++ b/BaseCompositeDevice.h @@ -1,7 +1,6 @@ #ifndef COMPOSITE_CONFIG_H #define COMPOSITE_CONFIG_H -#include //#include #include #include diff --git a/DualSenseGamepadDevice.cpp b/DualSenseGamepadDevice.cpp new file mode 100644 index 0000000..69fb96b --- /dev/null +++ b/DualSenseGamepadDevice.cpp @@ -0,0 +1,594 @@ +#include "DualsenseGamepadDevice.h" +#include "BleCompositeHID.h" +#include "DualsenseDescriptors.h" +#include +#include +#include +#include "esp_mac.h" +#if defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "DualsenseGamepadDevice" +#else +#include "esp_bt_device.h" +#include "esp_bt_main.h" +#include "esp_log.h" +#include "esp_log_level.h" + +static const char* LOG_TAG = "DualsenseGamepadDevice"; +#endif + +DualsenseGamepadCallbacks::DualsenseGamepadCallbacks(DualsenseGamepadDevice* device) + : _device(device) +{ +} + +void DualsenseGamepadCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) +{ + + std::string raw = pCharacteristic->getValue(); + + DualsenseGamepadOutputReportData OutputData; + /* + for (size_t i = 0; i < raw.length(); i++) { + Serial.printf(" %d : %02X ", i, (uint8_t)raw[i]); + if (i % 16 == 0) { + Serial.println(); + } + } + Serial.println(); + */ + if (!OutputData.load((const uint8_t*)raw.data(), raw.length())) { + ESP_LOGD(LOG_TAG, "Invalid DS output report size: %d", (int)raw.length()); + return; + } + + _device->onVibrate.fire(OutputData); +} + +void DualsenseGamepadCallbacks::onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) +{ + ESP_LOGD(LOG_TAG, "DualsenseGamepadCallbacks::onRead"); +} + +void DualsenseGamepadCallbacks::onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) +{ + ESP_LOGD(LOG_TAG, "DualsenseGamepadCallbacks::onSubscribe, code %d", subValue); +} + +void DualsenseGamepadCallbacks::onStatus(NimBLECharacteristic* pCharacteristic, int code) +{ + ESP_LOGD(LOG_TAG, "DualsenseGamepadCallbacks::onStatus, code: %d", code); +} + +DualsenseGamepadDevice::DualsenseGamepadDevice() + : _config(new DualsenseEdgeControllerDeviceConfiguration()) + , _extra_input(nullptr) + , _callbacks(nullptr) + , _firmwareInfo(nullptr) + , _calibration(nullptr) + , _pairingInfo(nullptr) +{ +} + +// DualsenseGamepadDevice methods +DualsenseGamepadDevice::DualsenseGamepadDevice(DualsenseGamepadDeviceConfiguration* config) + : _config(config) + , _extra_input(nullptr) + , _callbacks(nullptr) + , _firmwareInfo(nullptr) + , _calibration(nullptr) + , _pairingInfo(nullptr) +{ +} + +DualsenseGamepadDevice::~DualsenseGamepadDevice() +{ + if (getOutput() && _callbacks) { + getOutput()->setCallbacks(nullptr); + delete _callbacks; + _callbacks = nullptr; + } + + if (_extra_input) { + delete _extra_input; + _extra_input = nullptr; + } + if (_firmwareInfo) { + delete _firmwareInfo; + _firmwareInfo = nullptr; + } + if (_calibration) { + delete _calibration; + _calibration = nullptr; + } + if (_pairingInfo) { + delete _pairingInfo; + _pairingInfo = nullptr; + } + + if (_config) { + delete _config; + _config = nullptr; + } +} + +void DualsenseGamepadDevice::init(NimBLEHIDDevice* hid) +{ + /// Create input characteristic to send events to the computer + auto input = hid->getInputReport(DUALSENSE_EDGE_INPUT_REPORT_ID); + + // Create output characteristic to handle events coming from the computer + auto output = hid->getOutputReport(DUALSENSE_EDGE_OUTPUT_REPORT_ID); + _callbacks = new DualsenseGamepadCallbacks(this); + output->setCallbacks(_callbacks); + // pending callbacks for pairing and stuff + _calibration = hid->getFeatureReport(DUALSENSE_CALIBRATION_REPORT_ID); + _calibration->setCallbacks(_callbacks); + _firmwareInfo = hid->getFeatureReport(DUALSENSE_FIRMWARE_INFO_REPORT_ID); + _firmwareInfo->setCallbacks(_callbacks); + _pairingInfo = hid->getFeatureReport(DUALSENSE_PAIRING_INFO_REPORT_ID); + _pairingInfo->setCallbacks(_callbacks); + // memcpy(_pairingReport.mac_address,hid->esp_bt_dev_get_address(); + setCharacteristics(input, output); + m_pCrcTable = new uint32_t[256]; + generate_crc_table(m_pCrcTable); +} + +const BaseCompositeDeviceConfiguration* DualsenseGamepadDevice::getDeviceConfig() const +{ + // Return the device configuration + return _config; +} + +void DualsenseGamepadDevice::resetInputs() +{ + std::lock_guard lock(_mutex); + memset(&_inputReport, 0, sizeof(DualsenseGamepadInputReportData)); + + _inputReport.x = DUALSENSE_AXIS_CENTER_OFFSET; + _inputReport.y = DUALSENSE_AXIS_CENTER_OFFSET; + _inputReport.z = DUALSENSE_AXIS_CENTER_OFFSET; + _inputReport.rz = DUALSENSE_AXIS_CENTER_OFFSET; + _inputReport.hat = 0x00; + _inputReport.buttons = 0x00; + _inputReport.touchpoint_l_contact = 0x80; + _inputReport.touchpoint_r_contact = 0x80; + _inputReport.battery = 0xFF; +} + +void DualsenseGamepadDevice::press(uint32_t button) +{ + // Avoid double presses + if (!isPressed(button)) { + { + std::lock_guard lock(_mutex); + _inputReport.buttons |= button; + ESP_LOGD(LOG_TAG, "DualsenseGamepadDevice::press, button: %d", button); + } + + if (_config->getAutoReport()) { + sendGamepadReport(); + } + } +} +void DualsenseGamepadDevice::setLeftTouchpad(uint16_t x, uint16_t y) +{ + + if (_inputReport.touchpoint_l_contact != 0x00) { + std::lock_guard lock(_mutex); + _inputReport.touchpoint_l_contact = 0x00; + } + _inputReport.touchpoint_l_x = x & 0xFFF; + _inputReport.touchpoint_l_y = y & 0xFFF; + if (_config->getAutoReport()) { + sendGamepadReport(); + } +} +void DualsenseGamepadDevice::setRightTouchpad(uint16_t x, uint16_t y) +{ + + if (_inputReport.touchpoint_r_contact != 0x00) { + std::lock_guard lock(_mutex); + _inputReport.touchpoint_r_contact = 0x00; + } + _inputReport.touchpoint_r_x = x & 0xFFF; + _inputReport.touchpoint_r_y = y & 0xFFF; + if (_config->getAutoReport()) { + sendGamepadReport(); + } +} +void DualsenseGamepadDevice::releaseLeftTouchpad() +{ + _inputReport.touchpoint_l_contact = 0x80; + if (_config->getAutoReport()) { + sendGamepadReport(); + } +} +void DualsenseGamepadDevice::releaseRightTouchpad() +{ + _inputReport.touchpoint_r_contact = 0x80; + if (_config->getAutoReport()) { + sendGamepadReport(); + } +} + +void DualsenseGamepadDevice::release(uint32_t button) +{ + // Avoid double presses + if (isPressed(button)) { + { + std::lock_guard lock(_mutex); + _inputReport.buttons ^= button; + ESP_LOGD(LOG_TAG, "DualsenseGamepadDevice::release, button: %d", button); + } + + if (_config->getAutoReport()) { + sendGamepadReport(); + } + } +} + +bool DualsenseGamepadDevice::isPressed(uint32_t button) +{ + std::lock_guard lock(_mutex); + return (bool)((_inputReport.buttons & button) == button); +} + +void DualsenseGamepadDevice::setLeftThumb(int8_t x, int8_t y) +{ + x = constrain(x, DUALSENSE_STICK_MIN, DUALSENSE_STICK_MAX); + y = constrain(y, DUALSENSE_STICK_MIN, DUALSENSE_STICK_MAX); + + if (_inputReport.x != x || _inputReport.y != y) { + { + std::lock_guard lock(_mutex); + _inputReport.x = (uint8_t)(x + DUALSENSE_AXIS_CENTER_OFFSET); + _inputReport.y = (uint8_t)(y + DUALSENSE_AXIS_CENTER_OFFSET); + } + + if (_config->getAutoReport()) { + sendGamepadReport(); + } + } +} + +void DualsenseGamepadDevice::setRightThumb(int8_t z, int8_t rZ) +{ + z = constrain(z, DUALSENSE_STICK_MIN, DUALSENSE_STICK_MAX); + rZ = constrain(rZ, DUALSENSE_STICK_MIN, DUALSENSE_STICK_MAX); + + if (_inputReport.z != z || _inputReport.rz != rZ) { + { + std::lock_guard lock(_mutex); + _inputReport.z = (uint8_t)(z + DUALSENSE_AXIS_CENTER_OFFSET); + _inputReport.rz = (uint8_t)(rZ + DUALSENSE_AXIS_CENTER_OFFSET); + } + + if (_config->getAutoReport()) { + sendGamepadReport(); + } + } +} + +void DualsenseGamepadDevice::setLeftTrigger(uint8_t value) +{ + value = constrain(value, DUALSENSE_TRIGGER_MIN, DUALSENSE_TRIGGER_MAX); + + if (_inputReport.brake != value) { + { + std::lock_guard lock(_mutex); + _inputReport.brake = value; + } + + if (_config->getAutoReport()) { + sendGamepadReport(); + } + } +} + +void DualsenseGamepadDevice::setRightTrigger(uint8_t value) +{ + value = constrain(value, DUALSENSE_TRIGGER_MIN, DUALSENSE_TRIGGER_MAX); + + if (_inputReport.accelerator != value) { + { + std::lock_guard lock(_mutex); + _inputReport.accelerator = value; + } + + if (_config->getAutoReport()) { + sendGamepadReport(); + } + } +} + +void DualsenseGamepadDevice::setGyro(int16_t pitch, int16_t yaw, int16_t roll) +{ + pitch = constrain(pitch, -DUALSENSE_GYRO_RANGE, DUALSENSE_GYRO_RANGE); + yaw = constrain(yaw, -DUALSENSE_GYRO_RANGE, DUALSENSE_GYRO_RANGE); + roll = constrain(roll, -DUALSENSE_GYRO_RANGE, DUALSENSE_GYRO_RANGE); + + if (_inputReport.gyro_x != pitch || _inputReport.gyro_y != yaw || _inputReport.gyro_z != roll) { + { + std::lock_guard lock(_mutex); + _inputReport.gyro_x = (uint16_t)(pitch + DUALSENSE_AXIS_CENTER_OFFSET); + _inputReport.gyro_y = (uint16_t)(yaw + DUALSENSE_AXIS_CENTER_OFFSET); + _inputReport.gyro_z = (uint16_t)(roll + DUALSENSE_AXIS_CENTER_OFFSET); + } + + if (_config->getAutoReport()) { + sendGamepadReport(); + } + } +} +void DualsenseGamepadDevice::setAccel(int16_t x, int16_t y, int16_t z) +{ + x = constrain(x, -DUALSENSE_ACC_RANGE, DUALSENSE_ACC_RANGE); + y = constrain(y, -DUALSENSE_ACC_RANGE, DUALSENSE_ACC_RANGE); + z = constrain(z, -DUALSENSE_ACC_RANGE, DUALSENSE_ACC_RANGE); + + if (_inputReport.accel_x != x || _inputReport.accel_y != y || _inputReport.accel_z != z) { + { + std::lock_guard lock(_mutex); + _inputReport.accel_x = (uint16_t)(x + DUALSENSE_AXIS_CENTER_OFFSET); + _inputReport.accel_y = (uint16_t)(y + DUALSENSE_AXIS_CENTER_OFFSET); + _inputReport.accel_z = (uint16_t)(z + DUALSENSE_AXIS_CENTER_OFFSET); + } + + if (_config->getAutoReport()) { + sendGamepadReport(); + } + } +} + +void DualsenseGamepadDevice::setTriggers(uint8_t left, uint8_t right) +{ + left = constrain(left, DUALSENSE_TRIGGER_MIN, DUALSENSE_TRIGGER_MAX); + right = constrain(right, DUALSENSE_TRIGGER_MIN, DUALSENSE_TRIGGER_MAX); + + if (_inputReport.brake != left || _inputReport.accelerator != right) { + { + std::lock_guard lock(_mutex); + _inputReport.brake = left; + _inputReport.accelerator = right; + } + if (_config->getAutoReport()) { + sendGamepadReport(); + } + } +} + +void DualsenseGamepadDevice::pressDPadDirection(uint8_t direction) +{ + + // Avoid double presses + if (!isDPadPressed(direction)) { + ESP_LOGD(LOG_TAG, "Pressing dpad direction %s", dPadDirectionName(direction).c_str()); + { + std::lock_guard lock(_mutex); + _inputReport.hat = direction; + } + + if (_config->getAutoReport()) { + sendGamepadReport(); + } + } +} + +void DualsenseGamepadDevice::pressDPadDirectionFlag(DualsenseDpadFlags direction) +{ + // Filter opposite button presses + if ((direction & (DualsenseDpadFlags::NORTH | DualsenseDpadFlags::SOUTH)) == (DualsenseDpadFlags::NORTH | DualsenseDpadFlags::SOUTH)) { + ESP_LOGD(LOG_TAG, "Filtering opposite button presses - up down"); + direction = (DualsenseDpadFlags)(direction ^ (uint8_t)(DualsenseDpadFlags::NORTH | DualsenseDpadFlags::SOUTH)); + } + if ((direction & (DualsenseDpadFlags::EAST | DualsenseDpadFlags::WEST)) == (DualsenseDpadFlags::EAST | DualsenseDpadFlags::WEST)) { + ESP_LOGD(LOG_TAG, "Filtering opposite button presses - left right"); + direction = (DualsenseDpadFlags)(direction ^ (uint8_t)(DualsenseDpadFlags::EAST | DualsenseDpadFlags::WEST)); + } + + pressDPadDirection(dPadDirectionToValue(direction)); +} + +void DualsenseGamepadDevice::releaseDPad() +{ + pressDPadDirection(DUALSENSE_BUTTON_DPAD_NONE); +} + +bool DualsenseGamepadDevice::isDPadPressed(uint8_t direction) +{ + std::lock_guard lock(_mutex); + return _inputReport.hat == direction; +} + +bool DualsenseGamepadDevice::isDPadPressedFlag(DualsenseDpadFlags direction) +{ + std::lock_guard lock(_mutex); + + if (direction == DualsenseDpadFlags::NORTH) { + return _inputReport.hat == DUALSENSE_BUTTON_DPAD_NORTH; + } else if (direction == (DualsenseDpadFlags::NORTH | DualsenseDpadFlags::EAST)) { + return _inputReport.hat == DUALSENSE_BUTTON_DPAD_NORTHEAST; + } else if (direction == DualsenseDpadFlags::EAST) { + return _inputReport.hat == DUALSENSE_BUTTON_DPAD_EAST; + } else if (direction == (DualsenseDpadFlags::SOUTH | DualsenseDpadFlags::EAST)) { + return _inputReport.hat == DUALSENSE_BUTTON_DPAD_SOUTHEAST; + } else if (direction == DualsenseDpadFlags::SOUTH) { + return _inputReport.hat == DUALSENSE_BUTTON_DPAD_SOUTH; + } else if (direction == (DualsenseDpadFlags::SOUTH | DualsenseDpadFlags::WEST)) { + return _inputReport.hat == DUALSENSE_BUTTON_DPAD_SOUTHWEST; + } else if (direction == DualsenseDpadFlags::WEST) { + return _inputReport.hat == DUALSENSE_BUTTON_DPAD_WEST; + } else if (direction == (DualsenseDpadFlags::NORTH | DualsenseDpadFlags::WEST)) { + return _inputReport.hat == DUALSENSE_BUTTON_DPAD_NORTHWEST; + } + return false; +} + +void DualsenseGamepadDevice::seq() +{ + if (_inputReport.seq < 254) { + _inputReport.seq += 1; + } else { + _inputReport.seq = 0; + } + + sendGamepadReport(); +} +void DualsenseGamepadDevice::timestamp() +{ + uint32_t cycles = ESP.getCycleCount() / 1500; + + _inputReport.timestamp = cycles; +} + +void DualsenseGamepadDevice::sendGamepadReport(bool defer) +{ + if (defer || _config->getAutoDefer()) { + queueDeferredReport(std::bind(&DualsenseGamepadDevice::sendGamepadReportImpl, this)); + } else { + sendGamepadReportImpl(); + } +} +void DualsenseGamepadDevice::sendFirmInfoReport(bool defer) +{ + if (defer || _config->getAutoDefer()) { + queueDeferredReport(std::bind(&DualsenseGamepadDevice::sendFirmInfoReportImpl, this)); + } else { + sendFirmInfoReportImpl(); + } +} +void DualsenseGamepadDevice::sendCalibrationReport(bool defer) +{ + if (defer || _config->getAutoDefer()) { + queueDeferredReport(std::bind(&DualsenseGamepadDevice::sendCalibrationReportImpl, this)); + } else { + sendCalibrationReportImpl(); + } +} +void DualsenseGamepadDevice::sendPairingInfoReport(bool defer) +{ + if (defer || _config->getAutoDefer()) { + queueDeferredReport(std::bind(&DualsenseGamepadDevice::sendPairingInfoReportImpl, this)); + } else { + sendPairingInfoReportImpl(); + } +} +void DualsenseGamepadDevice::sendGamepadReportImpl() +{ + auto input = getInput(); + auto parentDevice = this->getParent(); + + if (!input || !parentDevice) + return; + + if (!parentDevice->isConnected()) + return; + + { + std::lock_guard lock(_mutex); + size_t packedSize = sizeof(_inputReport); + ESP_LOGD(LOG_TAG, "Sending gamepad report, size: %d", packedSize); + uint8_t bthdr[] = { PS_INPUT_CRC32_SEED, DUALSENSE_EDGE_INPUT_REPORT_ID }; + uint32_t crc = 0; + crc = this->crc32_le(0xFFFFFFFF, (uint8_t*)&bthdr, sizeof(bthdr)); + crc = ~this->crc32_le(crc, (uint8_t*)&_inputReport, sizeof(_inputReport) - 4); + _inputReport.crc32 = crc; + ESP_LOGD(LOG_TAG, "got input CRC %lx", crc); + input->setValue((uint8_t*)&_inputReport, sizeof(_inputReport)); + } + input->notify(); +} +void DualsenseGamepadDevice::sendFirmInfoReportImpl() +{ + auto firminfo = _firmwareInfo; + auto parentDevice = this->getParent(); + + if (!firminfo || !parentDevice) + return; + + if (!parentDevice->isConnected()) + return; + + { + std::lock_guard lock(_mutex); + size_t packedSize = DUALSENSE_FIRMWARE_INFO_REPORT_SIZE; + ESP_LOGD(LOG_TAG, "Sending firmware info report, size: %d", packedSize); + firminfo->setValue((uint8_t*)&DualsenseEdge_FirmwareInfo, packedSize); + } + firminfo->indicate(); +} +void DualsenseGamepadDevice::sendCalibrationReportImpl() +{ + auto calibration = _calibration; + auto parentDevice = this->getParent(); + + if (!calibration || !parentDevice) + return; + + if (!parentDevice->isConnected()) + return; + + { + std::lock_guard lock(_mutex); + size_t packedSize = DUALSENSE_CALIBRATION_REPORT_SIZE; + ESP_LOGD(LOG_TAG, "Sending calibration report, size: %d", packedSize); + calibration->setValue((uint8_t*)&DualsenseEdge_StockCalibration, packedSize); + } + calibration->indicate(); +} +void DualsenseGamepadDevice::sendPairingInfoReportImpl() +{ + ESP_LOGD(LOG_TAG, "Sending Pairing report"); + auto pairinginfo = _pairingInfo; + auto parentDevice = this->getParent(); + uint8_t mac[6]; + esp_read_mac(mac, ESP_MAC_BT); + memcpy(_pairingReport.mac_address, mac, 6); + memcpy(_pairingReport.common, (uint8_t*)&DualsenseEdge_PairInfo_common, 9); + if (!pairinginfo || !parentDevice) + return; + + if (!parentDevice->isConnected()) + return; + + { + std::lock_guard lock(_mutex); + uint8_t bthdr[] = { PS_FEATURE_CRC32_SEED, DUALSENSE_PAIRING_INFO_REPORT_ID }; + uint32_t crc = 0; + crc = this->crc32_le(0xFFFFFFFF, (uint8_t*)&bthdr, sizeof(bthdr)); + crc = ~this->crc32_le(crc, (uint8_t*)&_pairingReport, sizeof(_pairingReport) - 4); + _pairingReport.crc32 = crc; + ESP_LOGD(LOG_TAG, "got pairing CRC %lx", crc); + size_t packedSize = DUALSENSE_PAIRING_INFO_REPORT_SIZE; + ESP_LOGD(LOG_TAG, "Sending Pair report, size: %d", packedSize); + pairinginfo->setValue((uint8_t*)&_pairingReport, packedSize); + } + pairinginfo->indicate(); +} +// taken from https://github.com/StryderUK/BluetoothHID/blob/main/examples/DualShock4 +uint32_t DualsenseGamepadDevice::crc32_le(unsigned int crc, unsigned char const* buf, unsigned int len) +{ + uint32_t i; + for (i = 0; i < len; i++) { + crc = m_pCrcTable[(crc ^ buf[i]) & 0xff] ^ (crc >> 8); + } + return crc; +} +void DualsenseGamepadDevice::generate_crc_table(uint32_t* crcTable) +{ + const uint32_t POLYNOMIAL = 0xEDB88320; // 0x04C11DB7 reversed + uint32_t remainder; + uint8_t b = 0; + do { + // Start with the data byte + remainder = b; + for (unsigned long bit = 8; bit > 0; --bit) { + if (remainder & 1) + remainder = (remainder >> 1) ^ POLYNOMIAL; + else + remainder = (remainder >> 1); + } + crcTable[(size_t)b] = remainder; + } while (0 != ++b); +} diff --git a/DualsenseDescriptors.h b/DualsenseDescriptors.h new file mode 100644 index 0000000..0f45328 --- /dev/null +++ b/DualsenseDescriptors.h @@ -0,0 +1,363 @@ + +#ifndef DUALSENSE_DESCRIPTORS_H +#define DUALSENSE_DESCRIPTORS_H + +#include +#include + +#define DUALSENSE_VENDOR_ID 0x054C + +// Product: Dualsense Edge +#define DUALSENSE_PRODUCT_ID 0x0CE6 + +#define DUALSENSE_EDGE_PRODUCT_ID 0x0DF2 +#define DUALSENSE_EDGE_BCD_DEVICE_ID 0x0408 +#define DUALSENSE_EDGE_SERIAL "0" +#define DUALSENSE_EDGE_INPUT_REPORT_ID 0x31 +#define DUALSENSE_EDGE_OUTPUT_REPORT_ID 0x31 +#define DUALSENSE_EDGE_INPUT_REPORT_BT_SIZE 78 + +#define DS_OUTPUT_REPORT_BT_SIZE 78 +#define PS_INPUT_CRC32_SEED 0xA1 +#define PS_OUTPUT_CRC32_SEED 0xA2 +#define PS_FEATURE_CRC32_SEED 0xA3 +#define DUALSENSE_CALIBRATION_REPORT_ID 0x05 +#define DUALSENSE_CALIBRATION_REPORT_SIZE 41 +#define DUALSENSE_PAIRING_INFO_REPORT_ID 0x09 +#define DUALSENSE_PAIRING_INFO_REPORT_SIZE 20 +#define DUALSENSE_FIRMWARE_INFO_REPORT_ID 0x20 +#define DUALSENSE_FIRMWARE_INFO_REPORT_SIZE 64 + +static const uint8_t DualsenseEdge_PairInfo_common[] { + 0x08, + 0x25, + 0x00, + 0x1E, + 0x00, + 0xEE, + 0x74, + 0xD0, + 0xBC +}; + +static const uint8_t DualsenseEdge_FirmwareInfo[] { + 0x4A, + 0x75, + 0x6E, + 0x20, + 0x31, + 0x39, + 0x20, + 0x32, + 0x30, + 0x32, + 0x33, + 0x31, + 0x34, + 0x3A, + 0x34, + 0x37, + 0x3A, + 0x33, + 0x34, + 0x03, + 0x00, + 0x44, + 0x00, + 0x08, + 0x02, + 0x00, + 0x01, + 0x36, + 0x00, + 0x00, + 0x01, + 0xC1, + 0xC8, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x54, + 0x01, + 0x00, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x0B, + 0x00, + 0x01, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x82, + 0x79, + 0xD9, + 0x57 +}; +static const uint8_t DualsenseEdge_StockCalibration[] { + 0x00, // Gyro Pitch Bias + 0x00, + 0x00, // Gyro Yaw Bias + 0x00, + 0x00, // Gyro Roll Bias + 0x00, + 0x10, // Gyro Pitch Plus + 0x27, + 0xF0, // Gyro Pitch Minus + 0xD8, + 0x10, // Gyro Yaw Plus + 0x27, + 0xF0, // Gyro Yaw Minus + 0xD8, + 0x10, // Gyro Roll Plus + 0x27, + 0xF0, // Gyro Roll Minus + 0xD8, + 0xF4, // Gyro Speed Plus + 0x01, + 0xF4, // Gyro Speed Minus + 0x01, + 0x10, // Accel X Plus + 0x27, + 0xF0, // Accel X Minus + 0xD8, + 0x10, // Accel Y Plus + 0x27, + 0xF0, // Accel Y Minus + 0xD8, + 0x10, // Accel Z Plus + 0x27, + 0xF0, // Accel Z Minus + 0xD8, + 0x0B, + 0x00, + 0x8D, + 0x93, + 0xCA, + 0x2B +}; +static const uint8_t DualsenseEdge_HIDDescriptor[] { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x05, // Usage (Game Pad) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x01, // Report ID (1) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x09, 0x32, // Usage (Z) + 0x09, 0x35, // Usage (Rz) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x04, // Report Count (4) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x09, 0x39, // Usage (Hat switch) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x07, // Logical Maximum (7) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0x3B, 0x01, // Physical Maximum (315) + 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter) + 0x75, 0x04, // Report Size (4) + 0x95, 0x01, // Report Count (1) + 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) + 0x65, 0x00, // Unit (None) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (0x01) + 0x29, 0x0E, // Usage Maximum (0x0E) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x0E, // Report Count (14) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x06, // Report Size (6) + 0x95, 0x01, // Report Count (1) + 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x33, // Usage (Rx) + 0x09, 0x34, // Usage (Ry) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x02, // Report Count (2) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x4D, // Report Count (77) + 0x85, 0x31, // Report ID (49) + 0x09, 0x31, // Usage (0x31) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x09, 0x3B, // Usage (0x3B) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x85, 0x32, // Report ID (50) + 0x09, 0x32, // Usage (0x32) + 0x95, 0x8D, // Report Count (-115) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x33, // Report ID (51) + 0x09, 0x33, // Usage (0x33) + 0x95, 0xCD, // Report Count (-51) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x34, // Report ID (52) + 0x09, 0x34, // Usage (0x34) + 0x96, 0x0D, 0x01, // Report Count (269) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x35, // Report ID (53) + 0x09, 0x35, // Usage (0x35) + 0x96, 0x4D, 0x01, // Report Count (333) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x36, // Report ID (54) + 0x09, 0x36, // Usage (0x36) + 0x96, 0x8D, 0x01, // Report Count (397) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x37, // Report ID (55) + 0x09, 0x37, // Usage (0x37) + 0x96, 0xCD, 0x01, // Report Count (461) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x38, // Report ID (56) + 0x09, 0x38, // Usage (0x38) + 0x96, 0x0D, 0x02, // Report Count (525) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x39, // Report ID (57) + 0x09, 0x39, // Usage (0x39) + 0x96, 0x22, 0x02, // Report Count (546) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x06, 0x80, 0xFF, // Usage Page (Vendor Defined 0xFF80) + 0x85, 0x05, // Report ID (5) + 0x09, 0x33, // Usage (0x33) + 0x95, 0x28, // Report Count (40) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x08, // Report ID (8) + 0x09, 0x34, // Usage (0x34) + 0x95, 0x2F, // Report Count (47) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x09, // Report ID (9) + 0x09, 0x24, // Usage (0x24) + 0x95, 0x13, // Report Count (19) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x20, // Report ID (32) + 0x09, 0x26, // Usage (0x26) + 0x95, 0x3F, // Report Count (63) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x22, // Report ID (34) + 0x09, 0x40, // Usage (0x40) + 0x95, 0x3F, // Report Count (63) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x80, // Report ID (-128) + 0x09, 0x28, // Usage (0x28) + 0x95, 0x3F, // Report Count (63) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x81, // Report ID (-127) + 0x09, 0x29, // Usage (0x29) + 0x95, 0x3F, // Report Count (63) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x82, // Report ID (-126) + 0x09, 0x2A, // Usage (0x2A) + 0x95, 0x09, // Report Count (9) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x83, // Report ID (-125) + 0x09, 0x2B, // Usage (0x2B) + 0x95, 0x3F, // Report Count (63) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xF1, // Report ID (-15) + 0x09, 0x31, // Usage (0x31) + 0x95, 0x3F, // Report Count (63) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xF2, // Report ID (-14) + 0x09, 0x32, // Usage (0x32) + 0x95, 0x34, // Report Count (52) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xF0, // Report ID (-16) + 0x09, 0x30, // Usage (0x30) + 0x95, 0x3F, // Report Count (63) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x60, // Report ID (96) + 0x09, 0x41, // Usage (0x41) + 0x95, 0x3F, // Report Count (63) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x61, // Report ID (97) + 0x09, 0x42, // Usage (0x42) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x62, // Report ID (98) + 0x09, 0x43, // Usage (0x43) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x63, // Report ID (99) + 0x09, 0x44, // Usage (0x44) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x64, // Report ID (100) + 0x09, 0x45, // Usage (0x45) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x65, // Report ID (101) + 0x09, 0x46, // Usage (0x46) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x68, // Report ID (104) + 0x09, 0x47, // Usage (0x47) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x70, // Report ID (112) + 0x09, 0x48, // Usage (0x48) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x71, // Report ID (113) + 0x09, 0x49, // Usage (0x49) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x72, // Report ID (114) + 0x09, 0x4A, // Usage (0x4A) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x73, // Report ID (115) + 0x09, 0x4B, // Usage (0x4B) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x74, // Report ID (116) + 0x09, 0x4C, // Usage (0x4C) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x75, // Report ID (117) + 0x09, 0x4D, // Usage (0x4D) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x76, // Report ID (118) + 0x09, 0x4E, // Usage (0x4E) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x77, // Report ID (119) + 0x09, 0x4F, // Usage (0x4F) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x78, // Report ID (120) + 0x09, 0x50, // Usage (0x50) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x79, // Report ID (121) + 0x09, 0x51, // Usage (0x51) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x7A, // Report ID (122) + 0x09, 0x52, // Usage (0x52) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x7B, // Report ID (123) + 0x09, 0x53, // Usage (0x53) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xF4, // Report ID (-12) + 0x09, 0x2C, // Usage (0x2C) + 0x95, 0x3F, // Report Count (63) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xF5, // Report ID (-11) + 0x09, 0x2D, // Usage (0x2D) + 0x95, 0x07, // Report Count (7) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xF6, // Report ID (-10) + 0x09, 0x2E, // Usage (0x2E) + 0x96, 0x22, 0x02, // Report Count (546) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xF7, // Report ID (-9) + 0x09, 0x2F, // Usage (0x2F) + 0x95, 0x07, // Report Count (7) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0xC0 // End Collection + + // 428 bytes +}; +static_assert(sizeof(DualsenseEdge_HIDDescriptor) == 428, "Wrong size"); +#endif diff --git a/DualsenseGamepadConfiguration.h b/DualsenseGamepadConfiguration.h new file mode 100644 index 0000000..3d584b1 --- /dev/null +++ b/DualsenseGamepadConfiguration.h @@ -0,0 +1,25 @@ +#ifndef DUALSENSE_GAMEPAD_CONFIGURATION_H +#define DUALSENSE_GAMEPAD_CONFIGURATION_H + +#include "BaseCompositeDevice.h" +#include "DualsenseDescriptors.h" + +class DualsenseGamepadDeviceConfiguration : public BaseCompositeDeviceConfiguration { +public: + DualsenseGamepadDeviceConfiguration(uint8_t reportId = DUALSENSE_EDGE_INPUT_REPORT_ID); + virtual uint8_t getDeviceReportSize() const override { return 0; } + virtual size_t makeDeviceReport(uint8_t* buffer, size_t bufferSize) const override + { + return -1; + } +}; + +class DualsenseEdgeControllerDeviceConfiguration : public DualsenseGamepadDeviceConfiguration { +public: + virtual const char* getDeviceName() const { return "MusulsenseEdge"; } + virtual BLEHostConfiguration getIdealHostConfiguration() const override; + virtual uint8_t getDeviceReportSize() const override; + virtual size_t makeDeviceReport(uint8_t* buffer, size_t bufferSize) const override; +}; + +#endif // DUALSENSE_GAMEPAD_CONFIGURATION_H diff --git a/DualsenseGamepadConfigurations.cpp b/DualsenseGamepadConfigurations.cpp new file mode 100644 index 0000000..ae2cf53 --- /dev/null +++ b/DualsenseGamepadConfigurations.cpp @@ -0,0 +1,53 @@ +#include "DualsenseGamepadConfiguration.h" +#include "DualsenseGamepadDevice.h" +#include "NimBLEHIDDevice.h" + +// DualsenseGamepadDeviceConfiguration methods +DualsenseGamepadDeviceConfiguration::DualsenseGamepadDeviceConfiguration(uint8_t reportId) + : BaseCompositeDeviceConfiguration(reportId) +{ +} + +BLEHostConfiguration DualsenseEdgeControllerDeviceConfiguration::getIdealHostConfiguration() const +{ + // Fake a Dualsense controller + BLEHostConfiguration config; + + // Explicitly set HID Device type + config.setHidType(HID_GAMEPAD); + + // Vendor: Sony + config.setVidSource(VENDOR_USB_SOURCE); + config.setVid(DUALSENSE_VENDOR_ID); + + // Product: Dualsense Edge Wireless Controller + config.setPid(DUALSENSE_EDGE_PRODUCT_ID); + config.setGuidVersion(DUALSENSE_EDGE_BCD_DEVICE_ID); + config.setSerialNumber(DUALSENSE_EDGE_SERIAL); + config.setQueueSendRate(0); + + return config; +} + +uint8_t DualsenseEdgeControllerDeviceConfiguration::getDeviceReportSize() const +{ + // Return the size of the device report + + return sizeof(DualsenseGamepadInputReportData); // 16 +} + +size_t DualsenseEdgeControllerDeviceConfiguration::makeDeviceReport(uint8_t* buffer, size_t bufferSize) const +{ + size_t hidDescriptorSize = sizeof(DualsenseEdge_HIDDescriptor); + if (hidDescriptorSize < bufferSize) { + memcpy(buffer, DualsenseEdge_HIDDescriptor, hidDescriptorSize); + } else { + return -1; + } + + return hidDescriptorSize; +} + +// ------------------------------------- + +// ------------------------------------- diff --git a/DualsenseGamepadDevice.h b/DualsenseGamepadDevice.h new file mode 100644 index 0000000..0afa589 --- /dev/null +++ b/DualsenseGamepadDevice.h @@ -0,0 +1,311 @@ +#ifndef DUALSENSE_GAMEPAD_DEVICE_H +#define DUALSENSE_GAMEPAD_DEVICE_H + +#include +#include +#include +#include +#include + +#include "BLEHostConfiguration.h" +#include "BaseCompositeDevice.h" +#include "DualsenseDescriptors.h" +#include "DualsenseGamepadConfiguration.h" +#include "GamepadDevice.h" +// Button bitmasks +#define DUALSENSE_BUTTON_Y 0x08 +#define DUALSENSE_BUTTON_B 0x04 +#define DUALSENSE_BUTTON_A 0x02 +#define DUALSENSE_BUTTON_X 0x01 + +#define DUALSENSE_BUTTON_DPAD_NONE 0x08 +#define DUALSENSE_BUTTON_DPAD_NORTH 0x00 +#define DUALSENSE_BUTTON_DPAD_NORTHEAST 0x01 +#define DUALSENSE_BUTTON_DPAD_EAST 0x02 +#define DUALSENSE_BUTTON_DPAD_SOUTHEAST 0x03 +#define DUALSENSE_BUTTON_DPAD_SOUTH 0x04 +#define DUALSENSE_BUTTON_DPAD_SOUTHWEST 0x05 +#define DUALSENSE_BUTTON_DPAD_WEST 0x06 +#define DUALSENSE_BUTTON_DPAD_NORTHWEST 0x07 +#define DUALSENSE_BUTTON_LB 0x10 +#define DUALSENSE_BUTTON_RB 0x20 +#define DUALSENSE_BUTTON_LT 0x40 +#define DUALSENSE_BUTTON_RT 0x80 +#define DUALSENSE_BUTTON_SELECT 0x100 +#define DUALSENSE_BUTTON_START 0x200 +#define DUALSENSE_BUTTON_LS 0x400 +#define DUALSENSE_BUTTON_RS 0x800 + +#define DUALSENSE_BUTTON_MODE 0x1000 +#define DUALSENSE_BUTTON_TOUCHPAD 0x2000 +#define DUALSENSE_BUTTON_SHARE 0x4000 +#define DUALSENSE_BUTTON_MUTE 0x8000 +#define DUALSENSE_BUTTON_L4 0x10000 +#define DUALSENSE_BUTTON_R4 0x20000 +#define DUALSENSE_BUTTON_L5 0x40000 +#define DUALSENSE_BUTTON_R5 0x80000 + +// Dpad values + +// Dpad bitflags +enum DualsenseDpadFlags : uint8_t { + NONE = 0x08, + NORTH = 0x00, + EAST = 0x02, + SOUTH = 0x04, + WEST = 0x08 +}; + +// Trigger range +#define DUALSENSE_TRIGGER_MIN 0 +#define DUALSENSE_TRIGGER_MAX 255 + +// Thumbstick range +#define DUALSENSE_STICK_MIN -127 +#define DUALSENSE_STICK_MAX 127 +#define DUALSENSE_ACC_RES_PER_G 8192 +#define DUALSENSE_ACC_RANGE (4 * 8192) +#define DUALSENSE_GYRO_RES_PER_DEG_S 1024 +#define DUALSENSE_GYRO_RANGE (2048 * 1024) +#define DUALSENSE_AXIS_CENTER_OFFSET 0x80 + +// player leds masks +#define DUALSENSE_PLAYERLED_ON 0x20 +#define DUALSENSE_PLAYERLED_1 0x01 +#define DUALSENSE_PLAYERLED_2 0x02 +#define DUALSENSE_PLAYERLED_3 0x04 +#define DUALSENSE_PLAYERLED_4 0x08 +#define DUALSENSE_PLAYERLED_5 0x10 + +// Forwards +class DualsenseGamepadDevice; + +class DualsenseGamepadCallbacks : public NimBLECharacteristicCallbacks { +public: + DualsenseGamepadCallbacks(DualsenseGamepadDevice* device); + + void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override; + void onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override; + void onStatus(NimBLECharacteristic* pCharacteristic, int code) override; + void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) override; + +private: + DualsenseGamepadDevice* _device; +}; +struct DualsenseGamepadOutputReportData { + uint8_t type; + uint8_t seq_tag = 0; + uint8_t motor_right = 0, motor_left = 0; + uint8_t tag = 0; + + + uint8_t headphone_volume = 0, speaker_volume = 0, mic_volume = 0; + uint8_t audio_control = 0; + uint8_t mute_button_led = 0; + + uint8_t valid_flag0 = 0, valid_flag1 = 0; + uint8_t power_save_control = 0; + uint8_t reserved0[27] = { 0 }; + uint8_t audio_control2 = 0; + + uint8_t reserved1[2] = { 0 }; + uint8_t valid_flag2 = 0; + uint8_t lightbar_setup = 0; + uint8_t player_leds = 0; + uint8_t lightbar_red = 0; + uint8_t lightbar_green = 0; + uint8_t lightbar_blue = 0; + + uint8_t reserved2[24] = { 0 }; + uint32_t crc32 = 0; + + // default constructor OK + DualsenseGamepadOutputReportData() = default; + + // parsing function + bool load(const uint8_t* value, size_t size) + { + if (!value || size < 77) + return false; + type = value[0]; + seq_tag = value[1]; + tag = value[2]; + motor_right = value[3]; + motor_left = value[4]; + + headphone_volume = value[5]; + speaker_volume = value[6]; + mic_volume = value[7]; + audio_control = value[8]; + mute_button_led = value[9]; + valid_flag0 = value[10]; + valid_flag1 = value[11]; + power_save_control = value[12]; + audio_control2 = value[39]; + + valid_flag2 = value[42]; + lightbar_setup = value[43]; + player_leds = value[44]; + lightbar_red = value[45]; + lightbar_green = value[46]; + lightbar_blue = value[47]; + + crc32 = (uint32_t)value[73] + | ((uint32_t)value[74] << 8) + | ((uint32_t)value[75] << 16) + | ((uint32_t)value[76] << 24); + + return true; + } +} __attribute__((packed)); + +static_assert(sizeof(DualsenseGamepadOutputReportData) == 77, "Wrong size"); + +#pragma pack(push, 1) +struct DualsenseGamepadInputReportData { + uint8_t bt = 0x10; // -1 + uint8_t x = 0x80; // 0 Left joystick X + uint8_t y = 0x80; // 1 Left joystick Y + uint8_t z = 0x80; // 2 Right jostick X + uint8_t rz = 0x80; // 3 Right joystick Y + uint8_t brake = 0; // 4 8 bits for brake (left trigger) + uint8_t accelerator = 0; // 5 8 bits for accelerator (right trigger) + uint8_t seq = 0x20; // 6 + uint8_t hat : 4; // 6.5 4bits for hat switch (Dpad) + 0 bit padding (0.5 byte) + uint32_t buttons : 20; // 9 20 * 1bit for buttons + 8 bit padding (4 bytes) + uint8_t extra_buttons; // 10 + uint32_t reserved; // 14 + uint16_t gyro_x = 0; // 16 + uint16_t gyro_y = 0; // 18 + uint16_t gyro_z = 0; // 20 + uint16_t accel_x = 0; // 22 + uint16_t accel_y = 0; // 24 + uint16_t accel_z = 0; // 26 + uint32_t timestamp = 0x7621DD40; // 30 + uint8_t reserved2 = 0; // 31 + uint8_t touchpoint_l_contact = 0; + uint16_t touchpoint_l_x : 12; // 32 - 34 + uint16_t touchpoint_l_y : 12; // 34.5 - 35 + uint8_t touchpoint_r_contact = 0; // 36 + uint16_t touchpoint_r_x : 12; // 32 - 34 + uint16_t touchpoint_r_y : 12; // 34.5 - 35 + uint8_t data_41_53[12]; + uint8_t battery = 0xFF; + uint8_t data_54_74[18]; + uint8_t reserved3 = 0x00; + + uint32_t crc32 = 0; // 4-byte CRC32 checksum +} __attribute__((packed)); +#pragma pack(pop) +struct DualsenseGamepadPairingReportdata { + uint8_t mac_address[6]; + uint8_t common[9]; + uint32_t crc32; +} __attribute__((packed)); + +static uint8_t dPadDirectionToValue(DualsenseDpadFlags direction) +{ + if (direction == DualsenseDpadFlags::NORTH) + return DUALSENSE_BUTTON_DPAD_NORTH; + else if (direction == (DualsenseDpadFlags::EAST | DualsenseDpadFlags::NORTH)) + return DUALSENSE_BUTTON_DPAD_NORTHEAST; + else if (direction == DualsenseDpadFlags::EAST) + return DUALSENSE_BUTTON_DPAD_EAST; + else if (direction == (DualsenseDpadFlags::EAST | DualsenseDpadFlags::SOUTH)) + return DUALSENSE_BUTTON_DPAD_SOUTHEAST; + else if (direction == DualsenseDpadFlags::SOUTH) + return DUALSENSE_BUTTON_DPAD_SOUTH; + else if (direction == (DualsenseDpadFlags::WEST | DualsenseDpadFlags::SOUTH)) + return DUALSENSE_BUTTON_DPAD_SOUTHWEST; + else if (direction == DualsenseDpadFlags::WEST) + return DUALSENSE_BUTTON_DPAD_WEST; + else if (direction == (DualsenseDpadFlags::WEST | DualsenseDpadFlags::NORTH)) + return DUALSENSE_BUTTON_DPAD_NORTHWEST; + + return DUALSENSE_BUTTON_DPAD_NONE; +} + +static String dPadDirectionName(uint8_t direction) +{ + if (direction == DUALSENSE_BUTTON_DPAD_NORTH) + return "NORTH"; + else if (direction == DUALSENSE_BUTTON_DPAD_NORTHEAST) + return "NORTHEAST"; + else if (direction == DUALSENSE_BUTTON_DPAD_EAST) + return "EAST"; + else if (direction == DUALSENSE_BUTTON_DPAD_SOUTHEAST) + return "SOUTHEAST"; + else if (direction == DUALSENSE_BUTTON_DPAD_SOUTH) + return "SOUTH"; + else if (direction == DUALSENSE_BUTTON_DPAD_SOUTHWEST) + return "SOUTHWEST"; + else if (direction == DUALSENSE_BUTTON_DPAD_WEST) + return "WEST"; + else if (direction == DUALSENSE_BUTTON_DPAD_NORTHWEST) + return "NORTHWEST"; + return "NONE"; +} + +class DualsenseGamepadDevice : public BaseCompositeDevice { +public: + DualsenseGamepadDevice(); + DualsenseGamepadDevice(DualsenseGamepadDeviceConfiguration* config); + ~DualsenseGamepadDevice(); + + void init(NimBLEHIDDevice* hid) override; + const BaseCompositeDeviceConfiguration* getDeviceConfig() const override; + + Signal onVibrate; + + // Input Controls + void resetInputs(); + void press(uint32_t button = DUALSENSE_BUTTON_A); + void release(uint32_t button = DUALSENSE_BUTTON_A); + bool isPressed(uint32_t button = DUALSENSE_BUTTON_A); + void setLeftThumb(int8_t x = 0, int8_t y = 0); + void setRightThumb(int8_t z = 0, int8_t rZ = 0); + void setLeftTrigger(uint8_t rX = 0); + void setRightTrigger(uint8_t rY = 0); + void setTriggers(uint8_t rX = 0, uint8_t rY = 0); + void pressDPadDirection(uint8_t direction = 0); + void pressDPadDirectionFlag(DualsenseDpadFlags direction = DualsenseDpadFlags::NONE); + void releaseDPad(); + bool isDPadPressed(uint8_t direction = 0); + bool isDPadPressedFlag(DualsenseDpadFlags direction); + void pressShare(); + void releaseShare(); + void setLeftTouchpad(uint16_t x, uint16_t y); + void setRightTouchpad(uint16_t x, uint16_t y); + void releaseLeftTouchpad(); + void releaseRightTouchpad(); + void setAccel(int16_t x, int16_t y, int16_t z); + void setGyro(int16_t pitch, int16_t yaw, int16_t roll); + void sendGamepadReport(bool defer = false); + void sendFirmInfoReport(bool defer = false); + void sendCalibrationReport(bool defer = false); + void sendPairingInfoReport(bool defer = false); + void timestamp(); + void seq(); + +private: + void sendGamepadReportImpl(); + void sendFirmInfoReportImpl(); + void sendCalibrationReportImpl(); + void sendPairingInfoReportImpl(); + DualsenseGamepadInputReportData _inputReport; + DualsenseGamepadPairingReportdata _pairingReport; + void signCRC32Inplace(const uint8_t* data, uint8_t seed); + NimBLECharacteristic* _extra_input; + DualsenseGamepadCallbacks* _callbacks; + DualsenseGamepadDeviceConfiguration* _config; + NimBLECharacteristic* _calibration; + NimBLECharacteristic* _firmwareInfo; + NimBLECharacteristic* _pairingInfo; + uint32_t crc32_le(unsigned int crc, unsigned char const* buf, unsigned int len); + void generate_crc_table(uint32_t* crcTable); + uint32_t* m_pCrcTable; + // Threading + std::mutex _mutex; +}; + +#endif // DUALSENSE_GAMEPAD_DEVICE_H diff --git a/README.md b/README.md index f0043c4..6c8f33d 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,17 @@ Published under the MIT license. Please see license.txt. - [x] Haptic feedback callbacks for strong and weak motor rumble support - [ ] LED support (pull requests welcome) +## Dualsense Edge gamepad features + - [x] All buttons and joystick axes available, including 4 extra buttons (paddles1-2 and fn1-2) mute and touchpad + - [x] Steam/Emulator/SDL support + - [x] Player LEDs, mute LED and RGB LED support + - [x] Basic rumble support + - [x] Gyroscope/Accelerometer support + - [x] Touchpad support + - [ ] advanced Haptics + - [ ] adaptive trigger + - [ ] audio channels + ## Generic gamepad features (from ESP32-BLE-Gamepad) - [x] Button press (128 buttons) diff --git a/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino b/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino new file mode 100644 index 0000000..f83acc9 --- /dev/null +++ b/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino @@ -0,0 +1,379 @@ +#include + +#include +#include +#define CONFIG_BT_NIMBLE_EXT_ADV 1 + +int ledPin = 8; // LED connected to digital pin 13 +uint8_t playerLEDs = 0x00; +uint8_t ledcolor[3] = {0,0,0}; +uint8_t muteled = 0; +DualsenseGamepadDevice* dualsense; +BleCompositeHID compositeHID("Libresteishon Edge", "YeaSeb", 100); + +void onLEDsetup(DualsenseGamepadOutputReportData data) +{ + if (data.player_leds != playerLEDs) { + playerLEDs=data.player_leds; + if ((data.player_leds & DUALSENSE_PLAYERLED_ON) == DUALSENSE_PLAYERLED_ON) { + if ((data.player_leds & DUALSENSE_PLAYERLED_1) == DUALSENSE_PLAYERLED_1) { + Serial.println("led 1 on"); + } else { + Serial.println("led 1 off"); + } + if ((data.player_leds & DUALSENSE_PLAYERLED_2) == DUALSENSE_PLAYERLED_2) { + Serial.println("led 2 on"); + } else { + Serial.println("led 2 off"); + } + if ((data.player_leds & DUALSENSE_PLAYERLED_3) == DUALSENSE_PLAYERLED_3) { + Serial.println("led 3 on"); + } else { + Serial.println("led 3 off"); + } + if ((data.player_leds & DUALSENSE_PLAYERLED_4) == DUALSENSE_PLAYERLED_4) { + Serial.println("led 4 on"); + } else { + Serial.println("led 4 off"); + } + if ((data.player_leds & DUALSENSE_PLAYERLED_5) == DUALSENSE_PLAYERLED_5) { + Serial.println("led 5 on"); + } else { + Serial.println("led 5 off"); + } + } + } + if (data.lightbar_red!=ledcolor[0] || data.lightbar_green!=ledcolor[1] || data.lightbar_blue!=ledcolor[2]){ + ledcolor[0]=data.lightbar_red; + ledcolor[1]=data.lightbar_green; + ledcolor[2]=data.lightbar_blue; + Serial.println(String("LED color change, R: ") + ledcolor[0] + " G: " + ledcolor[1] + " B: " + ledcolor[2]); + } + if (data.mute_button_led!=muteled) + { + muteled = data.mute_button_led; + Serial.println(String("Mute button change: ")+muteled); + } +} +void OnVibrateEvent(DualsenseGamepadOutputReportData data) +{ + + if (data.motor_right > 0 || data.motor_left > 0) { + digitalWrite(ledPin, LOW); + } else { + digitalWrite(ledPin, HIGH); + } + if (data.lightbar_setup>0 || data.lightbar_red>0 || data.lightbar_green>0 || data.lightbar_blue>0 || data.mute_button_led>0 || data.player_leds>0){ + onLEDsetup(data); + } + Serial.println("Output event. Weak motor: " + String(data.motor_left) + " Strong motor: " + String(data.motor_right) + "LED change:" + data.lightbar_setup); + +} +void setup() +{ + Serial.begin(115200); + pinMode(ledPin, OUTPUT); // sets the digital pin as output + + DualsenseEdgeControllerDeviceConfiguration* config = new DualsenseEdgeControllerDeviceConfiguration(); + config->setAutoReport(true); + config->setAutoDefer(false); + + // The composite HID device pretends to be a valid Dualsense controller via vendor and product IDs (VID/PID). + // Platforms like windows/linux need this + BLEHostConfiguration hostConfig = config->getIdealHostConfiguration(); + Serial.println("Using VID source: " + String(hostConfig.getVidSource(), HEX)); + Serial.println("Using VID: " + String(hostConfig.getVid(), HEX)); + Serial.println("Using PID: " + String(hostConfig.getPid(), HEX)); + Serial.println("Using GUID version: " + String(hostConfig.getGuidVersion(), HEX)); + Serial.println("Using serial number: " + String(hostConfig.getSerialNumber())); + + // Set up dualsense + dualsense = new DualsenseGamepadDevice(config); + // Set up vibration event handler + FunctionSlot vibrationSlot(OnVibrateEvent); + dualsense->onVibrate.attach(vibrationSlot); + + // Add all child devices to the top-level composite HID device to manage them + compositeHID.addDevice(dualsense); + // Start the composite HID device to broadcast HID reports + // Serial.println("Starting composite HID device..."); + compositeHID.begin(hostConfig); +} + +void loop() +{ + if (compositeHID.isConnected()) { + delay(150); + dualsense->sendPairingInfoReport(); + + dualsense->sendFirmInfoReport(); + + dualsense->sendCalibrationReport(); + + delay(280); + dualsense->resetInputs(); + int selection = 32; + const float STEP = 0.05; // angle change per frame (speed) + + while (compositeHID.isConnected()) { + Serial.println("Select a button/axis to be activated for testing 0: Cross 1: Circle 2: Square 3: Triangle \n 4: L1 5: R1 6: L3 7: R3 8: select 9: start 10: Home 11:VolMute \n 12: DPAD u 13: DPAD R 14: DPAD L 15: DPAD D \n 16: L2 17: R2 \n 18:left stick left 19:left stick right 20:left stick down 21:left stick up \n 22:right stick left 23:right stick right 24:right stick down 25:right stick up \n 26: circle joysticks 27: L4 28 : R4 29: L5 30: R5 31: Gyro/Accel 32: Touchpad"); + while (Serial.available() < 3) { + dualsense->timestamp(); + dualsense->sendGamepadReport(); + delay(20); + } + while (Serial.available() > 2) { + Serial.println("Waiting for input"); + selection = Serial.parseInt(); + } + Serial.println(selection); + switch (selection) { + case 0: { + Serial.println("Pressing Cross"); + dualsense->press(DUALSENSE_BUTTON_A); + delay(500); + dualsense->release(DUALSENSE_BUTTON_A); + break; + } + case 1: { + dualsense->press(DUALSENSE_BUTTON_B); + Serial.println("Pressing Circle"); + delay(100); + dualsense->release(DUALSENSE_BUTTON_B); + break; + } + case 2: { + dualsense->press(DUALSENSE_BUTTON_X); + Serial.println("Pressing Square"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_X); + break; + } + case 3: + dualsense->press(DUALSENSE_BUTTON_Y); + Serial.println("Pressing Triangle"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_Y); + break; + case 4: + dualsense->press(DUALSENSE_BUTTON_LB); + Serial.println("Pressing L1"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_LB); + break; + case 5: + dualsense->press(DUALSENSE_BUTTON_RB); + Serial.println("Pressing R1"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_RB); + break; + case 6: + dualsense->press(DUALSENSE_BUTTON_LS); + Serial.println("Pressing L3"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_LS); + break; + case 7: + dualsense->press(DUALSENSE_BUTTON_RS); + Serial.println("Pressing R3"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_RS); + break; + case 8: + dualsense->press(DUALSENSE_BUTTON_SELECT); + Serial.println("Pressing Select"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_SELECT); + break; + case 9: + dualsense->press(DUALSENSE_BUTTON_START); + Serial.println("Pressing Start"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_START); + break; + case 10: + dualsense->press(DUALSENSE_BUTTON_MODE); + Serial.println("Pressing Home"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_MODE); + break; + case 11: + dualsense->press(DUALSENSE_BUTTON_MUTE); + Serial.println("Pressing Mute"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_MUTE); + break; + case 12: + dualsense->pressDPadDirection(DUALSENSE_BUTTON_DPAD_NORTH); + Serial.println("Pressing DPAD UP"); + delay(200); + dualsense->pressDPadDirection(DUALSENSE_BUTTON_DPAD_NONE); + break; + case 13: + dualsense->pressDPadDirection(DUALSENSE_BUTTON_DPAD_EAST); + Serial.println("Pressing DPAD RIGHT"); + delay(200); + dualsense->pressDPadDirection(DUALSENSE_BUTTON_DPAD_NONE); + break; + case 14: + dualsense->pressDPadDirection(DUALSENSE_BUTTON_DPAD_WEST); + Serial.println("Pressing DPAD LEFT"); + delay(200); + dualsense->pressDPadDirection(DUALSENSE_BUTTON_DPAD_NONE); + break; + case 15: + dualsense->pressDPadDirection(DUALSENSE_BUTTON_DPAD_SOUTH); + Serial.println("Pressing DPAD DOWN"); + delay(200); + dualsense->pressDPadDirection(DUALSENSE_BUTTON_DPAD_NONE); + break; + case 16: + dualsense->setLeftTrigger(100); + Serial.println("Pressing L2"); + delay(200); + dualsense->setLeftTrigger(0); + delay(200); + dualsense->press(DUALSENSE_BUTTON_LT); + delay(200); + dualsense->release(DUALSENSE_BUTTON_LT); + break; + case 17: + dualsense->setRightTrigger(100); + Serial.println("Pressing R2"); + delay(200); + dualsense->setRightTrigger(0); + delay(200); + dualsense->press(DUALSENSE_BUTTON_RT); + delay(200); + dualsense->release(DUALSENSE_BUTTON_RT); + break; + case 18: + dualsense->setLeftThumb(-120, 0); + Serial.println("Moving left_analog left"); + delay(200); + dualsense->setLeftThumb(0, 0); + break; + case 19: + dualsense->setLeftThumb(120, 0); + Serial.println("Moving left_analog Right"); + delay(200); + dualsense->setLeftThumb(0, 0); + break; + case 20: { + dualsense->setLeftThumb(0, 120); + Serial.println("Moving left_analog down"); + delay(200); + dualsense->setLeftThumb(0, 0); + break; + } + case 21: { + dualsense->setLeftThumb(0, -120); + Serial.println("Moving left_analog up"); + delay(200); + dualsense->setLeftThumb(0, 0); + break; + } + case 22: { + dualsense->setRightThumb(-120, 0); + Serial.println("Moving right_analog left"); + delay(200); + dualsense->setRightThumb(0, 0); + break; + } + case 23: { + dualsense->setRightThumb(120, 0); + Serial.println("Moving right_analog Right"); + delay(200); + dualsense->setRightThumb(0, 0); + break; + } + case 24: { + dualsense->setRightThumb(0, 120); + Serial.println("Moving right_analog down"); + delay(200); + dualsense->setRightThumb(0, 0); + break; + } + case 25: { + dualsense->setRightThumb(0, -120); + Serial.println("Moving right_analog up"); + delay(200); + dualsense->setRightThumb(0, 0); + break; + } + case 26: { + Serial.println("Circle both joysticks"); + for (float i = 0; i < TWO_PI; i += STEP) { + dualsense->setLeftThumb(cos(i) * 100, sin(i) * 100); + dualsense->setRightThumb(cos(i) * 100, -sin(i) * 100); + delay(15); + } + dualsense->setRightThumb(0, 0); + dualsense->setLeftThumb(0, 0); + break; + } + case 27: { + dualsense->press(DUALSENSE_BUTTON_L4); + Serial.println("Pressing L4"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_L4); + break; + } + case 28: { + dualsense->press(DUALSENSE_BUTTON_R4); + Serial.println("Pressing R4"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_R4); + break; + } + case 29: { + dualsense->press(DUALSENSE_BUTTON_L5); + Serial.println("Pressing L5"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_L5); + break; + } + case 30: { + dualsense->press(DUALSENSE_BUTTON_R5); + Serial.println("Pressing R5"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_R5); + break; + } + case 31: { + Serial.println("sending gyro/accel movement"); + for (float i = 0; i < TWO_PI; i += STEP) { + dualsense->setAccel(cos(i) * 300, sin(i) * 300, tan(i) * 300); + dualsense->setGyro(cos(i) * 400, sin(i) * 400, tan(i) * 400); + delay(5); + } + break; + } + case 32: { + Serial.println("Sending touchpad movement"); + delay(200); + for (float i = 0; i < TWO_PI; i += STEP) { + dualsense->setLeftTouchpad(cos(i) * 400 + 1000, sin(i) * 400 + 540); + delay(15); + } + delay(20); + Serial.println("Sending touchpad click"); + dualsense->press(DUALSENSE_BUTTON_TOUCHPAD); + Serial.println("Pressing R5"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_TOUCHPAD); + dualsense->releaseLeftTouchpad(); + break; + } + default: { + Serial.println("Selection invalid"); + Serial.println(selection); + } + } + } + + } else { + Serial.println("disconnected"); + delay(500); + } +}