From c2cecd9b3cf8b84c52ad03c309e8ef9487083935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerard=20Gasc=C3=B3n?= <52170489+GerardGascon@users.noreply.github.com> Date: Thu, 6 Jun 2024 17:24:59 +0200 Subject: [PATCH] feat: run joycon.net sample --- .../.idea/git_toolbox_prj.xml | 15 ++ Program.cs | 17 +- SwitchSlidePresenter.csproj | 4 + Tutorial.cs | 184 ++++++++++++++++++ 4 files changed, 216 insertions(+), 4 deletions(-) create mode 100644 .idea/.idea.SwitchSlidePresenter/.idea/git_toolbox_prj.xml create mode 100644 Tutorial.cs diff --git a/.idea/.idea.SwitchSlidePresenter/.idea/git_toolbox_prj.xml b/.idea/.idea.SwitchSlidePresenter/.idea/git_toolbox_prj.xml new file mode 100644 index 0000000..02b915b --- /dev/null +++ b/.idea/.idea.SwitchSlidePresenter/.idea/git_toolbox_prj.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/Program.cs b/Program.cs index d8598d8..7795ee7 100644 --- a/Program.cs +++ b/Program.cs @@ -1,7 +1,16 @@ -namespace SwitchSlidePresenter; +using HidSharp; +using System.Text; +using wtf.cluster.JoyCon; +using wtf.cluster.JoyCon.Calibration; +using wtf.cluster.JoyCon.ExtraData; +using wtf.cluster.JoyCon.HomeLed; +using wtf.cluster.JoyCon.InputReports; +using wtf.cluster.JoyCon.Rumble; -class Program { - static void Main(string[] args) { - Console.WriteLine("Hello, World!"); +namespace SwitchSlidePresenter { + class Program { + static async Task Main(string[] args) { + await Tutorial.Execute(); + } } } \ No newline at end of file diff --git a/SwitchSlidePresenter.csproj b/SwitchSlidePresenter.csproj index 2f4fc77..21b392a 100644 --- a/SwitchSlidePresenter.csproj +++ b/SwitchSlidePresenter.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/Tutorial.cs b/Tutorial.cs new file mode 100644 index 0000000..d7dd9e2 --- /dev/null +++ b/Tutorial.cs @@ -0,0 +1,184 @@ +using HidSharp; +using System.Text; +using wtf.cluster.JoyCon; +using wtf.cluster.JoyCon.Calibration; +using wtf.cluster.JoyCon.ExtraData; +using wtf.cluster.JoyCon.HomeLed; +using wtf.cluster.JoyCon.InputReports; +using wtf.cluster.JoyCon.Rumble; + +namespace SwitchSlidePresenter { + public class Tutorial { + public static async Task Execute() { + Console.OutputEncoding = Encoding.UTF8; + + HidDevice? device = null; + // Get a list of all HID devices + DeviceList list = DeviceList.Local; + if (OperatingSystem.IsWindows()) { + // Get all devices developed by Nintendo by vendor ID + var nintendos = list.GetHidDevices(0x057e); + // Get the first Nintendo controller + device = nintendos.FirstOrDefault(); + // It works fine for Windows, but... + } else { + // Linux has more complicated HID device management, we can't get device's vendor ID, + // so let's filter devices by their report descriptor + // It should work in Windows too, so it's more multiplatform solution + var hidDevices = list.GetHidDevices(); + foreach (var d in hidDevices) { + var rd = d.GetReportDescriptor(); + if (rd != null) { + if ( + rd.OutputReports.Count() == 4 + && rd.OutputReports.Count(r => r.ReportID == 0x01) == 1 + && rd.OutputReports.Count(r => r.ReportID == 0x10) == 1 + && rd.OutputReports.Count(r => r.ReportID == 0x11) == 1 + && rd.OutputReports.Count(r => r.ReportID == 0x12) == 1 + && rd.InputReports.Count() == 6 + && rd.InputReports.Count(r => r.ReportID == 0x21) == 1 + && rd.InputReports.Count(r => r.ReportID == 0x30) == 1 + && rd.InputReports.Count(r => r.ReportID == 0x31) == 1 + && rd.InputReports.Count(r => r.ReportID == 0x32) == 1 + && rd.InputReports.Count(r => r.ReportID == 0x33) == 1 + && rd.InputReports.Count(r => r.ReportID == 0x3F) == 1 + ) { + device = d; + break; + } + } + } + } + if (device == null) { + Console.WriteLine("No controller. Please connect Joy-Con or Pro controller via Bluetooth."); + return; + } + // Create a new JoyCon instance based on the HID device + var joycon = new JoyCon(device); + // Start controller polling + joycon.Start(); + + // First of all, we need to set format of the input reports, + // most of the time you will need to use InputReportMode.Full mode + await joycon.SetInputReportModeAsync(JoyCon.InputReportType.Full); + + // Get some information about the controller + DeviceInfo deviceInfo = await joycon.GetDeviceInfoAsync(); + Console.WriteLine( + $"Type: {deviceInfo.ControllerType}, Firmware: {deviceInfo.FirmwareVersionMajor}.{deviceInfo.FirmwareVersionMinor}"); + var serial = await joycon.GetSerialNumberAsync(); + Console.WriteLine($"Serial number: {serial ?? ""}"); + ControllerColors? colors = await joycon.GetColorsAsync(); + if (colors != null) { + Console.WriteLine($"Body color: {colors.BodyColor}, buttons color: {colors.ButtonsColor}"); + } else { + Console.WriteLine("Colors not specified, seems like the controller is grey."); + } + + // Enable IMU (accelerometer and gyroscope) + await joycon.EnableImuAsync(true); + // Enable rumble feature (it's enabled by default, actually) + await joycon.EnableRumbleAsync(true); + // You can control LEDs on the controller + await joycon.SetPlayerLedsAsync(JoyCon.LedState.Off, JoyCon.LedState.On, JoyCon.LedState.Off, + JoyCon.LedState.Blinking); + + // Get factory calibration data + CalibrationData facCal = await joycon.GetFactoryCalibrationAsync(); + // Get user calibration data + CalibrationData userCal = await joycon.GetUserCalibrationAsync(); + // Combine both calibrations, e.g. user calibration has higher priority + CalibrationData calibration = facCal + userCal; + // Get some parameters for the sticks + StickParametersSet sticksParameters = await joycon.GetStickParametersAsync(); + + // Home LED dimming pattern demo. Useless, but fun. + await joycon.SetHomeLedDimmingPatternAsync(new HomeLedDimmingPattern { + StepDurationBase = 4, // base duration is 40ms + StartLedBrightness = 0, // 0%, off + FullCyclesNumber = 0, // infinite + HomeLedDimmingSteps = { + new HomeLedDimmingStep { + LedBrightness = 0x0F, // 100% + TransitionDuration = 2, // base * 2 = 40ms * 2 = 80ms + PauseDuration = 4, // base * 4 = 40ms * 4 = 160ms + }, + new HomeLedDimmingStep { + LedBrightness = 0x00, // LED off + TransitionDuration = 2, // base * 2 = 40ms * 2 = 80ms + PauseDuration = 4, // base * 4 = 40ms * 4 = 160ms + }, + new HomeLedDimmingStep { LedBrightness = 0x0F, TransitionDuration = 2, PauseDuration = 4 }, + new HomeLedDimmingStep { LedBrightness = 0x00, TransitionDuration = 2, PauseDuration = 4 }, + new HomeLedDimmingStep { LedBrightness = 0x0F, TransitionDuration = 2, PauseDuration = 4 }, + new HomeLedDimmingStep { LedBrightness = 0x00, TransitionDuration = 2, PauseDuration = 4 }, + new HomeLedDimmingStep { + LedBrightness = 0x08, // 50% + TransitionDuration = 8, // base * 8 = 40ms * 8 = 320ms + PauseDuration = 4, // base * 8 = 40ms * 8 = 320ms + }, + new HomeLedDimmingStep { + LedBrightness = 0x00, // LED off + TransitionDuration = 8, // base * 8 = 40ms * 8 = 320ms + PauseDuration = 15, // base * 15 = 40ms * 15 = 600ms + } + }, + }); + + // Save the current cursor position + (var cX, var cY) = (Console.CursorLeft, Console.CursorTop); + + // Subscribe to the input reports + joycon.ReportReceived += (sender, input) => { + Console.SetCursorPosition(cX, cY); + // Check the type of the input report, most likely it will be InputWithImu + if (input is InputFullWithImu j) { + // Some base data from the controller + Console.WriteLine( + $"LeftStick: ({j.LeftStick}), RightStick: ({j.RightStick}), Buttons: ({j.Buttons}), Battery: {j.Battery}, Charging: {j.Charging} "); + // But we need calibrated data + StickPositionCalibrated leftStickCalibrated = + j.LeftStick.GetCalibrated(calibration.LeftStickCalibration!, + sticksParameters.LeftStickParameters.DeadZone); + StickPositionCalibrated rightStickCalibrated = + j.RightStick.GetCalibrated(calibration.RightStickCalibration!, + sticksParameters.RightStickParameters.DeadZone); + Console.WriteLine( + $"Calibrated LeftStick: (({leftStickCalibrated}), RightStick: ({rightStickCalibrated})) "); + // And accelerometer and gyroscope data + ImuFrameCalibrated calibratedImu = j.Imu.Frames[0].GetCalibrated(calibration.ImuCalibration!); + Console.WriteLine($"Calibrated IMU: ({calibratedImu}) "); + } else { + Console.WriteLine($"Invalid input report type: {input.GetType()}"); + } + return Task.CompletedTask; + }; + + // Error handling + joycon.StoppedOnError += new JoyCon.StoppedOnErrorHandler((_, ex) => { + Console.WriteLine(); + Console.WriteLine($"Critical error: {ex.Message}"); + Console.WriteLine("Controller polling stopped."); + Environment.Exit(1); + return Task.CompletedTask; + }); + + // Periodically send rumble command to the controller + var rumbleTimer = new Timer(async _ => { + try { + await joycon.WriteRumble(new RumbleSet(new RumbleData(40.9, 1), new RumbleData(65, 1))); + } + catch (Exception e) { + Console.WriteLine($"Rumble error: {e.Message}"); + } + }, null, 0, 1000); + + Console.ReadKey(); + await rumbleTimer.DisposeAsync(); + joycon.Stop(); + + Console.WriteLine(); + Console.WriteLine("Stopped."); + } + } +} \ No newline at end of file