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
149
150
151
152
153
154
155
156
157
158
use std::fmt;

use crate::{RawId, U16LE};

// subcommand id 0x58
//
// Maybe arg 2 is a device selector? Check with pokeball plus
//
// arg [4,0,0,2], ret [0,8,0,0,0,0,0,44]
// arg [4,4,5,2], ret [0,8,0,0,0,0,200]
// arg [4,4,50,2], ret [0,8,0,0,0,0,5,0,0,14]
// arg [4,4,10,2], ret [0,20,0,0,0,0,244,22,0,0,230,5,0,0,243,11,0,0,234,12, 0, 0]
// get ringcon calibration: arg [4,4,26,2]
//                          ret [0,20,0,0,0,0] + [135, 8, 28, 0, 48, 247, 243, 0, 44, 12, 224]
// write ringcon calibration: arg [20,4,26,1,16] + [135, 8, 28, 0, 48, 247, 243, 0, 44, 12, 224]
//                            ret [0, 4]
// get number steps offline ringcon: arg [4,4,49,2], ret [0,8,0,0,0,0,nb_steps, 0,0, 127|143]
// reset number steps offline ringcon: arg [8,4,49,1,4], ret [0,4]

#[repr(u8)]
#[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive, PartialEq, Eq)]
pub enum AccessoryCommandId {
    Get = 4,
    Reset = 8,
    Write = 20,
}

#[repr(u8)]
#[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive, PartialEq, Eq)]
pub enum AccessoryType {
    Ringcon = 4,
}

#[repr(u8)]
#[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive, PartialEq, Eq)]
pub enum RingconItemId {
    Calibration = 26,
    OfflineSteps = 49,
}

#[repr(packed)]
#[derive(Copy, Clone, Debug)]
#[allow(dead_code)]
pub struct AccessoryCommand {
    id: RawId<AccessoryCommandId>,
    ty: RawId<AccessoryType>,
    item: RawId<RingconItemId>,
    maybe_includes_arg: u8,
    maybe_arg_size: u8,
    raw: [u8; 18],
}
impl AccessoryCommand {
    pub fn get_offline_steps() -> Self {
        AccessoryCommand {
            id: AccessoryCommandId::Get.into(),
            ty: AccessoryType::Ringcon.into(),
            item: RingconItemId::OfflineSteps.into(),
            maybe_includes_arg: 2,
            maybe_arg_size: 0,
            raw: [0; 18],
        }
    }

    // Known CRC values:
    //    0 0
    //    1 127
    //    2 254
    //    3 129
    //    4 113
    //    5 14
    //    0x64 74
    //    0xf0 173
    //    0x100 200
    //    0x101 183
    //    0x103 73
    //    0x104 185
    //    0x1f4 20
    pub fn write_offline_steps(steps: u16, sum: u8) -> Self {
        let steps = steps.to_le_bytes();
        AccessoryCommand {
            id: AccessoryCommandId::Reset.into(),
            ty: AccessoryType::Ringcon.into(),
            item: RingconItemId::OfflineSteps.into(),
            maybe_includes_arg: 1,
            maybe_arg_size: 4,
            raw: [
                steps[0], steps[1], 0, sum, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            ],
        }
    }
}

#[repr(packed)]
#[derive(Copy, Clone)]
pub struct AccessoryResponse {
    //254: nothing connected
    error: u8,
    len: u8,
    unknown_0x00: [u8; 4],
    u: AccessoryResponseUnion,
}

impl AccessoryResponse {
    fn check_error(&self) -> Result<(), Error> {
        match self.error {
            0 => Ok(()),
            254 => Err(Error::NoAccessoryConnected),
            e => Err(Error::Other(e)),
        }
    }

    pub fn offline_steps(&self) -> Result<OfflineSteps, Error> {
        self.check_error()?;
        Ok(unsafe { self.u.offline_steps })
    }
}

impl fmt::Debug for AccessoryResponse {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("AccessoryResponse")
            .field("maybe_error", &self.error)
            .field("always_0x00", &self.unknown_0x00)
            .field("data", unsafe { &&self.u.raw[..self.len as usize] })
            .finish()
    }
}

#[derive(Copy, Clone)]
union AccessoryResponseUnion {
    offline_steps: OfflineSteps,
    raw: [u8; 20],
}

#[repr(packed)]
#[derive(Copy, Clone, Debug)]
#[allow(dead_code)]
pub struct OfflineSteps {
    pub steps: U16LE,
    unknown0x00: u8,
    maybe_crc: u8,
}

#[derive(Debug, Clone, Copy)]
pub enum Error {
    NoAccessoryConnected,
    Other(u8),
}

impl std::error::Error for Error {}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::NoAccessoryConnected => f.write_str("no accessory connected"),
            Error::Other(e) => f.write_fmt(format_args!("unknown accessory error: {}", e)),
        }
    }
}