From edd76fa1db7e1c2fe0101ca15260ebf7cd823c86 Mon Sep 17 00:00:00 2001 From: Slavey Karadzhov <slav@attachix.com> Date: Mon, 2 May 2022 11:46:40 +0200 Subject: [PATCH] Added Switch Joycon sample. --- Sming/Libraries/SwitchJoycon/.cs | 0 Sming/Libraries/SwitchJoycon/README.rst | 76 +++ Sming/Libraries/SwitchJoycon/component.mk | 5 + .../SwitchJoycon/samples/Bluetooth_Joycon/.cs | 0 .../samples/Bluetooth_Joycon/Makefile | 9 + .../samples/Bluetooth_Joycon/README.rst | 23 + .../Bluetooth_Joycon/app/application.cpp | 54 +++ .../samples/Bluetooth_Joycon/component.mk | 1 + Sming/Libraries/SwitchJoycon/src/.cs | 0 .../SwitchJoycon/src/SwitchJoycon.cpp | 438 ++++++++++++++++++ .../Libraries/SwitchJoycon/src/SwitchJoycon.h | 154 ++++++ .../src/SwitchJoyconConnection.cpp | 17 + .../SwitchJoycon/src/SwitchJoyconConnection.h | 36 ++ 13 files changed, 813 insertions(+) create mode 100644 Sming/Libraries/SwitchJoycon/.cs create mode 100644 Sming/Libraries/SwitchJoycon/README.rst create mode 100644 Sming/Libraries/SwitchJoycon/component.mk create mode 100644 Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/.cs create mode 100644 Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/Makefile create mode 100644 Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/README.rst create mode 100644 Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/app/application.cpp create mode 100644 Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/component.mk create mode 100644 Sming/Libraries/SwitchJoycon/src/.cs create mode 100644 Sming/Libraries/SwitchJoycon/src/SwitchJoycon.cpp create mode 100644 Sming/Libraries/SwitchJoycon/src/SwitchJoycon.h create mode 100644 Sming/Libraries/SwitchJoycon/src/SwitchJoyconConnection.cpp create mode 100644 Sming/Libraries/SwitchJoycon/src/SwitchJoyconConnection.h diff --git a/Sming/Libraries/SwitchJoycon/.cs b/Sming/Libraries/SwitchJoycon/.cs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Sming/Libraries/SwitchJoycon/README.rst b/Sming/Libraries/SwitchJoycon/README.rst new file mode 100644 index 0000000000..537b2c9a4a --- /dev/null +++ b/Sming/Libraries/SwitchJoycon/README.rst @@ -0,0 +1,76 @@ +Switch Joycon +============= + +.. highlight:: c++ + +Introduction +------------ +This library allows you to make the ESP32 act as a Nintendo Switch Joycon and control what it does. +The library uses :library:`NimBLE` for faster and lighter communication. + +Disclaimer +---------- +We are not affiliated, associated, authorized, endorsed by, or in any way officially connected with Nintendo, +or any of its subsidiaries or its affiliates. +The names Nintendo, Nintendo Switch and Joycon as well as related names, marks, emblems and images are +registered trademarks of their respective owners. + +Features +-------- + +Using this library you can do the following: + + - Button press and release (16 buttons) + - Switch Hat (1 hat ) + - Rotate 4 Axis + +Using +----- + +1. Add ``COMPONENT_DEPENDS += SwitchJoycon`` to your application componenent.mk file. +2. Add these lines to your application:: + + #include <SwitchJoycon.h> + + namespace + { + SwitchJoycon joycon; + + // ... + + } // namespace + + void init() + { + // ... + + joycon.begin(); + } + + +Notes +----- +By default, reports are sent on every button press/release or axis/hat movement, however this can be disabled:: + + joycon.setAutoReport(false); + +and then you should manually call sendReport on the joycon instance as shown below:: + + joycon.sendReport(); + + +HID Debugging +------------- + +On Linux you can install `hid-tools <https://gitlab.freedesktop.org/libevdev/hid-tools>`__ using the command below:: + + sudo pip3 install . + +Once installed hid-recorder can be used to check the device HID report description and sniff the different reports:: + + sudo hid-recorder + +Useful Links +------------ +- `Tutorial about USB HID Report Descriptors <https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/>`__ +- `HID constants <https://github.com/katyo/hid_def/blob/master/include/hid_def.h>`__ diff --git a/Sming/Libraries/SwitchJoycon/component.mk b/Sming/Libraries/SwitchJoycon/component.mk new file mode 100644 index 0000000000..7d86756dd3 --- /dev/null +++ b/Sming/Libraries/SwitchJoycon/component.mk @@ -0,0 +1,5 @@ +COMPONENT_SOC := esp32* +COMPONENT_DEPENDS := NimBLE + +COMPONENT_SRCDIRS := src +COMPONENT_INCDIRS := $(COMPONENT_SRCDIRS) \ No newline at end of file diff --git a/Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/.cs b/Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/.cs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/Makefile b/Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/Makefile new file mode 100644 index 0000000000..ff51b6c3a7 --- /dev/null +++ b/Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/Makefile @@ -0,0 +1,9 @@ +##################################################################### +#### Please don't change this file. Use component.mk instead #### +##################################################################### + +ifndef SMING_HOME +$(error SMING_HOME is not set: please configure it as an environment variable) +endif + +include $(SMING_HOME)/project.mk diff --git a/Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/README.rst b/Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/README.rst new file mode 100644 index 0000000000..94e0171a65 --- /dev/null +++ b/Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/README.rst @@ -0,0 +1,23 @@ +Switch Joycon +============= + +Introduction +------------ +This sample turns the ESP32 into a Switch Joycon (Bluetooth LE gamepad) that presses buttons and moves axis + +Possible buttons are 0 through to 15. + +Possible HAT switch position values are: +Centered, Up, UpRight, Right, DownRight, Down, DownLeft, Left, UpLeft. + + +Testing +------- + +You can use one of the following applications on your PC to test and see all buttons that were clicked. + +On Linux install ``jstest-gtk`` to test the ESP32 gamepad. Under Ubuntu this can be done by typing the following command:: + + sudo apt install jstest-gtk + +On Windows use this `Windows test application <http://www.planetpointy.co.uk/joystick-test-application/>`__. \ No newline at end of file diff --git a/Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/app/application.cpp b/Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/app/application.cpp new file mode 100644 index 0000000000..caa9ef8fe4 --- /dev/null +++ b/Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/app/application.cpp @@ -0,0 +1,54 @@ +#include <SmingCore.h> +#include <SwitchJoycon.h> + +namespace +{ +Timer procTimer; + +void onConnect(NimBLEServer& server); +void onDisconnect(NimBLEServer& server); + +SwitchJoycon joycon(SwitchJoycon::Type::Left, 100, onConnect, onDisconnect); + +void loop() +{ + if(!joycon.isConnected()) { + return; + } + + uint8_t button = random(0, 15); + + joycon.press(button); + joycon.setHat(SwitchJoycon::JoystickPosition::UpLeft); + delay(5000); + + joycon.release(button); + joycon.setHat(SwitchJoycon::JoystickPosition::Center); + delay(5000); +} + +void onConnect(NimBLEServer& server) +{ + Serial.println("Connected :) !"); + + procTimer.initializeMs(500, loop).start(); +} + +void onDisconnect(NimBLEServer& server) +{ + procTimer.stop(); + Serial.println("Disconnected :(!"); +} + +} // namespace + +void init() +{ + Serial.begin(COM_SPEED_SERIAL); + Serial.systemDebugOutput(true); + + Serial.println("Starting Joycon Gamepad sample!"); + joycon.begin(); + // Auto reporting is enabled by default. + // Use joycon.setAutoReport(false); to disable auto reporting, and then use joycon.sendReport(); as needed +} diff --git a/Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/component.mk b/Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/component.mk new file mode 100644 index 0000000000..1b24857470 --- /dev/null +++ b/Sming/Libraries/SwitchJoycon/samples/Bluetooth_Joycon/component.mk @@ -0,0 +1 @@ +COMPONENT_DEPENDS := SwitchJoycon diff --git a/Sming/Libraries/SwitchJoycon/src/.cs b/Sming/Libraries/SwitchJoycon/src/.cs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Sming/Libraries/SwitchJoycon/src/SwitchJoycon.cpp b/Sming/Libraries/SwitchJoycon/src/SwitchJoycon.cpp new file mode 100644 index 0000000000..75f10086e9 --- /dev/null +++ b/Sming/Libraries/SwitchJoycon/src/SwitchJoycon.cpp @@ -0,0 +1,438 @@ +#include "SwitchJoycon.h" + +#include <WString.h> +#include <NimBLEDevice.h> +#include <NimBLEUtils.h> +#include <NimBLEServer.h> +#include <HIDTypes.h> +#include <debug_progmem.h> + +#if DEBUG_VERBOSE_LEVEL == 3 +#include <Services/HexDump/HexDump.h> +HexDump dump; +#endif + +// TODO: Move report description to progmem +uint8_t tempHidReportDescriptor[150]; +int hidReportDescriptorSize = 0; + +constexpr uint8_t GAMEPAD_DEFAULT_REPORT_ID = 63; +constexpr uint8_t JOYSTICK_TYPE_GAMEPAD = 0x05; + +void SwitchJoycon::resetButtons() +{ + memset(&state.buttons, 0, sizeof(state.buttons)); +} + +bool SwitchJoycon::begin() +{ + if(started) { + debug_w("Service already started"); + return false; + } + + uint8_t axisCount = 0; + buttonCount = 0; + hatSwitchCount = 0; + switch(controllerType) { + case Type::Left: + /* fall through */ + case Type::Right: + buttonCount = 16; // buttonCount; + hatSwitchCount = 1; + axisCount = 4; // x, y, z and z rotation + break; + case Type::ProController: + buttonCount = 16; // buttonCount; + hatSwitchCount = 1; + axisCount = 4; // x, y, z and z rotation + break; + } + + started = true; + state.hat = static_cast<uint8_t>(JoystickPosition::Center); + state.leftX[0] = 0x00; + state.leftX[1] = 0x80; + state.leftY[0] = 0x00; + state.leftY[1] = 0x80; + state.rightX[0] = 0x00; + state.rightX[1] = 0x80; + state.rightY[0] = 0x00; + state.rightY[1] = 0x80; + + /** + * For HID debugging see: https://gitlab.freedesktop.org/libevdev/hid-tools + * For HID report descriptors see: https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/ + */ + + hidReportDescriptorSize = 0; + + // USAGE_PAGE (Generic Desktop) + tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE_PAGE(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01; + + // USAGE (Joystick - 0x04; Gamepad - 0x05; Multi-axis Controller - 0x08) + tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = JOYSTICK_TYPE_GAMEPAD; + + // COLLECTION (Application) + tempHidReportDescriptor[hidReportDescriptorSize++] = COLLECTION(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01; + + // REPORT_ID (Default: 3) + tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_ID(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = GAMEPAD_DEFAULT_REPORT_ID; + + if(buttonCount > 0) { + // USAGE_PAGE (Button) + tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE_PAGE(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09; + + // USAGE_MINIMUM (Button 1) + tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE_MINIMUM(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01; + + // USAGE_MAXIMUM (Button 16) + tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE_MAXIMUM(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = buttonCount; + + // LOGICAL_MINIMUM (0) + tempHidReportDescriptor[hidReportDescriptorSize++] = LOGICAL_MINIMUM(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00; + + // LOGICAL_MAXIMUM (1) + tempHidReportDescriptor[hidReportDescriptorSize++] = LOGICAL_MAXIMUM(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01; + + // REPORT_SIZE (1) + tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_SIZE(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01; + + // REPORT_COUNT (# of buttons) + tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_COUNT(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = buttonCount; + + // INPUT (Data,Var,Abs) + tempHidReportDescriptor[hidReportDescriptorSize++] = HIDINPUT(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x02; + } // buttonCount + + if(hatSwitchCount > 0) { + // USAGE_PAGE (Generic Desktop) + tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE_PAGE(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01; + + // USAGE (Hat Switch) + for(int i = 0; i < hatSwitchCount; i++) { + tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x39; + } + + // Logical Min (0) + tempHidReportDescriptor[hidReportDescriptorSize++] = LOGICAL_MINIMUM(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00; + + // Logical Max (7) + tempHidReportDescriptor[hidReportDescriptorSize++] = LOGICAL_MAXIMUM(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x07; + + // Report Size (4) + tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_SIZE(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x04; + + // Report Count (1) + tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_COUNT(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = hatSwitchCount; + + // Input (Data, Variable, Absolute) + tempHidReportDescriptor[hidReportDescriptorSize++] = HIDINPUT(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x42; + + // -- Padding for the 4 unused bits in the hat switch byte -- + tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE_PAGE(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09; + + // Report Size (4) + tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_SIZE(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x04; + + // Report Count (1) + tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_COUNT(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01; + + // Input (Cnst,Arr,Abs) + tempHidReportDescriptor[hidReportDescriptorSize++] = HIDINPUT(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01; + } + + if(axisCount > 0) { + // USAGE_PAGE (Generic Desktop) + tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE_PAGE(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01; + + // USAGE (X) + tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x30; + + // USAGE (Y) + tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x31; + + // USAGE (Rx) + tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x33; + + // USAGE (Ry) + tempHidReportDescriptor[hidReportDescriptorSize++] = USAGE(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x34; + + // LOGICAL_MINIMUM(255) + tempHidReportDescriptor[hidReportDescriptorSize++] = LOGICAL_MINIMUM(2); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00; + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00; + + // LOGICAL_MAXIMUM (255) + tempHidReportDescriptor[hidReportDescriptorSize++] = LOGICAL_MAXIMUM(3); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0xFF; + tempHidReportDescriptor[hidReportDescriptorSize++] = 0xFF; + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00; + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00; + + // REPORT_SIZE (16) + tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_SIZE(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x10; + + // REPORT_COUNT (axisCount) + tempHidReportDescriptor[hidReportDescriptorSize++] = REPORT_COUNT(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = axisCount; + + // INPUT (Data,Var,Abs) + tempHidReportDescriptor[hidReportDescriptorSize++] = HIDINPUT(1); + tempHidReportDescriptor[hidReportDescriptorSize++] = 0x02; + } + + // END_COLLECTION + tempHidReportDescriptor[hidReportDescriptorSize++] = END_COLLECTION(0); + + xTaskCreate(startServer, "server", 20000, (void*)this, 5, &taskHandle); + + return true; +} + +void SwitchJoycon::end() +{ + if(!started) { + return; + } + + if(taskHandle != nullptr) { + vTaskDelete(taskHandle); + } + + NimBLEDevice::deinit(true); + delete hid; + hid = nullptr; + delete inputGamepad; + inputGamepad = nullptr; + connectionStatus = nullptr; + memset(&state, 0, sizeof(state)); + + started = false; +} + +void SwitchJoycon::sendReport(void) +{ + if(!isConnected()) { + return; + } + + debug_d("Sending report ...."); + +#if DEBUG_VERBOSE_LEVEL == 3 + dump.resetAddr(); + dump.print(reinterpret_cast<uint8_t*>(&state), sizeof(state)); +#endif + + debug_d("================="); + + this->inputGamepad->setValue(state); + this->inputGamepad->notify(); +} + +bool SwitchJoycon::isPressed(uint8_t button) +{ + uint8_t index = button / 8; + uint8_t bit = button % 8; + + return bitRead(state.buttons[index], bit); +} +void SwitchJoycon::press(uint8_t button) +{ + if(isPressed(button)) { + return; + } + + uint8_t index = button / 8; + uint8_t bit = button % 8; + + bitSet(state.buttons[index], bit); + + if(autoReport) { + sendReport(); + } +} + +void SwitchJoycon::release(uint8_t button) +{ + if(!isPressed(button)) { + return; + } + + uint8_t index = button / 8; + uint8_t bit = button % 8; + + bitClear(state.buttons[index], bit); + + if(autoReport) { + sendReport(); + } +} + +void SwitchJoycon::setHat(JoystickPosition position) +{ + if(state.hat == static_cast<uint8_t>(position)) { + return; + } + + state.hat = static_cast<uint8_t>(position); + + if(autoReport) { + sendReport(); + } +} + +void SwitchJoycon::setXAxis(int16_t value) +{ + // TODO: add value checks + + state.leftX[0] = value; + state.leftX[1] = value >> 8; + + if(autoReport) { + sendReport(); + } +} + +void SwitchJoycon::setYAxis(int16_t value) +{ + // TODO: add value checks + + state.leftY[0] = value; + state.leftY[1] = value >> 8; + + if(autoReport) { + sendReport(); + } +} + +void SwitchJoycon::setZAxis(int16_t value) +{ + // TODO: add value checks + // if(value == -32768) { + // value = -32767; + // } + + state.rightX[0] = value; + state.rightX[1] = value >> 8; + + if(autoReport) { + sendReport(); + } +} + +void SwitchJoycon::setZAxisRotation(int16_t value) +{ + // TODO: add value checks + + state.rightY[0] = value; + state.rightY[1] = value >> 8; + + if(autoReport) { + sendReport(); + } +} + +bool SwitchJoycon::isConnected(void) +{ + if(connectionStatus == nullptr) { + return false; + } + + return connectionStatus->connected; +} + +void SwitchJoycon::setBatteryLevel(uint8_t level) +{ + batteryLevel = level; + if(hid != nullptr) { + hid->setBatteryLevel(batteryLevel); + } +} + +void SwitchJoycon::startServer(void* arg) +{ + SwitchJoycon* joycon = static_cast<SwitchJoycon*>(arg); + + String deviceName; + uint16_t productId = 0; + // See: http://gtoal.com/vectrex/vecx-colour/SDL/src/joystick/controller_type.h + switch(joycon->controllerType) { + case Type::Left: + deviceName = F("Joy-Con (L)"); + productId = 0x2006; + break; + case Type::Right: + deviceName = F("Joy-Con (R)"); + productId = 0x2007; + break; + case Type::ProController: + deviceName = F("Pro Controller"); + productId = 0x2009; + break; + } + + NimBLEDevice::init(deviceName.c_str()); + NimBLEServer* server = NimBLEDevice::createServer(); + server->setCallbacks(joycon->connectionStatus); + + delete joycon->hid; // TODO: ?! + joycon->hid = new NimBLEHIDDevice(server); + joycon->inputGamepad = joycon->hid->inputReport(GAMEPAD_DEFAULT_REPORT_ID); + joycon->connectionStatus->inputGamepad = joycon->inputGamepad; + + joycon->hid->manufacturer()->setValue("Nintendo"); + joycon->hid->pnp(0x01, __builtin_bswap16(0x057e), __builtin_bswap16(productId), 0x0110); + joycon->hid->hidInfo(0x00, 0x01); + + NimBLEDevice::setSecurityAuth(true, true, true); + +#if DEBUG_VERBOSE_LEVEL == 3 + dump.resetAddr(); + dump.print(tempHidReportDescriptor, hidReportDescriptorSize); +#endif + + joycon->hid->reportMap(tempHidReportDescriptor, hidReportDescriptorSize); + joycon->hid->startServices(); + + joycon->onStarted(server); + + NimBLEAdvertising* pAdvertising = server->getAdvertising(); + pAdvertising->setAppearance(HID_GAMEPAD); + pAdvertising->addServiceUUID(joycon->hid->hidService()->getUUID()); + pAdvertising->start(); + joycon->hid->setBatteryLevel(joycon->batteryLevel); + + debug_d("Advertising started!"); + + vTaskDelay(portMAX_DELAY); +} diff --git a/Sming/Libraries/SwitchJoycon/src/SwitchJoycon.h b/Sming/Libraries/SwitchJoycon/src/SwitchJoycon.h new file mode 100644 index 0000000000..86a2e63011 --- /dev/null +++ b/Sming/Libraries/SwitchJoycon/src/SwitchJoycon.h @@ -0,0 +1,154 @@ +#pragma once + +#include <Delegate.h> + +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include "nimconfig.h" +#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) + +#include "SwitchJoyconConnection.h" +#include <NimBLEHIDDevice.h> +#include <NimBLECharacteristic.h> + +class SwitchJoycon +{ +public: + enum class Type { + Left = 0, + Right, + ProController = 3, + }; + + enum class Button { + ButtonA = 0, + ButtonX, + ButtonB, + ButtonY, + ButtonSl, + ButtonSr, + + ButtonMunus = 8, + ButtonPlus, + + ButtonHome = 12, + ButtonCapture, + ButtonStickrl, + ButtonZrl, + }; + + enum class JoystickPosition { + Right = 0, + DownRight, + Down, + DownLeft, + Left, + UpLeft, + Up, + UpRight, + Center, + }; + + struct Gamepad // {00 00} 08 {00 80} {00 80} {00 80} {00 80} + { + uint8_t buttons[2]; + uint8_t hat; + uint8_t leftX[2]; + uint8_t leftY[2]; + uint8_t rightX[2]; + uint8_t rightY[2]; + }; + + SwitchJoycon(Type type, uint8_t batteryLevel = 100, SwitchJoyconConnection::Callback onConnected = nullptr, + SwitchJoyconConnection::Callback onDisconnected = nullptr) + : controllerType(type), batteryLevel(batteryLevel) + { + connectionStatus = new SwitchJoyconConnection(onConnected, onDisconnected); + } + + virtual ~SwitchJoycon() + { + end(); + } + + bool begin(); + + void end(); + + void setType(Type type) + { + controllerType = type; + } + + void setBatteryLevel(uint8_t level); + + // Buttons + + void press(Button button) + { + press(static_cast<uint8_t>(button)); + } + + void press(uint8_t button); + + void release(Button button) + { + release(static_cast<uint8_t>(button)); + } + + void release(uint8_t button); + + // Set Axis Values + + void setXAxis(int16_t value); + void setYAxis(int16_t value); + void setZAxis(int16_t value); + void setZAxisRotation(int16_t value); + + // Hat + + void setHat(JoystickPosition position); + + void setAutoReport(bool autoReport) + { + this->autoReport = autoReport; + } + + void sendReport(); + bool isPressed(uint8_t button); + bool isConnected(); + void resetButtons(); + +protected: + virtual void onStarted(NimBLEServer* pServer){}; + +private: + bool started{false}; + TaskHandle_t taskHandle{nullptr}; // owned + + // Joystick Type + Type controllerType; + + // Gamepad State + Gamepad state; + uint8_t batteryLevel{0}; + uint8_t buttonCount{0}; + uint8_t hatSwitchCount{0}; + + bool autoReport{true}; + size_t reportSize{0}; + + // HID Settings + NimBLEHIDDevice* hid{nullptr}; // owned + uint8_t hidReportId{0}; + + // Connection status and gamepad + SwitchJoyconConnection* connectionStatus{nullptr}; + NimBLECharacteristic* inputGamepad{nullptr}; // owned + + static void startServer(void* pvParameter); +}; + +#endif // CONFIG_BT_NIMBLE_ROLE_PERIPHERAL +#endif // CONFIG_BT_ENABLED diff --git a/Sming/Libraries/SwitchJoycon/src/SwitchJoyconConnection.cpp b/Sming/Libraries/SwitchJoycon/src/SwitchJoyconConnection.cpp new file mode 100644 index 0000000000..df2347e7d0 --- /dev/null +++ b/Sming/Libraries/SwitchJoycon/src/SwitchJoyconConnection.cpp @@ -0,0 +1,17 @@ +#include "SwitchJoyconConnection.h" + +void SwitchJoyconConnection::onConnect(NimBLEServer* server) +{ + connected = true; + if(connectCallback) { + connectCallback(*server); + } +} + +void SwitchJoyconConnection::onDisconnect(NimBLEServer* server) +{ + connected = false; + if(connectCallback) { + disconnectCallback(*server); + } +} diff --git a/Sming/Libraries/SwitchJoycon/src/SwitchJoyconConnection.h b/Sming/Libraries/SwitchJoycon/src/SwitchJoyconConnection.h new file mode 100644 index 0000000000..c96a29cead --- /dev/null +++ b/Sming/Libraries/SwitchJoycon/src/SwitchJoyconConnection.h @@ -0,0 +1,36 @@ +#ifndef ESP32_BLE_CONNECTION_STATUS_H +#define ESP32_BLE_CONNECTION_STATUS_H +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include "nimconfig.h" +#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) + +#include <NimBLEServer.h> +#include <NimBLECharacteristic.h> + +class SwitchJoyconConnection : public NimBLEServerCallbacks +{ +public: + using Callback = Delegate<void(NimBLEServer& server)>; + + bool connected{false}; + NimBLECharacteristic* inputGamepad{nullptr}; + + SwitchJoyconConnection(Callback onConnected = nullptr, Callback onDisconnected = nullptr) + { + connectCallback = onConnected; + disconnectCallback = onDisconnected; + } + + void onConnect(NimBLEServer* pServer); + void onDisconnect(NimBLEServer* pServer); + +private: + Callback connectCallback; + Callback disconnectCallback; +}; + +#endif // CONFIG_BT_NIMBLE_ROLE_PERIPHERAL +#endif // CONFIG_BT_ENABLED +#endif // ESP32_BLE_CONNECTION_STATUS_H