1use chrono::{DateTime, NaiveDate, NaiveDateTime};
2use reqwest::blocking::Client;
3use serde::Serialize;
4use sha1::{Digest, Sha1};
5use std::time::{SystemTime, UNIX_EPOCH};
6
7type WatchPowerAPIResult = Result<serde_json::Value, Box<dyn std::error::Error>>;
8
9#[derive(Debug, Serialize, Clone)]
10struct WatchPowerDailyData {
11 }
13
14impl WatchPowerDailyData {
15 fn from_json(json: &serde_json::Value) -> Self {
16 todo!()
17 }
18}
19
20#[derive(Debug, Serialize, Clone)]
21struct WatchPowerDeviceParams {
22 serial_number: String,
23 wifi_pn: String,
24 dev_code: i32,
25 dev_addr: i32,
26}
27
28#[derive(Debug, Serialize, Clone)]
29struct WatchPowerFlowData {
30 grid_voltage: f32,
31 grid_frequency: f32,
32 ac_output_voltage: f32,
33 ac_output_active_power: i32,
34 output_load_percent: i8,
35 battery_capacity: i8,
36 battery_voltage: f32,
37 pv_input_voltage: f32,
38 pv_input_power: f32,
39}
40
41impl WatchPowerFlowData {
42 fn from_json(json: &serde_json::Value) -> Self {
43 todo!()
44 }
45}
46
47#[derive(Debug, Serialize, Clone)]
48pub struct WatchPowerLastDataGrid {
49 pub grid_rating_voltage: f32,
50 pub grid_rating_current: f32,
51 pub battery_rating_voltage: f32,
52 pub ac_output_rating_voltage: f32,
53 pub ac_output_rating_current: f32,
54 pub ac_output_rating_frequency: f32,
55 pub ac_output_rating_apparent_power: i32,
56 pub ac_output_rating_active_power: i32,
57}
58
59impl WatchPowerLastDataGrid {
60 fn from_json(json: &serde_json::Value) -> Self {
61 let mut grid_rating_voltage = None;
62 let mut grid_rating_current = None;
63 let mut battery_rating_voltage = None;
64 let mut ac_output_rating_voltage = None;
65 let mut ac_output_rating_current = None;
66 let mut ac_output_rating_frequency = None;
67 let mut ac_output_rating_apparent_power = None;
68 let mut ac_output_rating_active_power = None;
69
70 for field in json.as_array().unwrap() {
71 match field["id"].as_str().unwrap() {
72 "gd_grid_rating_voltage" => {
73 grid_rating_voltage =
74 Some(field["val"].as_str().unwrap().parse::<f32>().unwrap())
75 }
76 "gd_grid_rating_current" => {
77 grid_rating_current =
78 Some(field["val"].as_str().unwrap().parse::<f32>().unwrap())
79 }
80 "gd_battery_rating_voltage" => {
81 battery_rating_voltage =
82 Some(field["val"].as_str().unwrap().parse::<f32>().unwrap())
83 }
84 "gd_bse_input_voltage_read" => {
85 ac_output_rating_voltage =
86 Some(field["val"].as_str().unwrap().parse::<f32>().unwrap())
87 }
88 "gd_ac_output_rating_current" => {
89 ac_output_rating_current =
90 Some(field["val"].as_str().unwrap().parse::<f32>().unwrap())
91 }
92 "gd_bse_output_frequency_read" => {
93 ac_output_rating_frequency =
94 Some(field["val"].as_str().unwrap().parse::<f32>().unwrap())
95 }
96 "gd_ac_output_rating_apparent_power" => {
97 ac_output_rating_apparent_power =
98 Some(field["val"].as_str().unwrap().parse::<i32>().unwrap())
99 }
100 "gd_ac_output_rating_active_power" => {
101 ac_output_rating_active_power =
102 Some(field["val"].as_str().unwrap().parse::<i32>().unwrap())
103 }
104 _ => continue,
105 }
106 }
107 WatchPowerLastDataGrid {
108 grid_rating_voltage: grid_rating_voltage.expect("Grid rating voltage not found"),
109 grid_rating_current: grid_rating_current.expect("Grid rating current not found"),
110 battery_rating_voltage: battery_rating_voltage
111 .expect("Battery rating voltage not found"),
112 ac_output_rating_voltage: ac_output_rating_voltage
113 .expect("AC output rating voltage not found"),
114 ac_output_rating_current: ac_output_rating_current
115 .expect("AC output rating current not found"),
116 ac_output_rating_frequency: ac_output_rating_frequency
117 .expect("AC output rating frequency not found"),
118 ac_output_rating_apparent_power: ac_output_rating_apparent_power
119 .expect("AC output rating apparent power not found"),
120 ac_output_rating_active_power: ac_output_rating_active_power
121 .expect("AC output rating active power not found"),
122 }
123 }
124}
125
126#[derive(Debug, Serialize, Clone)]
127pub struct WatchPowerLastDataSystem {
128 pub model: String,
129 pub main_cpu_firmware_version: String,
130 pub secondary_cpu_firmware_version: String,
131}
132
133impl WatchPowerLastDataSystem {
134 fn from_json(json: &serde_json::Value) -> Self {
135 let mut model = None;
136 let mut main_cpu_firmware_version = None;
137 let mut secondary_cpu_firmware_version = None;
138
139 for field in json.as_array().unwrap() {
140 match field["id"].as_str().unwrap() {
141 "sy_model" => model = Some(field["val"].as_str().unwrap().to_owned()),
142 "sy_main_cpu1_firmware_version" => {
143 main_cpu_firmware_version = Some(field["val"].as_str().unwrap().to_owned())
144 }
145 "sy_main_cpu2_firmware_version" => {
146 secondary_cpu_firmware_version = Some(field["val"].as_str().unwrap().to_owned())
147 }
148 _ => continue,
149 }
150 }
151 WatchPowerLastDataSystem {
152 model: model.expect("Model not found"),
153 main_cpu_firmware_version: main_cpu_firmware_version
154 .expect("Main CPU firmware version not found"),
155 secondary_cpu_firmware_version: secondary_cpu_firmware_version
156 .expect("Secondary CPU firmware version not found"),
157 }
158 }
159}
160
161#[derive(Debug, Serialize, Clone)]
162pub struct WatchPowerLastDataPV {
163 pub pv_input_current: f32,
164}
165
166impl WatchPowerLastDataPV {
167 fn from_json(json: &serde_json::Value) -> Self {
168 let mut pv_input_current = None;
169 for field in json.as_array().unwrap() {
170 match field["id"].as_str().unwrap() {
171 "pv_input_current" => {
172 pv_input_current = Some(field["val"].as_str().unwrap().parse::<f32>().unwrap())
173 }
174 _ => continue,
175 }
176 }
177 WatchPowerLastDataPV {
178 pv_input_current: pv_input_current.expect("PV input current not found"),
179 }
180 }
181}
182
183#[derive(Debug, Serialize, Clone)]
184pub struct WatchPowerLastDataMain {
185 pub grid_voltage: f32,
186 pub grid_frequency: f32,
187 pub pv_input_voltage: f32,
188 pub pv_input_power: i16,
189 pub battery_voltage: f32,
190 pub battery_capacity: i8,
191 pub battery_charging_current: f32,
192 pub battery_discharge_current: f32,
193 pub ac_output_voltage: f32,
194 pub ac_output_frequency: f32,
195 pub ac_output_apparent_power: i32,
196 pub ac_output_active_power: i32,
197 pub output_load_percent: i8,
198}
199
200impl WatchPowerLastDataMain {
201 fn from_json(json: &serde_json::Value) -> Self {
202 let mut grid_voltage = None;
203 let mut grid_frequency = None;
204 let mut pv_input_voltage = None;
205 let mut pv_input_power = None;
206 let mut battery_voltage = None;
207 let mut battery_capacity = None;
208 let mut battery_charging_current = None;
209 let mut battery_discharge_current = None;
210 let mut ac_output_voltage = None;
211 let mut ac_output_frequency = None;
212 let mut ac_output_apparent_power = None;
213 let mut ac_output_active_power = None;
214 let mut output_load_percent = None;
215 for field in json.as_array().unwrap() {
216 match field["id"].as_str().unwrap() {
217 "bt_grid_voltage" => {
218 grid_voltage = Some(field["val"].as_str().unwrap().parse::<f32>().unwrap())
219 }
220 "bt_grid_frequency" => {
221 grid_frequency = Some(field["val"].as_str().unwrap().parse::<f32>().unwrap())
222 }
223 "bt_voltage_1" => {
224 pv_input_voltage = Some(field["val"].as_str().unwrap().parse::<f32>().unwrap())
225 }
226 "bt_input_power" => {
227 pv_input_power = Some(field["val"].as_str().unwrap().parse::<i16>().unwrap())
228 }
229 "bt_battery_voltage" => {
230 battery_voltage = Some(field["val"].as_str().unwrap().parse::<f32>().unwrap())
231 }
232 "bt_battery_capacity" => {
233 battery_capacity = Some(field["val"].as_str().unwrap().parse::<i8>().unwrap())
234 }
235 "bt_battery_charging_current" => {
236 battery_charging_current =
237 Some(field["val"].as_str().unwrap().parse::<f32>().unwrap())
238 }
239 "bt_battery_discharge_current" => {
240 battery_discharge_current =
241 Some(field["val"].as_str().unwrap().parse::<f32>().unwrap())
242 }
243 "bt_ac_output_voltage" => {
244 ac_output_voltage = Some(field["val"].as_str().unwrap().parse::<f32>().unwrap())
245 }
246 "bt_grid_AC_frequency" => {
247 ac_output_frequency =
248 Some(field["val"].as_str().unwrap().parse::<f32>().unwrap())
249 }
250 "bt_ac_output_apparent_power" => {
251 ac_output_apparent_power =
252 Some(field["val"].as_str().unwrap().parse::<i32>().unwrap())
253 }
254 "bt_load_active_power_sole" => {
255 ac_output_active_power =
256 Some(field["val"].as_str().unwrap().parse::<i32>().unwrap())
257 }
258 "bt_output_load_percent" => {
259 output_load_percent =
260 Some(field["val"].as_str().unwrap().parse::<i8>().unwrap())
261 }
262 _ => continue,
263 }
264 }
265 WatchPowerLastDataMain {
266 grid_voltage: grid_voltage.expect("Grid voltage not found"),
267 grid_frequency: grid_frequency.expect("Grid frequency not found"),
268 pv_input_voltage: pv_input_voltage.expect("PV input voltage not found"),
269 pv_input_power: pv_input_power.expect("PV input power not found"),
270 battery_voltage: battery_voltage.expect("Battery voltage not found"),
271 battery_capacity: battery_capacity.expect("Battery capacity not found"),
272 battery_charging_current: battery_charging_current
273 .expect("Battery charging current not found"),
274 battery_discharge_current: battery_discharge_current
275 .expect("Battery discharge current not found"),
276 ac_output_voltage: ac_output_voltage.expect("AC output voltage not found"),
277 ac_output_frequency: ac_output_frequency.expect("AC output frequency not found"),
278 ac_output_apparent_power: ac_output_apparent_power
279 .expect("AC output apparent power not found"),
280 ac_output_active_power: ac_output_active_power
281 .expect("AC output active power not found"),
282 output_load_percent: output_load_percent.expect("Output load percent not found"),
283 }
284 }
285}
286
287#[derive(Debug, Serialize, Clone)]
288pub struct WatchPowerLastData {
289 pub timestamp: NaiveDateTime,
290 pub grid: WatchPowerLastDataGrid,
291 pub system: WatchPowerLastDataSystem,
292 pub pv: WatchPowerLastDataPV,
293 pub main: WatchPowerLastDataMain,
294}
295
296impl WatchPowerLastData {
297 fn from_json(json: &serde_json::Value) -> Self {
298 let dat_field = &json["dat"];
299 let pars_field = &dat_field["pars"];
300 WatchPowerLastData {
301 timestamp: DateTime::from_timestamp_millis(
302 dat_field["gts"].as_str().unwrap().parse::<i64>().unwrap(),
303 )
304 .unwrap()
305 .naive_local(),
306 grid: WatchPowerLastDataGrid::from_json(&pars_field["gd_"]),
307 system: WatchPowerLastDataSystem::from_json(&pars_field["sy_"]),
308 pv: WatchPowerLastDataPV::from_json(&pars_field["pv_"]),
309 main: WatchPowerLastDataMain::from_json(&pars_field["bt_"]),
310 }
311 }
312}
313
314#[derive(Debug, Clone)]
315pub struct WatchPowerAPI {
316 _base_url: String,
317 _suffix_context: String,
318 _company_key: String,
319 _token: Option<String>,
320 _secret: String,
321 _expire: Option<u64>,
322 _client: Client,
323 _device_params: WatchPowerDeviceParams,
324}
325
326impl WatchPowerAPI {
327 pub fn new(serial_number: &str, wifi_pn: &str, dev_code: i32, dev_addr: i32) -> Self {
328 WatchPowerAPI {
329 _base_url: "http://android.shinemonitor.com/public/".to_string(),
330 _suffix_context: "&i18n=pt_BR&lang=pt_BR&source=1&_app_client_=android&_app_id_=wifiapp.volfw.watchpower&_app_version_=1.0.6.3".to_string(),
331 _company_key: "bnrl_frRFjEz8Mkn".to_string(),
332 _token: None,
333 _secret: "ems_secret".to_string(),
334 _expire: None,
335 _client: Client::new(),
336 _device_params: WatchPowerDeviceParams{serial_number: serial_number.to_string(), wifi_pn: wifi_pn.to_string(), dev_code: dev_code, dev_addr: dev_addr},
337 }
338 }
339
340 fn generate_salt() -> String {
341 let start = SystemTime::now();
342 let since_the_epoch = start
343 .duration_since(UNIX_EPOCH)
344 .expect("Time went backwards");
345 (since_the_epoch.as_millis()).to_string()
346 }
347
348 fn sha1_str_lower_case(input: &[u8]) -> String {
349 let mut hasher = Sha1::new();
350 hasher.update(input);
351 format!("{:x}", hasher.finalize())
352 }
353
354 fn hash(&self, args: Vec<&str>) -> String {
355 let arg_concat = args.join("");
356 WatchPowerAPI::sha1_str_lower_case(arg_concat.as_bytes())
357 }
358
359 pub fn login(
360 &mut self,
361 username: &str,
362 password: &str,
363 ) -> Result<(), Box<dyn std::error::Error>> {
364 let base_action = format!(
365 "&action=authSource&usr={}&company-key={}{}",
366 username, self._company_key, self._suffix_context
367 );
368
369 let salt = WatchPowerAPI::generate_salt();
370 let password_hash = self.hash(vec![password]);
371 let sign = self.hash(vec![&salt, &password_hash, &base_action]);
372
373 let url = format!(
374 "{}?sign={}&salt={}{}",
375 self._base_url, sign, salt, base_action
376 );
377
378 let response: serde_json::Value = self._client.get(&url).send()?.json()?;
379
380 if response["err"].as_u64() == Some(0) {
381 self._secret = response["dat"]["secret"].as_str().unwrap().to_string();
382 self._token = Some(response["dat"]["token"].as_str().unwrap().to_string());
383 self._expire = Some(response["dat"]["expire"].as_u64().unwrap());
384 Ok(())
385 } else {
386 Err(Box::new(std::io::Error::new(
387 std::io::ErrorKind::Other,
388 format!("Login error: {:?}", response),
389 )))
390 }
391 }
392
393 fn _request(&self, action: &str, query: Option<&str>) -> WatchPowerAPIResult {
394 let base_action = format!(
395 "&action={}&pn={}&devcode={}&sn={}&devaddr={}{}{}",
396 action,
397 self._device_params.wifi_pn,
398 self._device_params.dev_code,
399 self._device_params.serial_number,
400 self._device_params.dev_addr,
401 query.unwrap_or(""),
402 self._suffix_context
403 );
404 let salt = WatchPowerAPI::generate_salt();
405 let sign = self.hash(vec![
406 &salt,
407 &self._secret,
408 self._token.as_ref().unwrap(),
409 &base_action,
410 ]);
411 let auth = format!(
412 "?sign={}&salt={}&token={}",
413 sign,
414 salt,
415 self._token.as_ref().unwrap()
416 );
417 let url = format!("{}{}{}", self._base_url, auth, base_action);
418
419 let response: serde_json::Value = self._client.get(&url).send()?.json()?;
420
421 if response["err"] == 0 {
422 Ok(response)
423 } else {
424 Err(Box::new(std::io::Error::new(
425 std::io::ErrorKind::Other,
426 format!("API error: {:?}", response),
427 )))
428 }
429 }
430
431 fn get_daily_data(
432 &self,
433 day: NaiveDate,
434 ) -> Result<WatchPowerDailyData, Box<dyn std::error::Error>> {
435 let _date = day.format("%Y-%m-%d").to_string();
436 let query = format!("&date={}", _date);
437 match self._request("queryDeviceDataOneDay", Some(&query)) {
438 Ok(raw) => Ok(WatchPowerDailyData::from_json(&raw)),
439 Err(e) => Err(e),
440 }
441 }
442
443 fn get_power_flow(&self) -> Result<WatchPowerFlowData, Box<dyn std::error::Error>> {
444 match self._request("queryDeviceFlowPower", None) {
445 Ok(raw) => Ok(WatchPowerFlowData::from_json(&raw)),
446 Err(e) => Err(e),
447 }
448 }
449
450 pub fn get_last_data(&self) -> Result<WatchPowerLastData, Box<dyn std::error::Error>> {
451 match self._request("querySPDeviceLastData", None) {
452 Ok(raw) => Ok(WatchPowerLastData::from_json(&raw)),
453 Err(e) => Err(e),
454 }
455 }
456}