From 8b53518a34f92672b0a3212b976f608a5e610ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Marcelo=20zambrano=20Rosas?= Date: Mon, 24 Nov 2025 19:46:06 -0300 Subject: [PATCH 01/19] Initial Dualsense Edge emulation support adapted code from hhd and other sources to the CompositeHID library. --- DualSenseGamepadDevice.cpp | 593 +++++++++++++++++++++++++++++ DualsenseDescriptors.h | 364 ++++++++++++++++++ DualsenseGamepadConfiguration.h | 25 ++ DualsenseGamepadConfigurations.cpp | 53 +++ DualsenseGamepadDevice.h | 310 +++++++++++++++ Dualsense_Edge_Controller.ino | 358 +++++++++++++++++ 6 files changed, 1703 insertions(+) create mode 100644 DualSenseGamepadDevice.cpp create mode 100644 DualsenseDescriptors.h create mode 100644 DualsenseGamepadConfiguration.h create mode 100644 DualsenseGamepadConfigurations.cpp create mode 100644 DualsenseGamepadDevice.h create mode 100644 Dualsense_Edge_Controller.ino diff --git a/DualSenseGamepadDevice.cpp b/DualSenseGamepadDevice.cpp new file mode 100644 index 0000000..02b7f9f --- /dev/null +++ b/DualSenseGamepadDevice.cpp @@ -0,0 +1,593 @@ +#include "DualsenseGamepadDevice.h" +#include "BleCompositeHID.h" +#include "DualsenseDescriptors.h" +#include +#include +#include +#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) { + { + 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..9555093 --- /dev/null +++ b/DualsenseDescriptors.h @@ -0,0 +1,364 @@ + +#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 Pirch 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 + 0x00 // Unknown (bTag: 0x00, bType: 0x00) + + // 429 bytes +}; +static_assert(sizeof(DualsenseEdge_HIDDescriptor) == 429, "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..e8e1b8d --- /dev/null +++ b/DualsenseGamepadDevice.h @@ -0,0 +1,310 @@ +#ifndef DUALSENSE_GAMEPAD_DEVICE_H +#define DUALSENSE_GAMEPAD_DEVICE_H + +#include +#include +#include +#include + +#include "BLEHostConfiguration.h" +#include "BaseCompositeDevice.h" +#include "DualsenseDescriptors.h" +#include "DualsenseGamepadConfiguration.h" +#include "GamepadDevice.h" +#include "esp_mac.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 = 0x01, + SOUTH = 0x02, + WEST = 0x04 +}; + +// 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 tag = 0; + uint8_t motor_right = 0, motor_left = 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; // 1 bits for share/menu button + 7 bit padding (1 byte) +} __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/Dualsense_Edge_Controller.ino b/Dualsense_Edge_Controller.ino new file mode 100644 index 0000000..71b5725 --- /dev/null +++ b/Dualsense_Edge_Controller.ino @@ -0,0 +1,358 @@ +#include + +#include +#include +#define CONFIG_BT_NIMBLE_EXT_ADV 0 +#define CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE 4096 +#define CONFIG_MTU_SIZE 430 +#define CONFIG_DEFAULT_PHY BLE_GAP_LE_PHY_2M_MASK + +int ledPin = 8; // LED connected to digital pin 13 +uint8_t playerLEDs = 0x00; +DualsenseGamepadDevice* dualsense; +BleCompositeHID compositeHID("Musulesteishon Edge", "YeaSeb", 100); +void OnPlayerLEDChange(DualsenseGamepadOutputReportData data) +{ + 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"); + } + } +} +void OnVibrateEvent(DualsenseGamepadOutputReportData data) +{ + + if (data.motor_right > 0 || data.motor_left > 0) { + digitalWrite(ledPin, LOW); + } else { + digitalWrite(ledPin, HIGH); + } + if (data.player_leds != playerLEDs) { + playerLEDs = data.player_leds; + OnPlayerLEDChange(data); + } + //Serial.println("Output event. Weak motor: " + String(data.motor_left) + " Strong motor: " + String(data.motor_right) + " Mute LED: " + String(data.mute_button_led) + " LED R: " + String(data.lightbar_red) + " G: " + String(data.lightbar_green) + " B: " + String(data.lightbar_blue)); +} + +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 \n 0: A \n 1: B \n 2: X \n 3: Y \n 4: X \n "); + 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_SHARE); + Serial.println("Pressing Share"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_SHARE); + 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(-120, 0); + Serial.println("Moving right_analog left"); + delay(200); + dualsense->setRightThumb(0, 0); + break; + } + case 25: { + dualsense->setRightThumb(0, 120); + Serial.println("Moving right_analog down"); + delay(200); + dualsense->setRightThumb(0, 0); + break; + } + case 26: { + dualsense->setRightThumb(0, -120); + Serial.println("Moving right_analog up"); + delay(200); + dualsense->setRightThumb(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); + dualsense->releaseLeftTouchpad(); + break; + } + default: { + Serial.println("Selection invalid"); + Serial.println(selection); + } + } + } + + } else { + Serial.println("disconnected"); + delay(500); + } + +} From 1e71fcf2f6555028c88ce2659423c1185425886b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Marcelo=20zambrano=20Rosas?= Date: Mon, 24 Nov 2025 19:55:45 -0300 Subject: [PATCH 02/19] Added example sketch Created Dualsense_Edge_Controller.ino with various serial commands for testing --- .../Dualsense_Edge_Controller.ino | 360 ++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino 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..307ba6b --- /dev/null +++ b/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino @@ -0,0 +1,360 @@ +#include + +#include +#include +#define CONFIG_BT_NIMBLE_EXT_ADV 0 +#define CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE 4096 +#define CONFIG_MTU_SIZE 430 +#define CONFIG_DEFAULT_PHY BLE_GAP_LE_PHY_2M_MASK + +int ledPin = 8; // LED connected to digital pin 8 on ESP32C3 +uint8_t playerLEDs = 0x00; +DualsenseGamepadDevice* dualsense; +BleCompositeHID compositeHID("Libresteishon Edge", "YeaSeb", 100); + +//decode the player led +void OnPlayerLEDChange(DualsenseGamepadOutputReportData data) +{ + 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"); + } + } +} +void OnVibrateEvent(DualsenseGamepadOutputReportData data) +{ + + if (data.motor_right > 0 || data.motor_left > 0) { + digitalWrite(ledPin, LOW); + } else { + digitalWrite(ledPin, HIGH); + } + if (data.player_leds != playerLEDs) { + playerLEDs = data.player_leds; + OnPlayerLEDChange(data); + } + Serial.println("Output event. Weak motor: " + String(data.motor_left) + " Strong motor: " + String(data.motor_right) + " Mute LED: " + String(data.mute_button_led) + " LED R: " + String(data.lightbar_red) + " G: " + String(data.lightbar_green) + " B: " + String(data.lightbar_blue)); +} + +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()) { + //initialization can be a bit unrealiable + delay(150); + dualsense->sendPairingInfoReport(); + + dualsense->sendFirmInfoReport(); + + dualsense->sendCalibrationReport(); + + delay(280); + dualsense->resetInputs(); + int selection = 33; + + while (compositeHID.isConnected()) { + Serial.println("Select a button/axis to be activated for testing \n 0: A \n 1: B \n 2: X \n 3: Y \n 4: X \n "); + 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_SHARE); + Serial.println("Pressing Share"); + delay(200); + dualsense->release(DUALSENSE_BUTTON_SHARE); + 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(-120, 0); + Serial.println("Moving right_analog left"); + delay(200); + dualsense->setRightThumb(0, 0); + break; + } + case 25: { + dualsense->setRightThumb(0, 120); + Serial.println("Moving right_analog down"); + delay(200); + dualsense->setRightThumb(0, 0); + break; + } + case 26: { + dualsense->setRightThumb(0, -120); + Serial.println("Moving right_analog up"); + delay(200); + dualsense->setRightThumb(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 += 0.05) { + 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 += 0.05) { + dualsense->setLeftTouchpad(cos(i) * 400 + 1000, sin(i) * 400 + 540); + delay(15); + } + delay(20); + dualsense->releaseLeftTouchpad(); + break; + } + default: { + Serial.println("Selection invalid"); + Serial.println(selection); + } + } + } + + } else { + Serial.println("disconnected"); + delay(500); + } + +} From bab6cd0e6ef82746a23f66d82b4d095f966a369f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Marcelo=20zambrano=20Rosas?= Date: Mon, 24 Nov 2025 19:56:39 -0300 Subject: [PATCH 03/19] Delete Dualsense_Edge_Controller.ino --- Dualsense_Edge_Controller.ino | 358 ---------------------------------- 1 file changed, 358 deletions(-) delete mode 100644 Dualsense_Edge_Controller.ino diff --git a/Dualsense_Edge_Controller.ino b/Dualsense_Edge_Controller.ino deleted file mode 100644 index 71b5725..0000000 --- a/Dualsense_Edge_Controller.ino +++ /dev/null @@ -1,358 +0,0 @@ -#include - -#include -#include -#define CONFIG_BT_NIMBLE_EXT_ADV 0 -#define CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE 4096 -#define CONFIG_MTU_SIZE 430 -#define CONFIG_DEFAULT_PHY BLE_GAP_LE_PHY_2M_MASK - -int ledPin = 8; // LED connected to digital pin 13 -uint8_t playerLEDs = 0x00; -DualsenseGamepadDevice* dualsense; -BleCompositeHID compositeHID("Musulesteishon Edge", "YeaSeb", 100); -void OnPlayerLEDChange(DualsenseGamepadOutputReportData data) -{ - 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"); - } - } -} -void OnVibrateEvent(DualsenseGamepadOutputReportData data) -{ - - if (data.motor_right > 0 || data.motor_left > 0) { - digitalWrite(ledPin, LOW); - } else { - digitalWrite(ledPin, HIGH); - } - if (data.player_leds != playerLEDs) { - playerLEDs = data.player_leds; - OnPlayerLEDChange(data); - } - //Serial.println("Output event. Weak motor: " + String(data.motor_left) + " Strong motor: " + String(data.motor_right) + " Mute LED: " + String(data.mute_button_led) + " LED R: " + String(data.lightbar_red) + " G: " + String(data.lightbar_green) + " B: " + String(data.lightbar_blue)); -} - -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 \n 0: A \n 1: B \n 2: X \n 3: Y \n 4: X \n "); - 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_SHARE); - Serial.println("Pressing Share"); - delay(200); - dualsense->release(DUALSENSE_BUTTON_SHARE); - 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(-120, 0); - Serial.println("Moving right_analog left"); - delay(200); - dualsense->setRightThumb(0, 0); - break; - } - case 25: { - dualsense->setRightThumb(0, 120); - Serial.println("Moving right_analog down"); - delay(200); - dualsense->setRightThumb(0, 0); - break; - } - case 26: { - dualsense->setRightThumb(0, -120); - Serial.println("Moving right_analog up"); - delay(200); - dualsense->setRightThumb(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); - dualsense->releaseLeftTouchpad(); - break; - } - default: { - Serial.println("Selection invalid"); - Serial.println(selection); - } - } - } - - } else { - Serial.println("disconnected"); - delay(500); - } - -} From 14b8a22db97d54ada43ea5e92e23864fe23da35c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Marcelo=20zambrano=20Rosas?= Date: Mon, 24 Nov 2025 20:03:26 -0300 Subject: [PATCH 04/19] Update README.md Added emulated dualsense features --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) 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) From 8a9e4a3ad21ecfa8e17244e0d0de955c11e22381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Marcelo=20zambrano=20Rosas?= Date: Tue, 25 Nov 2025 01:36:16 -0300 Subject: [PATCH 05/19] Update Dualsense_Edge_Controller.ino Missed some cleanup, added more detailed text for serial driven testing of individual inputs. --- .../Dualsense_Edge_Controller.ino | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino b/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino index 307ba6b..b304b45 100644 --- a/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino +++ b/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino @@ -7,12 +7,10 @@ #define CONFIG_MTU_SIZE 430 #define CONFIG_DEFAULT_PHY BLE_GAP_LE_PHY_2M_MASK -int ledPin = 8; // LED connected to digital pin 8 on ESP32C3 +int ledPin = 8; // LED connected to digital pin 13 uint8_t playerLEDs = 0x00; DualsenseGamepadDevice* dualsense; -BleCompositeHID compositeHID("Libresteishon Edge", "YeaSeb", 100); - -//decode the player led +BleCompositeHID compositeHID("Musulesteishon Edge", "YeaSeb", 100); void OnPlayerLEDChange(DualsenseGamepadOutputReportData data) { if ((data.player_leds & DUALSENSE_PLAYERLED_ON) == DUALSENSE_PLAYERLED_ON) { @@ -55,7 +53,7 @@ void OnVibrateEvent(DualsenseGamepadOutputReportData data) playerLEDs = data.player_leds; OnPlayerLEDChange(data); } - Serial.println("Output event. Weak motor: " + String(data.motor_left) + " Strong motor: " + String(data.motor_right) + " Mute LED: " + String(data.mute_button_led) + " LED R: " + String(data.lightbar_red) + " G: " + String(data.lightbar_green) + " B: " + String(data.lightbar_blue)); + // Serial.println("Output event. Weak motor: " + String(data.motor_left) + " Strong motor: " + String(data.motor_right) + " Mute LED: " + String(data.mute_button_led) + " LED R: " + String(data.lightbar_red) + " G: " + String(data.lightbar_green) + " B: " + String(data.lightbar_blue)); } void setup() @@ -81,7 +79,7 @@ void setup() // 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 @@ -92,7 +90,6 @@ void setup() void loop() { if (compositeHID.isConnected()) { - //initialization can be a bit unrealiable delay(150); dualsense->sendPairingInfoReport(); @@ -100,12 +97,13 @@ void loop() dualsense->sendCalibrationReport(); - delay(280); + delay(280); dualsense->resetInputs(); - int selection = 33; + 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 \n 0: A \n 1: B \n 2: X \n 3: Y \n 4: X \n "); + 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(); @@ -181,10 +179,10 @@ void loop() dualsense->release(DUALSENSE_BUTTON_START); break; case 10: - dualsense->press(DUALSENSE_BUTTON_SHARE); - Serial.println("Pressing Share"); + dualsense->press(DUALSENSE_BUTTON_MODE); + Serial.println("Pressing Home"); delay(200); - dualsense->release(DUALSENSE_BUTTON_SHARE); + dualsense->release(DUALSENSE_BUTTON_MODE); break; case 11: dualsense->press(DUALSENSE_BUTTON_MUTE); @@ -277,24 +275,28 @@ void loop() break; } case 24: { - dualsense->setRightThumb(-120, 0); - Serial.println("Moving right_analog left"); + 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 down"); + dualsense->setRightThumb(0, -120); + Serial.println("Moving right_analog up"); delay(200); dualsense->setRightThumb(0, 0); break; } case 26: { - dualsense->setRightThumb(0, -120); - Serial.println("Moving right_analog up"); - delay(200); + 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: { @@ -327,7 +329,7 @@ void loop() } case 31: { Serial.println("sending gyro/accel movement"); - for (float i = 0; i < TWO_PI; i += 0.05) { + 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); @@ -337,11 +339,16 @@ void loop() case 32: { Serial.println("Sending touchpad movement"); delay(200); - for (float i = 0; i < TWO_PI; i += 0.05) { + 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; } @@ -356,5 +363,4 @@ void loop() Serial.println("disconnected"); delay(500); } - } From 9ea701df3627012232e1706612d9c2c30a1f23d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Marcelo=20zambrano=20Rosas?= Date: Tue, 25 Nov 2025 01:54:14 -0300 Subject: [PATCH 06/19] Update Dualsense_Edge_Controller.ino --- .../Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino b/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino index b304b45..a01b130 100644 --- a/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino +++ b/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino @@ -10,7 +10,7 @@ int ledPin = 8; // LED connected to digital pin 13 uint8_t playerLEDs = 0x00; DualsenseGamepadDevice* dualsense; -BleCompositeHID compositeHID("Musulesteishon Edge", "YeaSeb", 100); +BleCompositeHID compositeHID("Libresteishon Edge", "YeaSeb", 100); void OnPlayerLEDChange(DualsenseGamepadOutputReportData data) { if ((data.player_leds & DUALSENSE_PLAYERLED_ON) == DUALSENSE_PLAYERLED_ON) { From 2d97099c99bd4ba25fa3e62e59056e9242e840d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Marcelo=20zambrano=20Rosas?= Date: Tue, 25 Nov 2025 01:55:29 -0300 Subject: [PATCH 07/19] Update Dualsense_Edge_Controller.ino --- .../Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino b/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino index a01b130..4af7163 100644 --- a/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino +++ b/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino @@ -53,7 +53,7 @@ void OnVibrateEvent(DualsenseGamepadOutputReportData data) playerLEDs = data.player_leds; OnPlayerLEDChange(data); } - // Serial.println("Output event. Weak motor: " + String(data.motor_left) + " Strong motor: " + String(data.motor_right) + " Mute LED: " + String(data.mute_button_led) + " LED R: " + String(data.lightbar_red) + " G: " + String(data.lightbar_green) + " B: " + String(data.lightbar_blue)); + Serial.println("Output event. Weak motor: " + String(data.motor_left) + " Strong motor: " + String(data.motor_right) + " Mute LED: " + String(data.mute_button_led) + " LED R: " + String(data.lightbar_red) + " G: " + String(data.lightbar_green) + " B: " + String(data.lightbar_blue)); } void setup() From 60af535cb6365292b8f6c4f0fd0129dc34d97f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Marcelo=20zambrano=20Rosas?= Date: Tue, 25 Nov 2025 14:01:15 -0300 Subject: [PATCH 08/19] Updated LED example code Better handling of reports not intended for LED. Might move all that code to it's own event as part of the library after the fact, for now it works in the sketch. --- .../Dualsense_Edge_Controller.ino | 81 +++++++++++-------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino b/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino index 4af7163..d4837d7 100644 --- a/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino +++ b/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino @@ -2,44 +2,60 @@ #include #include -#define CONFIG_BT_NIMBLE_EXT_ADV 0 -#define CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE 4096 +#define CONFIG_BT_NIMBLE_EXT_ADV 1 #define CONFIG_MTU_SIZE 430 #define CONFIG_DEFAULT_PHY BLE_GAP_LE_PHY_2M_MASK 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 OnPlayerLEDChange(DualsenseGamepadOutputReportData data) + +void onLEDsetup(DualsenseGamepadOutputReportData data) { - 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.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) { @@ -49,13 +65,12 @@ void OnVibrateEvent(DualsenseGamepadOutputReportData data) } else { digitalWrite(ledPin, HIGH); } - if (data.player_leds != playerLEDs) { - playerLEDs = data.player_leds; - OnPlayerLEDChange(data); + if (data.lightbar_setup>0 || data.lightbar_red>0 || data.lightbar_green>0 || data.lightbar_red>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) + " Mute LED: " + String(data.mute_button_led) + " LED R: " + String(data.lightbar_red) + " G: " + String(data.lightbar_green) + " B: " + String(data.lightbar_blue)); -} + 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); From 16cb7f12229cf1661f33f0eca96aa1c444b6e831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Marcelo=20zambrano=20Rosas?= Date: Tue, 25 Nov 2025 14:09:01 -0300 Subject: [PATCH 09/19] Cleaning up unused stuff Removed a couple of macros I was previously trying for assigning values to the nimbledevice config, those files are unmodified in this branch so it doesn't make sense to keep them --- .../Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino b/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino index d4837d7..e2d01ee 100644 --- a/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino +++ b/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino @@ -3,8 +3,6 @@ #include #include #define CONFIG_BT_NIMBLE_EXT_ADV 1 -#define CONFIG_MTU_SIZE 430 -#define CONFIG_DEFAULT_PHY BLE_GAP_LE_PHY_2M_MASK int ledPin = 8; // LED connected to digital pin 13 uint8_t playerLEDs = 0x00; From 792dca10a8654ef5bafe10bacf6c6f88c921377a Mon Sep 17 00:00:00 2001 From: Mystfit Date: Fri, 26 Dec 2025 09:00:42 +1300 Subject: [PATCH 10/19] Missing z accel check Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- DualSenseGamepadDevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DualSenseGamepadDevice.cpp b/DualSenseGamepadDevice.cpp index 02b7f9f..239d5d7 100644 --- a/DualSenseGamepadDevice.cpp +++ b/DualSenseGamepadDevice.cpp @@ -326,7 +326,7 @@ void DualsenseGamepadDevice::setAccel(int16_t x, int16_t y, int16_t z) 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) { + 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); From 1ca8ad0159c83f516c9973e4a1f1149ba6a65bee Mon Sep 17 00:00:00 2001 From: Mystfit Date: Fri, 26 Dec 2025 09:02:34 +1300 Subject: [PATCH 11/19] lightbar_red checked twice instead of lightbar_blue Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino b/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino index e2d01ee..f83acc9 100644 --- a/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino +++ b/examples/dualsenseExamples/Dualsense_Edge_Controller/Dualsense_Edge_Controller.ino @@ -63,7 +63,7 @@ void OnVibrateEvent(DualsenseGamepadOutputReportData data) } else { digitalWrite(ledPin, HIGH); } - if (data.lightbar_setup>0 || data.lightbar_red>0 || data.lightbar_green>0 || data.lightbar_red>0 || data.mute_button_led>0 || data.player_leds>0){ + 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); From 49b6d4c967707510cd912410e0d5936bfe7999c3 Mon Sep 17 00:00:00 2001 From: Mystfit Date: Fri, 26 Dec 2025 09:04:08 +1300 Subject: [PATCH 12/19] Pitch typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- DualsenseDescriptors.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DualsenseDescriptors.h b/DualsenseDescriptors.h index 9555093..12aaff7 100644 --- a/DualsenseDescriptors.h +++ b/DualsenseDescriptors.h @@ -106,7 +106,7 @@ static const uint8_t DualsenseEdge_FirmwareInfo[] { 0x57 }; static const uint8_t DualsenseEdge_StockCalibration[] { - 0x00, // Gyro Pirch Bias + 0x00, // Gyro Pitch Bias 0x00, 0x00, // Gyro Yaw Bias 0x00, From fab3762eedf742eb6dbe1cba84f7a6e318d3a37a Mon Sep 17 00:00:00 2001 From: Mystfit Date: Fri, 26 Dec 2025 09:05:42 +1300 Subject: [PATCH 13/19] Wrong comment for CRC32 field Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- DualsenseGamepadDevice.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DualsenseGamepadDevice.h b/DualsenseGamepadDevice.h index e8e1b8d..0ffa865 100644 --- a/DualsenseGamepadDevice.h +++ b/DualsenseGamepadDevice.h @@ -193,7 +193,7 @@ struct DualsenseGamepadInputReportData { uint8_t data_54_74[18]; uint8_t reserved3 = 0x00; - uint32_t crc32 = 0; // 1 bits for share/menu button + 7 bit padding (1 byte) + uint32_t crc32 = 0; // 4-byte CRC32 checksum } __attribute__((packed)); #pragma pack(pop) struct DualsenseGamepadPairingReportdata { From f6f8ec5099b52419f93e08003369212f49c36b0b Mon Sep 17 00:00:00 2001 From: Mystfit Date: Fri, 26 Dec 2025 09:23:28 +1300 Subject: [PATCH 14/19] Updating bitflags for dpad Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- DualsenseGamepadDevice.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DualsenseGamepadDevice.h b/DualsenseGamepadDevice.h index 0ffa865..5d91c53 100644 --- a/DualsenseGamepadDevice.h +++ b/DualsenseGamepadDevice.h @@ -51,9 +51,9 @@ enum DualsenseDpadFlags : uint8_t { NONE = 0x08, NORTH = 0x00, - EAST = 0x01, - SOUTH = 0x02, - WEST = 0x04 + EAST = 0x02, + SOUTH = 0x04, + WEST = 0x08 }; // Trigger range From dca550e5d12c24c2dfbe9d3a6639a58f30060560 Mon Sep 17 00:00:00 2001 From: Mystfit Date: Fri, 26 Dec 2025 09:24:12 +1300 Subject: [PATCH 15/19] Updating bitflags for dpad Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- DualSenseGamepadDevice.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DualSenseGamepadDevice.cpp b/DualSenseGamepadDevice.cpp index 239d5d7..e9400ee 100644 --- a/DualSenseGamepadDevice.cpp +++ b/DualSenseGamepadDevice.cpp @@ -406,19 +406,19 @@ bool DualsenseGamepadDevice::isDPadPressedFlag(DualsenseDpadFlags direction) if (direction == DualsenseDpadFlags::NORTH) { return _inputReport.hat == DUALSENSE_BUTTON_DPAD_NORTH; - } else if (direction == (DualsenseDpadFlags::NORTH & DualsenseDpadFlags::EAST)) { + } 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)) { + } 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)) { + } 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)) { + } else if (direction == (DualsenseDpadFlags::NORTH | DualsenseDpadFlags::WEST)) { return _inputReport.hat == DUALSENSE_BUTTON_DPAD_NORTHWEST; } return false; From 504cff41eafac1c67889b8cd5992b0e3d8a47155 Mon Sep 17 00:00:00 2001 From: Mystfit Date: Fri, 26 Dec 2025 15:08:14 +1300 Subject: [PATCH 16/19] Updated includes --- BLEHostConfiguration.h | 1 - BaseCompositeDevice.h | 1 - DualSenseGamepadDevice.cpp | 1 + DualsenseGamepadDevice.h | 2 +- 4 files changed, 2 insertions(+), 3 deletions(-) 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 index e9400ee..69fb96b 100644 --- a/DualSenseGamepadDevice.cpp +++ b/DualSenseGamepadDevice.cpp @@ -4,6 +4,7 @@ #include #include #include +#include "esp_mac.h" #if defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" #define LOG_TAG "DualsenseGamepadDevice" diff --git a/DualsenseGamepadDevice.h b/DualsenseGamepadDevice.h index 5d91c53..8c7ab6f 100644 --- a/DualsenseGamepadDevice.h +++ b/DualsenseGamepadDevice.h @@ -1,6 +1,7 @@ #ifndef DUALSENSE_GAMEPAD_DEVICE_H #define DUALSENSE_GAMEPAD_DEVICE_H +#include #include #include #include @@ -11,7 +12,6 @@ #include "DualsenseDescriptors.h" #include "DualsenseGamepadConfiguration.h" #include "GamepadDevice.h" -#include "esp_mac.h" // Button bitmasks #define DUALSENSE_BUTTON_Y 0x08 #define DUALSENSE_BUTTON_B 0x04 From 7594a5e50ca6ce73a605f9548b3a787b05fdef73 Mon Sep 17 00:00:00 2001 From: Mystfit Date: Fri, 26 Dec 2025 23:17:06 +1300 Subject: [PATCH 17/19] Added new dualsense example to github compile workflow --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From ae45c85f4b9d7344e03b157c8aa97554b25a771d Mon Sep 17 00:00:00 2001 From: Mystfit Date: Wed, 7 Jan 2026 01:10:42 +1300 Subject: [PATCH 18/19] Removed extra byte at the end of the HID descriptor that was breaking connections to windows --- DualsenseDescriptors.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/DualsenseDescriptors.h b/DualsenseDescriptors.h index 12aaff7..0f45328 100644 --- a/DualsenseDescriptors.h +++ b/DualsenseDescriptors.h @@ -355,10 +355,9 @@ static const uint8_t DualsenseEdge_HIDDescriptor[] { 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 - 0x00 // Unknown (bTag: 0x00, bType: 0x00) + 0xC0 // End Collection - // 429 bytes + // 428 bytes }; -static_assert(sizeof(DualsenseEdge_HIDDescriptor) == 429, "Wrong size"); +static_assert(sizeof(DualsenseEdge_HIDDescriptor) == 428, "Wrong size"); #endif From 48215da03668e401dcab6884fdd4b827b63ad517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Marcelo=20zambrano=20Rosas?= Date: Thu, 15 Jan 2026 16:08:31 -0300 Subject: [PATCH 19/19] Fix Rumble for steam Use bit 3 and 4 as opposed to gpadtester.com since it's not consistent with how steam handles it --- DualsenseGamepadDevice.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DualsenseGamepadDevice.h b/DualsenseGamepadDevice.h index 8c7ab6f..0afa589 100644 --- a/DualsenseGamepadDevice.h +++ b/DualsenseGamepadDevice.h @@ -95,8 +95,9 @@ class DualsenseGamepadCallbacks : public NimBLECharacteristicCallbacks { struct DualsenseGamepadOutputReportData { uint8_t type; uint8_t seq_tag = 0; - uint8_t 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;