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."); } } }