1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use std::path::PathBuf;

use clap::Parser;

/// Access every feature of the Nintendo Switch controllers
///
/// Env variables:
///
/// - `RUST_LOG=<level>`:
///
///   -   `trace`: log every bluetooth packet
///
///   -   `debug`: only log important packets
///
/// - `LOG_PRETTY=1`: use a more verbose logging format
///
/// - `LOG_TIMING=1`: show timings
#[derive(Parser)]
pub struct Opts {
    #[clap(subcommand)]
    pub subcmd: SubCommand,
    /// Wait for a controller to connect
    #[clap(short, long)]
    pub wait: bool,
}

#[derive(Parser)]
pub enum SubCommand {
    /// Calibrate the controller
    ///
    /// The calibration will be stored on the controller and used by the Switch.
    Calibrate(Calibrate),
    /// Print settings from the controller
    Get,
    /// Configure settings of the controller
    Set(Set),
    /// Show live inputs from the controller
    Monitor,
    PulseRate,
    #[cfg(feature = "interface")]
    Tui,
    /// Dump the memory of the controller to a binary file
    Dump,
    /// Restore the memory of the controller from a dump file
    Restore,
    /// Decode raw reports exchanged between the controller and the Switch
    ///
    /// See the `relay` subcommand to record new traces.
    ///
    /// See the `trace/` folder for recorded dumps, and
    /// [relay_joycon.py](https://github.com/Yamakaky/joycontrol/blob/capture-text-file/scripts/relay_joycon.py)
    /// for capturing new dumps.
    Decode,
    /// Relay the bluetooth trafic between a controller and the Switch
    ///
    /// Important commands are decoded and shown, and a full log can be recorded.
    /// See the `decode` subcommand to decode logs.
    Relay(Relay),
    /// Ringcon-specific actions
    Ringcon(Ringcon),
    Camera,
}

#[derive(Parser)]
pub struct Calibrate {
    #[clap(subcommand)]
    pub subcmd: CalibrateE,
}

#[derive(Parser)]
pub enum CalibrateE {
    /// Calibrate the sticks
    Sticks,
    /// Calibrate the gyroscope
    Gyroscope,
    /// Reset gyroscope and sticks calibration to factory values
    Reset,
}

#[derive(Parser)]
pub struct Set {
    #[clap(subcommand)]
    pub subcmd: SetE,
}

#[derive(Parser)]
pub enum SetE {
    /// Change the color of the controller
    ///
    /// This is used by the switch for the controller icons. Every color is in `RRGGBB` format.
    Color(SetColor),
}

#[derive(Parser)]
pub struct SetColor {
    /// Color of the body of the controller
    pub body: String,
    /// Color of the buttons, sticks and triggers
    pub buttons: String,
    /// Color of the left grip (Pro Controller only)
    pub left_grip: Option<String>,
    /// Color of the right grip (Pro Controller only)
    pub right_grip: Option<String>,
}

#[derive(Parser)]
pub struct Ringcon {
    #[clap(subcommand)]
    pub subcmd: RingconE,
}

#[derive(Parser)]
pub enum RingconE {
    /// Get the number of flex stored in the ringcon
    StoredFlex,
    /// Show the flex value in realtime
    Monitor,
    /// Random experiments
    Exp,
}

#[derive(Parser)]
pub struct Relay {
    /// Bluetooth MAC address of the Switch
    #[clap(short, long, validator(is_mac))]
    pub address: String,
    /// Location of the log to write
    #[clap(short, long)]
    pub output: Option<PathBuf>,
    /// Decode important HID reports and print them to stdout
    #[clap(short, long)]
    pub verbose: bool,
}

fn is_mac(input: &str) -> Result<(), String> {
    let mut i = 0;
    for x in input.split(":").map(|x| u8::from_str_radix(x, 16)) {
        match x {
            Ok(_) => i += 1,
            Err(e) => return Err(format!("MAC parsing error: {}", e)),
        }
    }
    if i == 6 {
        Ok(())
    } else {
        Err("invalid MAC address".to_string())
    }
}