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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
use std::{net::Ipv4Addr, time::Duration};
use phf::phf_map;
use crate::{
constants,
network::{util::reverse_mac, DiscoveryResponse, RemoteDataCommand, RemoteDataMessage},
Device, DeviceInfo,
};
/// A mapping of remote device codes to their friendly model equivalent.
pub const REMOTE_CODES: phf::Map<u16, &'static str> = phf_map! {
0x520Bu16 => "RM4 Pro",
0x5213u16 => "RM4 Pro",
0x5218u16 => "RM4C Pro",
0x6026u16 => "RM4 Pro",
0x6184u16 => "RMC4 Pro",
0x61A2u16 => "RM4 Pro",
0x649Bu16 => "RM4 Pro",
0x653Cu16 => "RM4 Pro",
};
/// A broadlink device capable of transmitting IR / RF codes.
#[derive(Debug, Clone)]
pub struct RemoteDevice {
/// Base information about the remote.
pub info: DeviceInfo,
}
impl RemoteDevice {
/// Create a new RemoteDevice.
///
/// Note: This should not be called directly. Please use [Device::from_ip] or
/// [Device::list] instead.
pub fn new(name: &str, addr: Ipv4Addr, response: DiscoveryResponse) -> RemoteDevice {
// Get the type of remote
let friendly_model: String = REMOTE_CODES
.get(&response.model_code)
.unwrap_or(&"Unknown")
.to_string();
return Self {
info: DeviceInfo {
address: addr,
mac: reverse_mac(response.mac),
model_code: response.model_code,
friendly_type: "Remote".into(),
friendly_model: friendly_model,
name: name.into(),
auth_id: 0, // This will be populated when authenticated.
key: constants::INITIAL_KEY,
is_locked: response.is_locked,
},
};
}
/// Attempt to learn an IR code.
///
/// When learning, the remote's LED will light up orange. Simply long press
/// (and release) the IR button while pointing the control at the device until the light
/// turns off.
pub fn learn_ir(&self) -> Result<Vec<u8>, String> {
// First enter learning...
self.send_command(&[], RemoteDataCommand::StartLearningIR)
.map_err(|e| format!("Could not enter learning mode! {}", e))?;
// Block until we learn the code or timeout
let attempts = 10;
let interval = Duration::from_secs(3);
for _ in 0..attempts {
// Sleep before trying again
std::thread::sleep(interval);
let code: Vec<u8> = self
.send_command(&[], RemoteDataCommand::GetCode)
.map_err(|e| format!("Could not check code status of device! {}", e))?;
if code.len() != 0 {
return Ok(code);
}
}
// If we haven't gotten anything up until now, then we failed
return Err("Could not learn IR code! Operation timed out.".into());
}
/// Attempts to learn an RF code.
///
/// The device must go through two stages in order to learn an RF code.
/// 1) It must learn which RF frequency is in use.
/// 2) It must learn the actual RF code.
///
/// To do so, follow these instructions:
/// 1) Wait for the device's LED to turn orange
/// 2) Long press (and release) the RF button until the orange LED turns off
/// and then back on.
/// 3) Press the RF button once more normally until the orange LED turns off.
pub fn learn_rf(&self) -> Result<Vec<u8>, String> {
// Start sweeping for the type of frequency in use
self.send_command(&[], RemoteDataCommand::SweepRfFrequencies)
.map_err(|e| format!("Could not start sweeping frequencies! {}", e))?;
// Wait for the frequency to be identified
let attempts = 10;
let interval = Duration::from_secs(3);
let mut frequency_found = false;
for _ in 0..attempts {
// Sleep before trying again
std::thread::sleep(interval);
let frequency: Vec<u8> = self
.send_command(&[], RemoteDataCommand::CheckFrequency)
.map_err(|e| format!("Could not check code status of device! {}", e))?;
if frequency[0] == 1 {
frequency_found = true;
break;
}
}
// Error out if no frequency is found
if !frequency_found {
self.send_command(&[], RemoteDataCommand::StopRfSweep)
.map_err(|e| format!("Could not cancel RF sweep! {}", e))?;
return Err("Could not determine frequency!".into());
}
// Enter RF learning mode
self.send_command(&[], RemoteDataCommand::StartLearningRF)
.map_err(|e| format!("Could not enter learning mode! {}", e))?;
// Block until we learn the code or timeout
for _ in 0..attempts {
// Sleep before trying again
std::thread::sleep(interval);
let code: Vec<u8> = self
.send_command(&[], RemoteDataCommand::GetCode)
.map_err(|e| format!("Could not check code status of device! {}", e))?;
if code.len() != 0 {
return Ok(code);
}
}
// If we haven't gotten anything up until now, then we failed
self.send_command(&[], RemoteDataCommand::StopRfSweep)
.map_err(|e| format!("Could not cancel RF sweep! {}", e))?;
return Err("Could not learn RF code! Operation timed out.".into());
}
/// Sends an IR/RF code to the world.
pub fn send_code(&self, code: &[u8]) -> Result<(), String> {
self.send_command(code, RemoteDataCommand::SendCode)
.map_err(|e| format!("Could not send IR code to device! {}", e))?;
return Ok(());
}
/// Sends a raw command to the remote.
/// Note: Try to avoid using this method in favor of [RemoteDevice::send_code], [RemoteDevice::learn_ir], etc.
pub fn send_command(
&self,
payload: &[u8],
command: RemoteDataCommand,
) -> Result<Vec<u8>, String> {
// We cast this object to a generic device in order to make use of the shared
// helper utilities.
let generic_device = Device::Remote {
remote: self.clone(),
};
// Construct the data message
let msg = RemoteDataMessage::new(command);
let packed = msg
.pack_with_payload(&payload)
.map_err(|e| format!("Could not pack remote data message! {}", e))?;
let response = generic_device
.send_command::<RemoteDataMessage>(&packed)
.map_err(|e| format!("Could not send code inside of the command! {}", e))?;
return RemoteDataMessage::unpack_with_payload(&response);
}
}