diff --git a/.idea/dictionaries/Murakumo.xml b/.idea/dictionaries/Murakumo.xml
index 7a471bb..be05d54 100644
--- a/.idea/dictionaries/Murakumo.xml
+++ b/.idea/dictionaries/Murakumo.xml
@@ -3,6 +3,7 @@
hidapi
joycon
+ procon
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index c707d92..c918ed6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "joycon-rs"
-version = "0.2.0"
+version = "0.3.0"
authors = ["Kaisei Yokoyama "]
edition = "2018"
description = " a framework for dealing with Nintendo Switch Joy-Con on Rust easily and efficiently"
diff --git a/README.md b/README.md
index daa75cd..11687d0 100644
--- a/README.md
+++ b/README.md
@@ -121,6 +121,58 @@ managed_devices.into_iter()
.unwrap();
```
+### Rumble
+```no_run
+use joycon_rs::prelude::*;
+use std::convert::TryInto;
+use std::ops::Deref;
+use joycon_rs::joycon::joycon_features::JoyConFeature;
+
+fn main() -> JoyConResult<()> {
+ // First, connect your Joy-Cons to your computer!
+
+ let manager = JoyConManager::new()?;
+ let (managed_devices, new_devices) = {
+ let lock = manager.lock();
+ match lock {
+ Ok(manager) =>
+ (manager.managed_devices(), manager.new_devices()),
+ Err(_) => unreachable!(),
+ }
+ };
+
+ managed_devices.into_iter()
+ .chain(new_devices)
+ .inspect(|d| {
+ let lock = d.lock();
+ let device = match lock {
+ Ok(device) => device,
+ Err(e) => e.into_inner(),
+ };
+ let hid_device: JoyConResult<&HidDevice> = device.deref().try_into();
+ if let Ok(hid_device) = hid_device {
+ println!("{:?}", hid_device.get_product_string())
+ }
+ })
+ .try_for_each::<_, JoyConResult<()>>(|d| {
+ let mut driver = SimpleJoyConDriver::new(&d)?;
+
+ driver.enable_feature(JoyConFeature::Vibration)?;
+
+ // let rumble = Rumble::new(80.0,0.2);
+ let rumble = Rumble::new(300.0,0.9);
+ driver.rumble((Some(rumble), Some(rumble)))?;
+
+ Ok(())
+ })?;
+
+ Ok(())
+}
+```
+
+### More Examples
+[Here](examples).
+
# Features
You can use `Joycon-rs` for...
- Manage Joy-Cons
diff --git a/examples/home_light.rs b/examples/home_light.rs
new file mode 100644
index 0000000..3d7d0e0
--- /dev/null
+++ b/examples/home_light.rs
@@ -0,0 +1,48 @@
+#![allow(unused_must_use)]
+
+use joycon_rs::prelude::{*, lights::*};
+use std::convert::TryInto;
+use std::ops::Deref;
+use joycon_rs::joycon::lights::home_button::LightEmittingPattern;
+
+fn main() -> JoyConResult<()> {
+ // First, connect your Joy-Cons to your computer!
+
+ let manager =
+ JoyConManager::new()?;
+ let (managed_devices, new_devices) = {
+ let lock = manager.lock();
+ match lock {
+ Ok(manager) =>
+ (manager.managed_devices(), manager.new_devices()),
+ Err(_) => unreachable!(),
+ }
+ };
+
+ managed_devices.into_iter()
+ .chain(new_devices)
+ .inspect(|d| {
+ let lock = d.lock();
+ let device = match lock {
+ Ok(device) => device,
+ Err(e) => e.into_inner(),
+ };
+ let hid_device: JoyConResult<&HidDevice> = device.deref().try_into();
+ if let Ok(hid_device) = hid_device {
+ println!("{:?}", hid_device.get_product_string())
+ }
+ })
+ .try_for_each::<_, JoyConResult<()>>(|d| {
+ let mut driver = SimpleJoyConDriver::new(&d)?;
+
+ let pattern =
+ LightEmittingPattern::new(100, 0, 0u8.into())
+ .add_phase(100,500,0)
+ .add_phase(0,500,0);
+ driver.set_home_light(&pattern);
+
+ Ok(())
+ })?;
+
+ Ok(())
+}
\ No newline at end of file
diff --git a/src/joycon/device.rs b/src/joycon/device.rs
index 4d8cfd9..a94416f 100644
--- a/src/joycon/device.rs
+++ b/src/joycon/device.rs
@@ -1,67 +1,101 @@
use super::*;
use std::convert::TryInto;
-
-pub fn is_joycon(device_info: &DeviceInfo) -> JoyConResult<()> {
- if device_info.vendor_id() != JoyConDevice::VENDOR_ID {
- Err(JoyConDeviceError::InvalidVendorID(device_info.vendor_id()))?;
- }
-
- match device_info.product_id() {
- JoyConDevice::PRODUCT_ID_JOYCON_L | JoyConDevice::PRODUCT_ID_JOYCON_R => Ok(()),
- other => Err(JoyConDeviceError::InvalidProductID(other))?,
- }
+#[derive(Debug, Clone, Hash, Eq, PartialEq)]
+pub enum JoyConDeviceType {
+ JoyConL = 0,
+ JoyConR = 1,
+ ProCon = 2,
}
-pub enum JoyConDevice {
- Connected(HidDevice),
- Disconnected,
+pub struct JoyConDevice {
+ hid_device: Option,
+ serial_number: String,
+ device_type: JoyConDeviceType,
}
impl JoyConDevice {
pub const VENDOR_ID: u16 = 1406;
pub const PRODUCT_ID_JOYCON_L: u16 = 8198;
pub const PRODUCT_ID_JOYCON_R: u16 = 8199;
+ pub const PRODUCT_ID_PROCON: u16 = 8201;
+
+ pub fn check_type_of_device(device_info: &DeviceInfo) -> JoyConResult {
+ if device_info.vendor_id() != JoyConDevice::VENDOR_ID {
+ Err(JoyConDeviceError::InvalidVendorID(device_info.vendor_id()))?;
+ }
+
+ match device_info.product_id() {
+ JoyConDevice::PRODUCT_ID_JOYCON_L => Ok(JoyConDeviceType::JoyConL),
+ JoyConDevice::PRODUCT_ID_JOYCON_R => Ok(JoyConDeviceType::JoyConR),
+ JoyConDevice::PRODUCT_ID_PROCON => Ok(JoyConDeviceType::ProCon),
+ other => Err(JoyConDeviceError::InvalidProductID(other))?,
+ }
+ }
+
+ pub fn is_connected(&self) -> bool {
+ self.hid_device.is_some()
+ }
+
+ pub fn serial_number(&self) -> &str {
+ &self.serial_number
+ }
+
+ pub fn device_type(&self) -> JoyConDeviceType {
+ self.device_type.clone()
+ }
+
+ pub fn reset_device(&mut self, hid_device: HidDevice) {
+ self.hid_device = Some(hid_device);
+ }
+
+ pub fn forget_device(&mut self) {
+ self.hid_device = None;
+ }
pub fn new(device_info: &DeviceInfo, hidapi: &HidApi) -> JoyConResult {
- is_joycon(device_info)?;
+ let device_type = Self::check_type_of_device(device_info)?;
let serial = device_info.serial_number().unwrap_or("");
- let device = hidapi.open_serial(device_info.vendor_id(),
- device_info.product_id(),
- serial)?;
- // let device = device_info.open_device(&hidapi)?;
- Ok(JoyConDevice::Connected(device))
+ let hid_device = hidapi.open_serial(device_info.vendor_id(),
+ device_info.product_id(),
+ serial)?;
+
+ Ok(
+ JoyConDevice {
+ hid_device: Some(hid_device),
+ serial_number: serial.to_string(),
+ device_type,
+ }
+ )
}
pub fn write(&self, data: &[u8]) -> JoyConResult {
- match &self {
- JoyConDevice::Connected(hid_device) => Ok(hid_device.write(data)?),
- JoyConDevice::Disconnected => Err(JoyConError::Disconnected),
+ if let Some(hid_device) = &self.hid_device {
+ Ok(hid_device.write(data)?)
+ } else {
+ Err(JoyConError::Disconnected)
}
}
pub fn read(&self, buf: &mut [u8]) -> JoyConResult {
- match &self {
- JoyConDevice::Connected(hid_device) => Ok(hid_device.read(buf)?),
- JoyConDevice::Disconnected => Err(JoyConError::Disconnected),
- }
- }
-
- pub fn is_connected(&self) -> bool {
- match &self {
- JoyConDevice::Connected(_) => true,
- JoyConDevice::Disconnected => false,
+ if let Some(hid_device) = &self.hid_device {
+ Ok(hid_device.read(buf)?)
+ } else {
+ Err(JoyConError::Disconnected)
}
}
}
impl Debug for JoyConDevice {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- writeln!(f, "{}", match &self {
- JoyConDevice::Connected(_) => "Connected",
- JoyConDevice::Disconnected => "Disconnected",
- })
+ writeln!(f, "JoyConDevice {{ hid_device: {}, serial_number: {}, device_type: {:?} }}",
+ if self.is_connected() {
+ "Connected"
+ } else { "Disconnected" },
+ &self.serial_number,
+ &self.device_type
+ )
}
}
@@ -69,9 +103,7 @@ impl<'a> TryInto<&'a HidDevice> for &'a JoyConDevice {
type Error = JoyConError;
fn try_into(self) -> Result<&'a HidDevice, Self::Error> {
- match self {
- JoyConDevice::Connected(d) => Ok(d),
- JoyConDevice::Disconnected => Err(JoyConError::Disconnected),
- }
+ self.hid_device.as_ref()
+ .ok_or(JoyConError::Disconnected)
}
}
diff --git a/src/joycon/driver.rs b/src/joycon/driver.rs
index cd1aa31..dcc22dd 100644
--- a/src/joycon/driver.rs
+++ b/src/joycon/driver.rs
@@ -1668,6 +1668,205 @@ pub mod lights {
const ARGS: Self::ArgsType = [];
}
+ pub mod home_button {
+ use super::*;
+
+ #[allow(non_camel_case_types)]
+ #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
+ pub struct u4(u8);
+
+ impl u4 {
+ const MAX: Self = u4(15);
+ }
+
+ impl From for u4 {
+ fn from(v: u8) -> Self {
+ let value = u4(v);
+
+ if value > Self::MAX {
+ Self::MAX
+ } else { value }
+ }
+ }
+
+ impl Into for u4 {
+ fn into(self) -> u8 {
+ self.0
+ }
+ }
+
+ /// Element of HOME light emitting pattern.
+ /// The LED Duration Multiplier and the Fading Multiplier use the same algorithm:
+ /// Global Mini Cycle Duration ms * Multiplier value.
+ ///
+ /// Example: GMCD is set to xF = 175ms and LED Duration Multiplier is set to x4.
+ /// The Duration that the LED will stay on it's configured intensity is then 175 * 4 = 700ms.
+ #[derive(Clone, Debug, Hash, Eq, PartialEq)]
+ pub struct LightEmittingPhase {
+ /// LED intensity. x0 -> 0%, xF -> 100%
+ pub led_intensity: u4,
+ /// Fading Transition Duration. Value is a Multiplier of Global Mini Cycle Duration.
+ pub fading_transition_duration: u4,
+ /// LED Duration Multiplier
+ pub led_duration: u4,
+ }
+
+ /// HOME light emitting pattern.
+ #[derive(Clone, Debug, Hash, Eq, PartialEq)]
+ pub struct LightEmittingPattern {
+ phases_len: Option,
+ phases: Vec,
+ /// Global Mini Cycle Duration. 8ms - 175ms. Value x0 = 0ms/OFF
+ global_mini_cycle_duration: u4,
+ /// LED Start Intensity. Value x0=0% - xF=100%
+ led_start_intensity: u4,
+ repeat_count: u4,
+ }
+
+ impl LightEmittingPattern {
+ /// Constructor of `LightEmittingPattern`.
+ ///
+ /// * global_mini_cycle_duration (*ms*) - 0 <= global_mini_cycle_duration <= 175
+ /// * led_start_intensity (*%*) - 0 <= led_start_intensity <= 100
+ /// * repeat_count - 0 <= repeat_count <= 15: Value `0` is repeat forever.
+ pub fn new(global_mini_cycle_duration: u8, led_start_intensity: u8, repeat_count: u4) -> Self {
+ let global_mini_cycle_duration = if global_mini_cycle_duration == 0 {
+ 0.into()
+ } else {
+ ((global_mini_cycle_duration - 7) / 12 + 1).into()
+ };
+
+ let led_start_intensity = {
+ let saturated = if 100 < led_start_intensity { 100 } else { led_start_intensity } as f32;
+ ((saturated / 6.25) as u8).into()
+ };
+
+ LightEmittingPattern {
+ phases_len: None,
+ phases: Vec::with_capacity(15),
+ global_mini_cycle_duration,
+ led_start_intensity,
+ repeat_count,
+ }
+ }
+
+ pub fn push_phase(&mut self, phase: LightEmittingPhase) {
+ self.phases.push(phase);
+ }
+
+ /// Add emitting phase to pattern.
+ ///
+ /// * led_intensity (*%*) - 0 <= led_intensity <= 100
+ /// * fading_transition_duration (*ms*) - 0 < fading_transition_duration < self.global_mini_cycle_duration (ms) * 15
+ /// * led_duration (*ms*) - 0 < fading_transition_duration < self.global_mini_cycle_duration (ms) * 15
+ pub fn add_phase(mut self, led_intensity: u8, fading_transition_duration: u16, led_duration: u16) -> Self {
+ let led_intensity = {
+ let saturated = if 100 < led_intensity { 100 } else { led_intensity } as f32;
+ ((saturated / 6.25) as u8).into()
+ };
+ let fading_transition_duration: u4 = {
+ let gmcd: u8 = self.global_mini_cycle_duration.into();
+ (fading_transition_duration / gmcd as u16) as u8
+ }.into();
+ let led_duration = {
+ let gmcd: u8 = self.global_mini_cycle_duration.into();
+ (led_duration / gmcd as u16) as u8
+ }.into();
+
+ let phase = LightEmittingPhase {
+ led_intensity,
+ fading_transition_duration,
+ led_duration,
+ };
+
+ self.push_phase(phase);
+
+ self
+ }
+
+ /// Does the 1st phase and then the LED stays on with LED Start Intensity.
+ pub fn emit_first_phase(&self) -> Self {
+ let mut pattern = self.clone();
+ pattern.phases_len = Some(0u8.into());
+ pattern.repeat_count = 0u8.into();
+
+ pattern
+ }
+ }
+
+ impl Into<[u8; 25]> for LightEmittingPattern {
+ fn into(self) -> [u8; 25] {
+ fn nibbles_to_u8(high: u4, low: u4) -> u8 {
+ let high = {
+ let high :u8 = high.into();
+ (high & 0x0F) << 4
+ };
+ let low = {
+ let low: u8 = low.into();
+ low & 0x0F
+ };
+
+ high | low
+ }
+
+ let mut buf = [0u8; 25];
+
+ let number_of_phases =
+ if let Some(p) = self.phases_len {
+ p
+ } else {
+ (self.phases.len() as u8).into()
+ };
+ buf[0] = nibbles_to_u8(number_of_phases, self.global_mini_cycle_duration);
+
+ buf[1] = nibbles_to_u8(self.led_start_intensity, self.repeat_count);
+
+ let mut even_phases = self.phases.iter()
+ .take(15)
+ .enumerate()
+ .filter(|(idx, _)| idx % 2 == 0)
+ .map(|e| e.1);
+ let mut odd_phases = self.phases.iter()
+ .take(15)
+ .enumerate()
+ .filter(|(idx, _)| idx % 2 == 1)
+ .map(|e| e.1);
+
+
+ let mut buf_index = 2;
+ while let (Some(even), odd) = (even_phases.next(), odd_phases.next()) {
+ // LED intensities
+ {
+ let even_led_intensity = even.led_intensity;
+ let odd_led_intensity = odd.map(|odd| odd.led_intensity)
+ .unwrap_or(0u8.into());
+
+ buf[buf_index] = nibbles_to_u8(even_led_intensity, odd_led_intensity);
+ buf_index += 1;
+ }
+
+ // Even fading & led
+ {
+ let fading = even.fading_transition_duration;
+ let led = even.led_duration;
+ buf[buf_index] = nibbles_to_u8(fading, led);
+ buf_index += 1;
+ }
+
+ // Odd fading & led
+ if let Some(odd) = odd {
+ let fading = odd.fading_transition_duration;
+ let led = odd.led_duration;
+ buf[buf_index] = nibbles_to_u8(fading, led);
+ buf_index += 1;
+ }
+ }
+
+ buf
+ }
+ }
+ }
+
/// Operations of player lights.
pub trait Lights: JoyConDriver {
const LIGHT_UP: [LightUp; 4] = LIGHT_UP;
@@ -1774,6 +1973,34 @@ pub mod lights {
{
LightsStatus::once(self)
}
+
+ /// Set HOME light.
+ ///
+ /// # Example
+ /// ```no_run
+ /// use joycon_rs::prelude::{*, lights::{*, home_button::*}};
+ ///
+ /// # let manager = JoyConManager::new().unwrap();
+ /// #
+ /// # let device = manager.lock()
+ /// # .unwrap()
+ /// # .managed_devices()
+ /// # .remove(0);
+ /// #
+ /// # let mut joycon_driver = SimpleJoyConDriver::new(&device).unwrap();
+ /// let pattern =
+ /// // loop pattern forever
+ /// LightEmittingPattern::new(100, 0, 0u8.into())
+ /// // 0.5 seconds to light up
+ /// .add_phase(100,500,0)
+ /// // 0.5 seconds to turn off
+ /// .add_phase(0,500,0);
+ /// let player_lights_status = joycon_driver.set_home_light(&pattern);
+ /// ```
+ fn set_home_light(&mut self, pattern: &home_button::LightEmittingPattern) -> JoyConResult<[u8; 362]> {
+ let arg: [u8;25] = pattern.clone().into();
+ self.send_sub_command(SubCommand::SetHOMELight, &arg)
+ }
}
impl Lights for D where D: JoyConDriver {}
@@ -1782,21 +2009,14 @@ pub mod lights {
pub mod device_info {
use super::{*, input_report_mode::sub_command_mode::*};
- #[derive(Debug, Clone, Hash, Eq, PartialEq)]
- pub enum JoyConDeviceKind {
- JoyConR = 0,
- JoyConL = 1,
- ProCon = 2,
- }
-
- impl TryFrom for JoyConDeviceKind {
+ impl TryFrom for JoyConDeviceType {
type Error = ();
fn try_from(value: u8) -> Result {
let kind = match value {
- 0 => JoyConDeviceKind::JoyConL,
- 1 => JoyConDeviceKind::JoyConR,
- 2 => JoyConDeviceKind::ProCon,
+ 0 => JoyConDeviceType::JoyConL,
+ 1 => JoyConDeviceType::JoyConR,
+ 2 => JoyConDeviceType::ProCon,
_ => Err(())?
};
@@ -1810,7 +2030,7 @@ pub mod device_info {
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct JoyConDeviceInfo {
pub firmware_version: u16,
- pub device_kind: JoyConDeviceKind,
+ pub device_type: JoyConDeviceType,
pub mac_address: JoyConMacAddress,
pub colors_in_spi: bool,
}
@@ -1820,7 +2040,7 @@ pub mod device_info {
fn try_from(value: [u8; 35]) -> Result {
let firmware_version = u16::from_be_bytes([value[0], value[1]]);
- let device_kind = JoyConDeviceKind::try_from(value[2])
+ let device_kind = JoyConDeviceType::try_from(value[2])
.map_err(|()| {
JoyConError::SubCommandError(SubCommand::RequestDeviceInfo as u8, value.to_vec())
})?;
@@ -1833,7 +2053,7 @@ pub mod device_info {
Ok(JoyConDeviceInfo {
firmware_version,
- device_kind,
+ device_type: device_kind,
mac_address,
colors_in_spi,
})
diff --git a/src/joycon/manager.rs b/src/joycon/manager.rs
index 3ab633d..585bcd3 100644
--- a/src/joycon/manager.rs
+++ b/src/joycon/manager.rs
@@ -5,7 +5,6 @@ use std::sync::Mutex;
use std::time::Duration;
use std::thread::JoinHandle;
use std::option::Option::Some;
-use crate::joycon::device::is_joycon;
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct JoyConSerialNumber(pub String);
@@ -109,7 +108,10 @@ impl JoyConManager {
.collect::>();
let detected_device_serials = hid_api.device_list()
- .filter(|&device_info| is_joycon(device_info).is_ok())
+ .filter(|&device_info|
+ JoyConDevice::check_type_of_device(device_info)
+ .is_ok()
+ )
.flat_map(|device_info|
device_info.serial_number()
.map(|s| s.to_string())
@@ -118,7 +120,10 @@ impl JoyConManager {
.collect::>();
let mut detected_devices = hid_api.device_list()
- .filter(|&device_info| is_joycon(device_info).is_ok())
+ .filter(|&device_info|
+ JoyConDevice::check_type_of_device(device_info)
+ .is_ok()
+ )
.flat_map(|di| {
let serial_number = di.serial_number()
.map(|s| s.to_string())
@@ -141,7 +146,7 @@ impl JoyConManager {
Err(e) => e.into_inner()
};
- *device = JoyConDevice::Disconnected;
+ device.forget_device();
});
});
}
diff --git a/src/joycon/mod.rs b/src/joycon/mod.rs
index 5a41d1a..9ad0e18 100644
--- a/src/joycon/mod.rs
+++ b/src/joycon/mod.rs
@@ -1,6 +1,6 @@
use crate::prelude::*;
-pub use device::JoyConDevice;
+pub use device::{JoyConDevice, JoyConDeviceType};
pub use driver::{
Rotation,
Rumble,