diff --git a/.gitmodules b/.gitmodules index 7607e1ac02..c828c2366a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -377,8 +377,18 @@ # # Esp32 libraries # - -### NONE ### +[submodule "Libraries.ESP32-BLE-Gamepad"] + path = Sming/Libraries/BLEGamepad/ESP32-BLE-Gamepad + url = https://github.com/lemmingDev/ESP32-BLE-Gamepad.git + ignore = dirty +[submodule "Libraries.ESP32-BLE-Keyboard"] + path = Sming/Libraries/BLEKeyboard/ESP32-BLE-Keyboard + url = https://github.com/T-vK/ESP32-BLE-Keyboard.git + ignore = dirty +[submodule "Libraries.esp-nimble-cpp"] + path = Sming/Libraries/NimBLE/esp-nimble-cpp + url = https://github.com/h2zero/esp-nimble-cpp.git + ignore = dirty # diff --git a/Sming/Arch/Esp32/Components/esp32/component.mk b/Sming/Arch/Esp32/Components/esp32/component.mk index affffe905e..e568d0efa6 100644 --- a/Sming/Arch/Esp32/Components/esp32/component.mk +++ b/Sming/Arch/Esp32/Components/esp32/component.mk @@ -17,11 +17,22 @@ SDKCONFIG_H := $(SDK_BUILD_BASE)/config/sdkconfig.h SDK_LIBDIRS := \ esp_wifi/lib/$(ESP_VARIANT) \ - xtensa/$(ESP_VARIANT)/ \ - hal/$(ESP_VARIANT)/ \ + xtensa/$(ESP_VARIANT) \ + hal/$(ESP_VARIANT) \ $(ESP_VARIANT)/ld \ esp_rom/$(ESP_VARIANT)/ld +# BLUETOOTH +ifeq ($(ESP_VARIANT),esp32) +SDK_LIBDIRS += bt/controller/lib_esp32/$(ESP_VARIANT) +ENABLE_BLUETOOTH := 1 +else ifneq (,$(findstring $(ESP_VARIANT),esp32c3 esp32s3)) +SDK_LIBDIRS += bt/controller/lib_esp32c3_family/$(ESP_VARIANT) +ENABLE_BLUETOOTH := 1 +else +ENABLE_BLUETOOTH := 0 +endif + ESP32_COMPONENT_PATH := $(COMPONENT_PATH) SDK_DEFAULT_PATH := $(ESP32_COMPONENT_PATH)/sdk @@ -71,6 +82,33 @@ SDK_INCDIRS := \ lwip/include/apps/sntp \ wpa_supplicant/include/esp_supplicant +ifeq ($(ENABLE_BLUETOOTH),1) +SDK_INCDIRS += \ + bt/include/$(ESP_VARIANT)/include \ + bt/common/api/include/api \ + bt/common/btc/profile/esp/blufi/include \ + bt/common/btc/profile/esp/include \ + bt/common/osi/include \ + bt/host/nimble/nimble/nimble/include \ + bt/host/nimble/nimble/nimble/host/include \ + bt/host/nimble/nimble/porting/nimble/include \ + bt/host/nimble/nimble/porting/npl/freertos/include \ + bt/host/nimble/nimble/nimble/host/services/ans/include \ + bt/host/nimble/nimble/nimble/host/services/bas/include \ + bt/host/nimble/nimble/nimble/host/services/dis/include \ + bt/host/nimble/nimble/nimble/host/services/gap/include \ + bt/host/nimble/nimble/nimble/host/services/gatt/include \ + bt/host/nimble/nimble/nimble/host/services/ias/include \ + bt/host/nimble/nimble/nimble/host/services/ipss/include \ + bt/host/nimble/nimble/nimble/host/services/lls/include \ + bt/host/nimble/nimble/nimble/host/services/tps/include \ + bt/host/nimble/nimble/nimble/host/util/include \ + bt/host/nimble/nimble/nimble/host/store/ram/include \ + bt/host/nimble/nimble/nimble/host/store/config/include \ + bt/host/nimble/esp-hci/include \ + bt/host/nimble/port/include +endif + ifdef IDF_TARGET_ARCH_RISCV SDK_INCDIRS += \ freertos/port/riscv/include \ @@ -151,6 +189,13 @@ SDK_ESP_WIFI_LIBS := \ pp \ smartconfig +ifeq ($(ENABLE_BLUETOOTH),1) +SDK_ESP_BLUETOOTH_LIBS := bt btdm_app +ifneq (,$(filter $(ESP_VARIANT),esp32c3 esp32s3)) +SDK_ESP_BLUETOOTH_LIBS += btbb +endif +endif + ifeq ($(ESP_VARIANT),esp32) SDK_ESP_WIFI_LIBS += rtc endif @@ -167,6 +212,11 @@ EXTRA_LIBS := \ ifneq ($(DISABLE_WIFI),1) EXTRA_LIBS += $(SDK_ESP_WIFI_LIBS) + +ifeq ($(ENABLE_BLUETOOTH),1) +EXTRA_LIBS += $(SDK_ESP_BLUETOOTH_LIBS) +endif + endif LinkerScript = -T $(ESP_VARIANT).$1.ld diff --git a/Sming/Arch/Esp32/Components/esp32/sdk/config/common b/Sming/Arch/Esp32/Components/esp32/sdk/config/common index 6bb6d89c55..a4caeb16e7 100644 --- a/Sming/Arch/Esp32/Components/esp32/sdk/config/common +++ b/Sming/Arch/Esp32/Components/esp32/sdk/config/common @@ -23,6 +23,16 @@ CONFIG_ETH_USE_SPI_ETHERNET=y CONFIG_ETH_SPI_ETHERNET_W5500=y CONFIG_ETH_SPI_ETHERNET_DM9051=y +# Bluetooth +CONFIG_BT_ENABLED=y +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS=n +CONFIG_BLE_MESH=n +CONFIG_BT_NIMBLE_MESH=n + +CONFIG_ESP32_WIFI_SW_COEXIST_ENABLE=n + # Mandatory Sming framework changes CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=16384 CONFIG_ESP_TASK_WDT_TIMEOUT_S=8 diff --git a/Sming/Arch/Esp32/Components/esp_wifi/README.rst b/Sming/Arch/Esp32/Components/esp_wifi/README.rst index 1e6d05f976..2a401d40ff 100644 --- a/Sming/Arch/Esp32/Components/esp_wifi/README.rst +++ b/Sming/Arch/Esp32/Components/esp_wifi/README.rst @@ -1,5 +1,4 @@ -Esp8266 WiFi -============ +Esp32 WiFi +========== -All related libraries for WiFi support. Definitions are provided in the -Espressif Non-OS SDK. +All related libraries for WiFi support. diff --git a/Sming/Libraries/BLEGamepad/ESP32-BLE-Gamepad b/Sming/Libraries/BLEGamepad/ESP32-BLE-Gamepad new file mode 160000 index 0000000000..544dc65029 --- /dev/null +++ b/Sming/Libraries/BLEGamepad/ESP32-BLE-Gamepad @@ -0,0 +1 @@ +Subproject commit 544dc650296f4a4b9a22995b996458c2c66fa4e5 diff --git a/Sming/Libraries/BLEGamepad/README.rst b/Sming/Libraries/BLEGamepad/README.rst new file mode 100644 index 0000000000..c6a41f113f --- /dev/null +++ b/Sming/Libraries/BLEGamepad/README.rst @@ -0,0 +1,65 @@ +ESP32 BLE Gamepad +================= + +.. highlight:: c++ + +Introduction +------------ +This library allows you to make the ESP32 act as a Bluetooth gamepad and control what it does. +The library uses :library:`NimBLE` for faster and lighter communication. + +Features +-------- + +Using this library you can do the following: + + - Button press (128 buttons) + - Button release (128 buttons) + - Axes movement (6 axes (16 bit) (x, y, z, rZ, rX, rY) --> (Left Thumb X, Left Thumb Y, Right Thumb X, Right Thumb Y, Left Trigger, Right Trigger)) + - 2 Sliders (16 bit) (Slider 1 and Slider 2) + - 4 point of view hats (ie. d-pad plus 3 other hat switches) + - Simulation controls (rudder, throttle, accelerator, brake, steering) + - Configurable HID descriptor + - Report optional battery level to host (basically works, but it doesn't show up in Android's status bar) + - Customize Bluetooth device name/manufacturer + - Uses efficient NimBLE bluetooth library + - Compatible with Windows + - Compatible with Android (Android OS maps default buttons / axes / hats slightly differently than Windows) + - Compatible with Linux (limited testing) + - Compatible with MacOS X (limited testing) + +Using +----- + +1. Add ``COMPONENT_DEPENDS += BLEGamepad`` to your application componenent.mk file. +2. Add these lines to your application:: + + #include + + namespace + { + BleGamepad bleGamepad; + + // ... + + } // namespace + + void init() + { + // ... + + bleGamepad.begin(); + } + + +Notes +----- +By default, reports are sent on every button press/release or axis/slider/hat/simulation movement, however this can be disabled, +and then you manually call sendReport on the gamepad instance as shown in the IndividualAxes.ino example. + +There is also Bluetooth specific information that you can use (optional): + +Instead of ``BleGamepad bleGamepad;`` you can do ``BleGamepad bleGamepad("Bluetooth Device Name", "Bluetooth Device Manufacturer", 100);``. +The third parameter is the initial battery level of your device. +Adjusting the battery level later on doesn't work. +By default the battery level will be set to 100%, the device name will be `ESP32 BLE Gamepad` and the manufacturer will be `Espressif`. \ No newline at end of file diff --git a/Sming/Libraries/BLEGamepad/component.mk b/Sming/Libraries/BLEGamepad/component.mk new file mode 100644 index 0000000000..1bec8c0d31 --- /dev/null +++ b/Sming/Libraries/BLEGamepad/component.mk @@ -0,0 +1,8 @@ +COMPONENT_SUBMODULES := ESP32-BLE-Gamepad +COMPONENT_DEPENDS := NimBLE + +COMPONENT_SRCDIRS := ESP32-BLE-Gamepad +COMPONENT_INCDIRS := $(COMPONENT_SRCDIRS) + +COMPONENT_CPPFLAGS:= -DUSE_NIMBLE=1 +APP_CFLAGS += -DUSE_NIMBLE=1 diff --git a/Sming/Libraries/BLEGamepad/samples/Bluetooth_Gamepad/.cs b/Sming/Libraries/BLEGamepad/samples/Bluetooth_Gamepad/.cs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Sming/Libraries/BLEGamepad/samples/Bluetooth_Gamepad/Makefile b/Sming/Libraries/BLEGamepad/samples/Bluetooth_Gamepad/Makefile new file mode 100644 index 0000000000..ff51b6c3a7 --- /dev/null +++ b/Sming/Libraries/BLEGamepad/samples/Bluetooth_Gamepad/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/BLEGamepad/samples/Bluetooth_Gamepad/README.rst b/Sming/Libraries/BLEGamepad/samples/Bluetooth_Gamepad/README.rst new file mode 100644 index 0000000000..1572eddf96 --- /dev/null +++ b/Sming/Libraries/BLEGamepad/samples/Bluetooth_Gamepad/README.rst @@ -0,0 +1,32 @@ +Bluetooth Gamepad +================= + +Introduction +------------ +This sample turns the ESP32 into a Bluetooth LE gamepad that presses buttons and moves axis + +Possible buttons are: BUTTON_1 through to BUTTON_16 +(16 buttons supported by default. Library can be configured to support up to 128) + +Possible DPAD/HAT switch position values are: +DPAD_CENTERED, DPAD_UP, DPAD_UP_RIGHT, DPAD_RIGHT, DPAD_DOWN_RIGHT, DPAD_DOWN, DPAD_DOWN_LEFT, DPAD_LEFT, DPAD_UP_LEFT +(or HAT_CENTERED, HAT_UP etc) + +bleGamepad.setAxes takes the following int16_t parameters for the Left/Right Thumb X/Y, Left/Right Triggers plus slider1 and slider2, and hat switch position as above: +(Left Thumb X, Left Thumb Y, Right Thumb X, Right Thumb Y, Left Trigger, Right Trigger, Hat switch position ^ (1 hat switch (dpad) supported by default. Library can be configured to support up to 4) + +Library can also be configured to support up to 5 simulation controls (can be set with setSimulationControls) +(rudder, throttle, accelerator, brake, steering), but they are not enabled by default. + + +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 `__. + \ No newline at end of file diff --git a/Sming/Libraries/BLEGamepad/samples/Bluetooth_Gamepad/app/application.cpp b/Sming/Libraries/BLEGamepad/samples/Bluetooth_Gamepad/app/application.cpp new file mode 100644 index 0000000000..7e10dbbfd7 --- /dev/null +++ b/Sming/Libraries/BLEGamepad/samples/Bluetooth_Gamepad/app/application.cpp @@ -0,0 +1,42 @@ +#include +#include + +namespace +{ +BleGamepad bleGamepad; + +Timer procTimer; + +void loop() +{ + if(!bleGamepad.isConnected()) { + return; + } + + Serial.println("Press buttons 5 and 16. Move all enabled axes to max. Set DPAD (hat 1) to down right."); + bleGamepad.press(BUTTON_5); + bleGamepad.press(BUTTON_16); + bleGamepad.setAxes(32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, DPAD_DOWN_RIGHT); + // All axes, sliders, hats etc can also be set independently. See the IndividualAxes.ino example + delay(500); + + Serial.println("Release button 5. Move all axes to min. Set DPAD (hat 1) to centred."); + bleGamepad.release(BUTTON_5); + bleGamepad.setAxes(-32767, -32767, -32767, -32767, -32767, -32767, -32767, -32767, DPAD_CENTERED); + delay(500); +} + +} // namespace + +void init() +{ + Serial.begin(COM_SPEED_SERIAL); + Serial.println("Starting BLE Gamepad sample!"); + bleGamepad.begin(); + // The default bleGamepad.begin() above is the same as bleGamepad.begin(16, 1, true, true, true, true, true, true, true, true, false, false, false, false, false); + // which enables a gamepad with 16 buttons, 1 hat switch, enabled x, y, z, rZ, rX, rY, slider 1, slider 2 and disabled rudder, throttle, accelerator, brake, steering + // Auto reporting is enabled by default. + // Use bleGamepad.setAutoReport(false); to disable auto reporting, and then use bleGamepad.sendReport(); as needed + + procTimer.initializeMs(500, loop).start(); +} diff --git a/Sming/Libraries/BLEGamepad/samples/Bluetooth_Gamepad/component.mk b/Sming/Libraries/BLEGamepad/samples/Bluetooth_Gamepad/component.mk new file mode 100644 index 0000000000..c28c735b08 --- /dev/null +++ b/Sming/Libraries/BLEGamepad/samples/Bluetooth_Gamepad/component.mk @@ -0,0 +1 @@ +COMPONENT_DEPENDS := BLEGamepad diff --git a/Sming/Libraries/BLEKeyboard/ESP32-BLE-Keyboard b/Sming/Libraries/BLEKeyboard/ESP32-BLE-Keyboard new file mode 160000 index 0000000000..f8dd485211 --- /dev/null +++ b/Sming/Libraries/BLEKeyboard/ESP32-BLE-Keyboard @@ -0,0 +1 @@ +Subproject commit f8dd4852113a722a6b8dc8af987e94cf84d73ad5 diff --git a/Sming/Libraries/BLEKeyboard/ESP32-BLE-Keyboard.patch b/Sming/Libraries/BLEKeyboard/ESP32-BLE-Keyboard.patch new file mode 100644 index 0000000000..2f7cee53ce --- /dev/null +++ b/Sming/Libraries/BLEKeyboard/ESP32-BLE-Keyboard.patch @@ -0,0 +1,16 @@ +diff --git a/BleKeyboard.cpp b/BleKeyboard.cpp +index 0d043f4..3c2677c 100644 +--- a/BleKeyboard.cpp ++++ b/BleKeyboard.cpp +@@ -539,8 +539,8 @@ void BleKeyboard::delay_ms(uint64_t ms) { + if(ms){ + uint64_t e = (m + (ms * 1000)); + if(m > e){ //overflow +- while(esp_timer_get_time() > e) { } ++ while(uint64_t(esp_timer_get_time()) > e) { } + } +- while(esp_timer_get_time() < e) {} ++ while(uint64_t(esp_timer_get_time()) < e) {} + } + } +\ No newline at end of file diff --git a/Sming/Libraries/BLEKeyboard/README.rst b/Sming/Libraries/BLEKeyboard/README.rst new file mode 100644 index 0000000000..d27c3e5ced --- /dev/null +++ b/Sming/Libraries/BLEKeyboard/README.rst @@ -0,0 +1,81 @@ +ESP32 BLE Keyboard +================== + +.. highlight:: c++ + +Introduction +------------ +This library allows you to make the ESP32 act as a Bluetooth keyboard and control what it does. +The library uses :library:`NimBLE` for faster and lighter communication. + +Features +-------- + +Using this library you can do the following: + + - Send key strokes + - Send text + - Press/release individual keys + - Media keys are supported + - Set battery level (basically works, but doesn't show up in Android's status bar) + - Compatible with Android + - Compatible with Windows + - Compatible with Linux + - Compatible with MacOS X (not stable, some people have issues, doesn't work with old devices) + - Compatible with iOS (not stable, some people have issues, doesn't work with old devices) + +Using +----- + +1. Add ``COMPONENT_DEPENDS += BLEKeyboard`` to your application componenent.mk file. +2. Add these lines to your application:: + + #include + + + namespace + { + BleKeyboard bleKeyboard; + + // ... + + } // namespace + + void init() + { + // ... + + bleKeyboard.begin(); + } + + +API documentation +----------------- +The BleKeyboard interface is almost identical to the Keyboard Interface, so you can use documentation right here: +https://www.arduino.cc/reference/en/language/functions/usb/keyboard/ + +In addition to that you can send media keys (which is not possible with the USB keyboard library). Supported are the following: + + - KEY_MEDIA_NEXT_TRACK + - KEY_MEDIA_PREVIOUS_TRACK + - KEY_MEDIA_STOP + - KEY_MEDIA_PLAY_PAUSE + - KEY_MEDIA_MUTE + - KEY_MEDIA_VOLUME_UP + - KEY_MEDIA_VOLUME_DOWN + - KEY_MEDIA_WWW_HOME + - KEY_MEDIA_LOCAL_MACHINE_BROWSER // Opens "My Computer" on Windows + - KEY_MEDIA_CALCULATOR + - KEY_MEDIA_WWW_BOOKMARKS + - KEY_MEDIA_WWW_SEARCH + - KEY_MEDIA_WWW_STOP + - KEY_MEDIA_WWW_BACK + - KEY_MEDIA_CONSUMER_CONTROL_CONFIGURATION // Media Selection + - KEY_MEDIA_EMAIL_READER + +There is also Bluetooth specific information that you can set (optional): +Instead of ``BleKeyboard bleKeyboard;`` you can do ``BleKeyboard bleKeyboard("Bluetooth Device Name", "Bluetooth Device Manufacturer", 100);``. (Max length is 15 characters, anything beyond that will be truncated.) +The third parameter is the initial battery level of your device. To adjust the battery level later on you can simply call e.g. ``bleKeyboard.setBatteryLevel(50)`` (set battery level to 50%). +By default the battery level will be set to 100%, the device name will be `ESP32 Bluetooth Keyboard` and the manufacturer will be `Espressif`. +There is also a ``setDelay`` method to set a delay between each key event. E.g. ``bleKeyboard.setDelay(10)`` (10 milliseconds). The default is `8`. +This feature is meant to compensate for some applications and devices that can't handle fast input and will skip letters if too many keys are sent in a small time frame. diff --git a/Sming/Libraries/BLEKeyboard/component.mk b/Sming/Libraries/BLEKeyboard/component.mk new file mode 100644 index 0000000000..6016b6b22a --- /dev/null +++ b/Sming/Libraries/BLEKeyboard/component.mk @@ -0,0 +1,9 @@ +COMPONENT_SUBMODULES := ESP32-BLE-Keyboard + +COMPONENT_DEPENDS := NimBLE + +COMPONENT_SRCDIRS := ESP32-BLE-Keyboard +COMPONENT_INCDIRS := $(COMPONENT_SRCDIRS) + +COMPONENT_CPPFLAGS:= -DUSE_NIMBLE=1 +APP_CFLAGS += -DUSE_NIMBLE=1 diff --git a/Sming/Libraries/BLEKeyboard/samples/Bluetooth_Keyboard/.cs b/Sming/Libraries/BLEKeyboard/samples/Bluetooth_Keyboard/.cs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Sming/Libraries/BLEKeyboard/samples/Bluetooth_Keyboard/Makefile b/Sming/Libraries/BLEKeyboard/samples/Bluetooth_Keyboard/Makefile new file mode 100644 index 0000000000..ff51b6c3a7 --- /dev/null +++ b/Sming/Libraries/BLEKeyboard/samples/Bluetooth_Keyboard/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/BLEKeyboard/samples/Bluetooth_Keyboard/README.rst b/Sming/Libraries/BLEKeyboard/samples/Bluetooth_Keyboard/README.rst new file mode 100644 index 0000000000..4d01eb7a89 --- /dev/null +++ b/Sming/Libraries/BLEKeyboard/samples/Bluetooth_Keyboard/README.rst @@ -0,0 +1,13 @@ +Bluetooth Keyboard +================== + +This sample demonstrates how to turn an Esp32 device into external keyboard. +The "keyboard" and your PC will be communicating using Bluetooth Low Energy (BLE). +The "keyboard" will write words, press Enter, press a media key and, if enabled in the sample code, Ctrl+Alt+Delete. + +Usage +----- +Once this sample is flashed and running on your ESP32 you can test it. +Open a new text editor on your PC. Then search from your PC for new bluetooth devices. +A device named "Sming BLE Keyboard" should show up. Connect to it and focus/open you text editor window. +Be fast. Soon enough a "Hello World" text will start to be "magically" typed inside your text editor. \ No newline at end of file diff --git a/Sming/Libraries/BLEKeyboard/samples/Bluetooth_Keyboard/app/application.cpp b/Sming/Libraries/BLEKeyboard/samples/Bluetooth_Keyboard/app/application.cpp new file mode 100644 index 0000000000..bc6d959e34 --- /dev/null +++ b/Sming/Libraries/BLEKeyboard/samples/Bluetooth_Keyboard/app/application.cpp @@ -0,0 +1,48 @@ +#include +#include + +namespace +{ +BleKeyboard bleKeyboard("Sming BLE Keyboard"); +Timer procTimer; + +void loop() +{ + if(!bleKeyboard.isConnected()) { + return; + } + + Serial.println("Sending 'Hello world'..."); + bleKeyboard.print("Hello world"); + + Serial.println("Sending Enter key..."); + bleKeyboard.write(KEY_RETURN); + + delay(1000); + + Serial.println("Sending Play/Pause media key..."); + bleKeyboard.write(KEY_MEDIA_PLAY_PAUSE); + + // + // Below is an example of pressing multiple keyboard modifiers + // which by default is commented out. + /* + Serial.println("Sending Ctrl+Alt+Delete..."); + bleKeyboard.press(KEY_LEFT_CTRL); + bleKeyboard.press(KEY_LEFT_ALT); + bleKeyboard.press(KEY_DELETE); + delay(100); + bleKeyboard.releaseAll(); + */ +} + +} // namespace + +void init() +{ + Serial.begin(COM_SPEED_SERIAL); + Serial.println("Starting BLE Keyboard sample!"); + bleKeyboard.begin(); + + procTimer.initializeMs(1000, loop).start(); +} diff --git a/Sming/Libraries/BLEKeyboard/samples/Bluetooth_Keyboard/component.mk b/Sming/Libraries/BLEKeyboard/samples/Bluetooth_Keyboard/component.mk new file mode 100644 index 0000000000..0140a4bec6 --- /dev/null +++ b/Sming/Libraries/BLEKeyboard/samples/Bluetooth_Keyboard/component.mk @@ -0,0 +1 @@ +COMPONENT_DEPENDS := BLEKeyboard diff --git a/Sming/Libraries/NimBLE/README.rst b/Sming/Libraries/NimBLE/README.rst new file mode 100644 index 0000000000..0313349fb2 --- /dev/null +++ b/Sming/Libraries/NimBLE/README.rst @@ -0,0 +1,24 @@ +ESP32 NimBLE +============ + +.. highlight:: c++ + +Introduction +------------ +NimBLE is a completely open source Bluetooth Low Energy (BLE) stack produced by `Apache `_. +It is more suited to resource constrained devices than bluedroid and has now been ported to the ESP32 by Espressif. + +Using +----- + +1. Add ``COMPONENT_DEPENDS += NimBLE`` to your application componenent.mk file. +2. Add these lines to your application:: + + #include + + void init() + { + // ... + + BLEDevice::init(""); + } diff --git a/Sming/Libraries/NimBLE/component.mk b/Sming/Libraries/NimBLE/component.mk new file mode 100644 index 0000000000..dd9d540ce3 --- /dev/null +++ b/Sming/Libraries/NimBLE/component.mk @@ -0,0 +1,6 @@ +COMPONENT_SUBMODULES := esp-nimble-cpp + +COMPONENT_SOC := esp32 esp32c3 esp32s3 + +COMPONENT_SRCDIRS := esp-nimble-cpp/src +COMPONENT_INCDIRS := $(COMPONENT_SRCDIRS) diff --git a/Sming/Libraries/NimBLE/esp-nimble-cpp b/Sming/Libraries/NimBLE/esp-nimble-cpp new file mode 160000 index 0000000000..9e5db157f8 --- /dev/null +++ b/Sming/Libraries/NimBLE/esp-nimble-cpp @@ -0,0 +1 @@ +Subproject commit 9e5db157f88444bb95415e08a92c7eb817ff533d 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 + + 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 `__ 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 `__ +- `HID constants `__ diff --git a/Sming/Libraries/SwitchJoycon/component.mk b/Sming/Libraries/SwitchJoycon/component.mk new file mode 100644 index 0000000000..e07e0f70a7 --- /dev/null +++ b/Sming/Libraries/SwitchJoycon/component.mk @@ -0,0 +1,4 @@ +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 `__. \ 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 +#include + +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 +#include +#include +#include +#include +#include + +#if DEBUG_VERBOSE_LEVEL == 3 +#include +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(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(&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(position)) { + return; + } + + state.hat = static_cast(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(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 + +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include "nimconfig.h" +#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) + +#include "SwitchJoyconConnection.h" +#include +#include + +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(button)); + } + + void press(uint8_t button); + + void release(Button button) + { + release(static_cast(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..ffaaef0aa1 --- /dev/null +++ b/Sming/Libraries/SwitchJoycon/src/SwitchJoyconConnection.h @@ -0,0 +1,35 @@ +#pragma once + +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include "nimconfig.h" +#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) + +#include +#include + +class SwitchJoyconConnection : public NimBLEServerCallbacks +{ +public: + using Callback = Delegate; + + 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