Skip to content

Commit a9e057f

Browse files
authored
Fix u64 and add some resilience (#36)
* Add support for uint64 attribute type * Two more fixes: - Actually run discovery futures in parallel, so even if one fails, the others can run. - Allow unknown attributes to just fall through the cracks. * changelog * make a note
1 parent 3888fa0 commit a9e057f

File tree

7 files changed

+169
-73
lines changed

7 files changed

+169
-73
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
## Next
2+
- Added support for a UINT64 attribute type. (Closes #34)
3+
- Broadcast discovery for all devices, even if one in the middle fails.
4+
- Omit attributes from devices with unknown types.
25

36
## 0.2.0
47
- Add HTTP server running on port 3000. You can toggle stuff on it!

Cargo.lock

Lines changed: 74 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,26 @@ license = "CC-BY-4.0"
1010
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1111

1212
[dependencies]
13+
async-channel = "1.4"
1314
async-trait = "0.1.42"
14-
rumqttc = "0.2.0"
1515
clap = "3.0.0-beta.1"
16-
slog = {version = "2.5.2", features=["max_level_trace"]}
17-
slog-term = "2.6.0"
16+
futures = "0.3.13"
17+
hyper = {version = "0.13.9", features=["runtime", "tcp"], default-features=false}
18+
lazy_static = "1.4.0"
1819
log = "*"
19-
url = "2.1.1"
20-
simple-error = "0.2.1"
2120
regex = "1"
22-
lazy_static = "1.4.0"
23-
tokio = {version = "0.2.22", features=["blocking", "rt-core", "process"]}
24-
slog-stdlog = "4.0.0"
25-
slog-scope = "4.3.0"
26-
subprocess = "0.2.4"
27-
serde_json = "1.0"
28-
async-channel = "1.4"
29-
hyper = {version = "0.13.9", features=["runtime", "tcp"], default-features=false}
21+
rumqttc = "0.2.0"
3022
rust-embed = {version="5.7.0", features=["compression"]}
3123
serde = {version="1.0.118", features=["derive"]}
24+
serde_json = "1.0"
25+
simple-error = "0.2.1"
26+
slog = {version = "2.5.2", features=["max_level_trace"]}
27+
slog-scope = "4.3.0"
28+
slog-stdlog = "4.0.0"
29+
slog-term = "2.6.0"
30+
subprocess = "0.2.4"
31+
tokio = {version = "0.2.22", features=["blocking", "rt-core", "process"]}
32+
url = "2.1.1"
3233

3334
[profile.release]
3435
opt-level = 'z' # Optimize for size.

src/controller.rs

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::utils::Numberish;
66
use regex::Regex;
77
use serde::{Serialize, Serializer};
88
use simple_error::{bail, simple_error};
9-
use slog::debug;
9+
use slog::{debug, error};
1010
use slog_scope;
1111
use std::collections::HashMap;
1212
use std::future::Future;
@@ -31,6 +31,7 @@ pub enum AttributeType {
3131
UInt8,
3232
UInt16,
3333
UInt32,
34+
UInt64,
3435
}
3536

3637
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -41,6 +42,7 @@ pub enum AttributeValue {
4142
UInt8(u8),
4243
UInt16(u16),
4344
UInt32(u32),
45+
UInt64(u64),
4446
}
4547

4648
impl AttributeType {
@@ -50,6 +52,7 @@ impl AttributeType {
5052
AttributeType::UInt8 => AttributeValue::UInt8(payload_str.parse::<u8>()?),
5153
AttributeType::UInt16 => AttributeValue::UInt16(payload_str.parse::<u16>()?),
5254
AttributeType::UInt32 => AttributeValue::UInt32(payload_str.parse::<u32>()?),
55+
AttributeType::UInt64 => AttributeValue::UInt64(payload_str.parse::<u64>()?),
5356
AttributeType::String => AttributeValue::String(payload_str.to_string()),
5457
AttributeType::Bool => {
5558
AttributeValue::Bool(match payload_str.to_ascii_lowercase().as_str() {
@@ -82,6 +85,10 @@ impl AttributeType {
8285
.ok_or_else(|| simple_error!("{} is not a u64", n))?
8386
.try_into()?,
8487
),
88+
(serde_json::Value::Number(n), AttributeType::UInt64) => AttributeValue::UInt64(
89+
n.as_u64()
90+
.ok_or_else(|| simple_error!("{} is not a u64", n))?,
91+
),
8592
(serde_json::Value::Bool(v), AttributeType::Bool) => AttributeValue::Bool(*v),
8693
(v, _) => {
8794
bail!("unknown value for type {:?}: {}", self, v);
@@ -99,6 +106,7 @@ impl AttributeValue {
99106
AttributeValue::UInt8(_) => Some(AttributeType::UInt8),
100107
AttributeValue::UInt16(_) => Some(AttributeType::UInt16),
101108
AttributeValue::UInt32(_) => Some(AttributeType::UInt32),
109+
AttributeValue::UInt64(_) => Some(AttributeType::UInt64),
102110
}
103111
}
104112

@@ -117,6 +125,7 @@ impl AttributeValue {
117125
AttributeValue::UInt8(i) => serde_json::Value::Number(serde_json::Number::from(*i)),
118126
AttributeValue::UInt16(i) => serde_json::Value::Number(serde_json::Number::from(*i)),
119127
AttributeValue::UInt32(i) => serde_json::Value::Number(serde_json::Number::from(*i)),
128+
AttributeValue::UInt64(i) => serde_json::Value::Number(serde_json::Number::from(*i)),
120129
AttributeValue::String(s) => serde_json::Value::String(s.clone()),
121130
}
122131
}
@@ -234,6 +243,7 @@ fn parse_attr_value(t: AttributeType, v: &str) -> Result<AttributeValue, Box<dyn
234243
AttributeType::UInt8 => AttributeValue::UInt8(v.parse()?),
235244
AttributeType::UInt16 => AttributeValue::UInt16(v.parse()?),
236245
AttributeType::UInt32 => AttributeValue::UInt32(v.parse()?),
246+
AttributeType::UInt64 => AttributeValue::UInt64(v.parse()?),
237247
AttributeType::Bool => AttributeValue::Bool(match v {
238248
"TRUE" => true,
239249
"FALSE" => false,
@@ -311,6 +321,7 @@ impl DeviceController for AprontestController {
311321
"UINT8" => AttributeType::UInt8,
312322
"UINT16" => AttributeType::UInt16,
313323
"UINT32" => AttributeType::UInt32,
324+
"UINT64" => AttributeType::UInt64,
314325
"BOOL" => AttributeType::Bool,
315326
"STRING" => AttributeType::String,
316327
_ => bail!("Bad attribute type: {}", m.name("type").unwrap().as_str()),
@@ -331,7 +342,14 @@ impl DeviceController for AprontestController {
331342
)?,
332343
})
333344
})
334-
.collect::<Result<Vec<DeviceAttribute>, Box<dyn Error>>>()?,
345+
.filter_map(|v| match v {
346+
Ok(v) => Some(v),
347+
Err(e) => {
348+
error!(slog_scope::logger(), "failed_to_parse_attribute"; "error" => ?e);
349+
None
350+
}
351+
})
352+
.collect::<Vec<DeviceAttribute>>(),
335353
})
336354
}
337355

@@ -346,6 +364,7 @@ impl DeviceController for AprontestController {
346364
AttributeValue::UInt8(v) => format!("{}", v),
347365
AttributeValue::UInt16(v) => format!("{}", v),
348366
AttributeValue::UInt32(v) => format!("{}", v),
367+
AttributeValue::UInt64(v) => format!("{}", v),
349368
AttributeValue::Bool(v) => if *v { "TRUE" } else { "FALSE" }.to_string(),
350369
AttributeValue::String(v) => v.clone(),
351370
};
@@ -731,6 +750,7 @@ New HA Dimmable Light
731750
61447 | PowerSource | UINT8 | R | 1 |
732751
258048 | IdentifyTime | UINT16 | R/W | 0 |
733752
1699842 | ZB_CurrentFileVersion | UINT32 | R | 33554952 |
753+
1699843 | ArtificialAttribute | UINT64 | R | 33554952 |
734754
4294901760 | WK_TransitionTime | UINT16 | R/W | |
735755
"###;
736756

@@ -739,13 +759,21 @@ New HA Dimmable Light
739759
let controller = controller_with_output(OTHER_TYPES_DESCRIBE);
740760

741761
let result = controller.describe(2).await.unwrap();
742-
assert_eq!(14, result.attributes.len());
762+
assert_eq!(15, result.attributes.len());
743763
assert_eq!(
744764
AttributeType::UInt32,
745-
result.attributes[result.attributes.len() - 2].attribute_type
765+
result.attributes[result.attributes.len() - 3].attribute_type
746766
);
747767
assert_eq!(
748768
AttributeValue::UInt32(33554952),
769+
result.attributes[result.attributes.len() - 3].current_value
770+
);
771+
assert_eq!(
772+
AttributeType::UInt64,
773+
result.attributes[result.attributes.len() - 2].attribute_type
774+
);
775+
assert_eq!(
776+
AttributeValue::UInt64(33554952),
749777
result.attributes[result.attributes.len() - 2].current_value
750778
);
751779
}
@@ -760,9 +788,10 @@ New HA Dimmable Light
760788
AttributeValue::String("".into()),
761789
AttributeValue::Bool(true),
762790
AttributeValue::Bool(false),
763-
AttributeValue::UInt8(u8::max_value()),
764-
AttributeValue::UInt16(u16::max_value()),
765-
AttributeValue::UInt32(u32::max_value()),
791+
AttributeValue::UInt8(u8::MAX),
792+
AttributeValue::UInt16(u16::MAX),
793+
AttributeValue::UInt32(u32::MAX),
794+
AttributeValue::UInt64(u64::MAX),
766795
];
767796

768797
for test in tests.iter() {

src/converter.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ fn switch_to_discovery_payload(
3333
let on_off = device.attribute("On_Off").unwrap();
3434

3535
let (payload_on, payload_off) = match on_off.attribute_type {
36-
AttributeType::UInt8 => ("0", "255"),
37-
AttributeType::UInt16 => ("0", "65535"),
38-
AttributeType::UInt32 => ("0", "4294967295"),
39-
AttributeType::Bool => ("TRUE", "FALSE"),
40-
AttributeType::String => ("ON", "OFF"),
36+
AttributeType::UInt8 => ("0", format!("{}", u8::MAX)),
37+
AttributeType::UInt16 => ("0", format!("{}", u16::MAX)),
38+
AttributeType::UInt32 => ("0", format!("{}", u32::MAX)),
39+
AttributeType::UInt64 => ("0", format!("{}", u64::MAX)),
40+
AttributeType::Bool => ("TRUE", "FALSE".into()),
41+
AttributeType::String => ("ON", "OFF".into()),
4142
};
4243

4344
let unique_id = format!(
@@ -75,10 +76,11 @@ fn dimmer_to_discovery_payload(
7576
device: &LongDevice,
7677
) -> Result<AutodiscoveryMessage, Box<dyn Error>> {
7778
let level = device.attribute("Level").unwrap();
78-
let scale: u32 = match level.attribute_type {
79-
AttributeType::UInt8 => u8::max_value() as u32,
80-
AttributeType::UInt16 => u16::max_value() as u32,
81-
AttributeType::UInt32 => u32::max_value(),
79+
let scale: u64 = match level.attribute_type {
80+
AttributeType::UInt8 => u8::MAX as u64,
81+
AttributeType::UInt16 => u16::MAX as u64,
82+
AttributeType::UInt32 => u32::MAX as u64,
83+
AttributeType::UInt64 => u64::MAX,
8284
AttributeType::Bool => 1,
8385
AttributeType::String => {
8486
bail!("A string level type! Please report with `aprontest -l` output!")

0 commit comments

Comments
 (0)