use rustc_serialize::json::BuilderError;
use rustc_serialize::json::Json;
use rustc_serialize::json::Object;
use sensors::BurglarAlarmData;
use sensors::GeneralPurposeBinaryData;
pub type Timestamp = i64;
pub type ParseError = BuilderError;
#[derive(Clone)]
pub struct GatewayState {
json: Json,
end_timestamp: Timestamp,
}
#[derive(Clone)]
pub struct PartialGatewayState {
json: Json,
start_timestamp: Timestamp,
end_timestamp: Timestamp,
}
#[derive(Debug)]
pub enum ResponseError {
ParseError,
MalformedResponse,
MissingTimestamp,
PossibleMissingEvents,
}
impl GatewayState {
pub fn build(raw_json: &str) -> Result<GatewayState, ResponseError> {
let json = try!(parse_json(raw_json));
let timestamp = try!(json.find("updateTime")
.and_then(|t| t.as_i64())
.ok_or(ResponseError::MissingTimestamp));
Ok(GatewayState {
json: json,
end_timestamp: timestamp,
})
}
pub fn get_json(&self) -> &Json {
&self.json
}
pub fn get_end_timestamp(&self) -> Timestamp {
self.end_timestamp
}
pub fn merge(&mut self, partial_state: &PartialGatewayState)
-> Result<(), ResponseError> {
if partial_state.get_start_timestamp() > self.end_timestamp {
return Err(ResponseError::PossibleMissingEvents);
} else if partial_state.get_end_timestamp() <= self.end_timestamp {
return Ok(());
}
let updated_values_obj = match partial_state.get_json().as_object() {
None => { return Err(ResponseError::MalformedResponse); },
Some(j) => j,
};
for (key, updated_json_val) in updated_values_obj.iter() {
if key != "updateTime" {
self.merge_updated_values(key, updated_json_val);
}
}
self.end_timestamp = partial_state.get_end_timestamp();
Ok(())
}
fn merge_updated_values(&mut self, device_key: &str, updated_json: &Json) {
let updated_object = match updated_json.as_object() {
None => { return; }, Some(j) => j,
};
let split = device_key.split(".");
let search_path = split.collect::<Vec<&str>>();
let root_object = self.json.as_object_mut();
let maybe_subtree = GatewayState::get_subtree_mut(root_object, &search_path);
let mut subtree = match maybe_subtree {
None => {
return;
},
Some(stree) => stree,
};
for (updated_key, updated_val) in updated_object.iter() {
subtree.insert(updated_key.to_string(), updated_val.clone());
}
}
fn get_subtree_mut<'a>(maybe_json_object: Option<&'a mut Object>, path: &[&str])
-> Option<&'a mut Object> {
let object = match maybe_json_object {
None => { return None; }, Some(o) => o,
};
match path.split_first() {
None => {
return Some(object); },
Some((first, remaining_path)) => {
let child : Option<&mut Json> = object.get_mut(&(first.to_string()));
let child_json = match child {
None => { return None; }, Some(j) => j,
};
GatewayState::get_subtree_mut(child_json.as_object_mut(), remaining_path)
},
}
}
pub fn get_burglar_alarm(&self, device: u8, instance: u8) ->
Option<BurglarAlarmData> {
let name = format!(
"devices.{}.instances.{}.commandClasses.113.data.7",
device, instance);
let path = DataResponse::path_query_parts(&name);
let alarm_data = self.json.find_path(&path);
alarm_data.and_then(|data| Some(BurglarAlarmData::new(&data)))
}
pub fn get_general_purpose_binary(&self, device: u8, instance: u8) ->
Option<GeneralPurposeBinaryData> {
let name = format!(
"devices.{}.instances.{}.commandClasses.48.data.1",
device, instance);
let path = DataResponse::path_query_parts(&name);
let sensor_data = self.json.find_path(&path);
sensor_data.and_then(
|data| Some(GeneralPurposeBinaryData::new(&data)))
}
}
impl PartialGatewayState {
pub fn build(raw_json: &str, request_time: Timestamp) ->
Result<PartialGatewayState, ResponseError> {
let json = try!(parse_json(raw_json));
let timestamp = try!(json.find("updateTime")
.and_then(|t| t.as_i64())
.ok_or(ResponseError::MissingTimestamp));
Ok(PartialGatewayState {
json: json,
start_timestamp: request_time,
end_timestamp: timestamp,
})
}
pub fn get_json(&self) -> &Json {
&self.json
}
pub fn get_start_timestamp(&self) -> Timestamp {
self.start_timestamp
}
pub fn get_end_timestamp(&self) -> Timestamp {
self.end_timestamp
}
}
fn parse_json(raw_string: &str) -> Result<Json, ResponseError> {
return Json::from_str(raw_string).map_err(|_| ResponseError::ParseError)
}
pub struct DataResponse {
json: Json,
}
impl DataResponse {
pub fn new(json: Json) -> DataResponse {
DataResponse { json: json }
}
pub fn from_str(raw_response: &str) -> Result<DataResponse, ParseError> {
let json = try!(Json::from_str(raw_response));
Ok(DataResponse::new(json))
}
pub fn get_timestamp(&self) -> Option<Timestamp> {
self.json.find("updateTime").and_then(|t| t.as_i64())
}
pub fn is_full_response(&self) -> bool {
self.json.find("devices").is_some()
}
pub fn get_burglar_alarm(&self, device: u8, instance: u8) ->
Option<BurglarAlarmData> {
let name = format!(
"devices.{}.instances.{}.commandClasses.113.data.7",
device, instance);
let alarm_data = if self.is_full_response() {
let path = DataResponse::path_query_parts(&name);
self.json.find_path(&path)
} else {
self.json.find(&name)
};
alarm_data.and_then(|data| Some(BurglarAlarmData::new(&data)))
}
pub fn get_general_purpose_binary(&self, device: u8, instance: u8) ->
Option<GeneralPurposeBinaryData> {
let name = format!(
"devices.{}.instances.{}.commandClasses.48.data.1",
device, instance);
let sensor_data = if self.is_full_response() {
let path = DataResponse::path_query_parts(&name);
self.json.find_path(&path)
} else {
self.json.find(&name)
};
sensor_data.and_then(
|data| Some(GeneralPurposeBinaryData::new(&data)))
}
pub fn get_json(&self) -> &Json {
&self.json
}
fn path_query_parts(query: &str) -> Vec<&str> {
query.split(".").collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_timestamp_present() {
let json = "{ \"updateTime\": 1456036584 }";
let response = DataResponse::from_str(json).unwrap();
assert_eq!(1456036584i64, response.get_timestamp().unwrap());
}
#[test]
fn get_timestamp_absent() {
let json = "{}";
let response = DataResponse::from_str(json).unwrap();
assert!(response.get_timestamp().is_none());
}
#[test]
fn get_timestamp_invalid() {
let json = "{\"updateTime\": \"invalid\" }";
let response = DataResponse::from_str(json).unwrap();
assert!(response.get_timestamp().is_none());
}
#[test]
fn path_query_parts() {
let expected = vec!["devices", "1", "instances"];
let result = DataResponse::path_query_parts("devices.1.instances");
assert_eq!(expected, result);
}
const FULL_JSON : &'static str = "\
{ \
\"devices\": { \
\"4\": { \
\"instances\": { \
\"0\": { \
\"commandClasses\": {
\"113\": { \
\"name\": \"Alarm\", \
\"data\": { \
\"7\": { \
\"value\": null, \
\"type\": \"empty\", \
\"typeString\": { \
\"value\": \"Burglar\", \
\"type\": \"string\", \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1455606542 \
}, \
\"status\": { \
\"value\": 0, \
\"type\": \"int\", \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1456014517 \
}, \
\"eventMask\": { \
\"value\": 128, \
\"type\": \"int\", \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1455606542 \
}, \
\"event\": { \
\"value\": 7, \
\"type\": \"int\", \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1456014517 \
}, \
\"eventString\": { \
\"value\": \"Motion detected\", \
\"type\": \"string\", \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1456014517 \
}, \
\"eventParameters\": { \
\"value\": [ 7 ], \
\"type\": \"binary\", \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1456014517 \
}, \
\"eventSequence\": { \
\"value\": null, \
\"type\": \"empty\", \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1455606542 \
}, \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1456014517 \
}, \
\"invalidateTime\": 1455606416, \
\"updateTime\": 1455606417 \
} \
} \
} \
} \
} \
}, \
\"5\": { \
\"instances\": { \
\"0\": { \
\"commandClasses\": { \
\"48\": { \
\"name\": \"SensorBinary\", \
\"data\": { \
\"1\": { \
\"value\": null, \
\"type\": \"empty\", \
\"sensorTypeString\": { \
\"value\": \"General purpose\", \
\"type\": \"string\", \
\"invalidateTime\": 1456552384, \
\"updateTime\": 1456552385 \
}, \
\"level\": { \
\"value\": true, \
\"type\": \"bool\", \
\"invalidateTime\": 1456552384, \
\"updateTime\": 1456569899 \
}, \
\"invalidateTime\": 1456552384, \
\"updateTime\": 1456569899 \
}, \
\"invalidateTime\": 1456552382, \
\"updateTime\": 1456552383 \
} \
} \
} \
} \
} \
} \
}, \
\"updateTime\": 1456036584 \
} \
";
#[test]
fn is_full_response_on_full_payload() {
let response = DataResponse::from_str(FULL_JSON).unwrap();
assert!(response.is_full_response());
}
#[test]
fn get_burglar_alarm_on_full_payload() {
let response = DataResponse::from_str(FULL_JSON).unwrap();
let alarm = response.get_burglar_alarm(4, 0);
assert!(alarm.is_some());
}
#[test]
fn get_general_purpose_binary_data_on_full_payload() {
let response = DataResponse::from_str(FULL_JSON).unwrap();
let binary = response.get_general_purpose_binary(5, 0);
assert!(binary.is_some());
assert!(binary.unwrap().get_status().unwrap());
}
#[test]
fn get_timestamp_on_full_payload() {
let response = DataResponse::from_str(FULL_JSON).unwrap();
assert_eq!(1456036584i64, response.get_timestamp().unwrap());
}
const PARTIAL_JSON : &'static str = "\
{ \
\"devices.4.instances.0.commandClasses.113.data.7\": { \
\"value\": null, \
\"type\": \"empty\", \
\"typeString\": { \
\"value\": \"Burglar\", \
\"type\": \"string\", \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1455606542 \
}, \
\"status\": { \
\"value\": 0, \
\"type\": \"int\", \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1456014517 \
}, \
\"eventMask\": { \
\"value\": 128, \
\"type\": \"int\", \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1455606542 \
}, \
\"event\": { \
\"value\": 7, \
\"type\": \"int\", \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1456014517 \
}, \
\"eventString\": { \
\"value\": \"Motion detected\", \
\"type\": \"string\", \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1456014517 \
}, \
\"eventParameters\": { \
\"value\": [ 7 ], \
\"type\": \"binary\", \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1456014517 \
}, \
\"eventSequence\": { \
\"value\": null, \
\"type\": \"empty\", \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1455606542 \
}, \
\"invalidateTime\": 1455606541, \
\"updateTime\": 1456014517 \
}, \
\"devices.5.instances.0.commandClasses.48.data.1\": { \
\"value\": null, \
\"type\": \"empty\", \
\"sensorTypeString\": { \
\"value\": \"General purpose\", \
\"type\": \"string\", \
\"invalidateTime\": 1456552384, \
\"updateTime\": 1456552385 \
}, \
\"level\": { \
\"value\": false, \
\"type\": \"bool\", \
\"invalidateTime\": 1456552384, \
\"updateTime\": 1456553060 \
}, \
\"invalidateTime\": 1456552384, \
\"updateTime\": 1456553060 \
}, \
\"updateTime\": 1456036584 \
}";
#[test]
fn is_full_response_on_partial_payload() {
let response = DataResponse::from_str(PARTIAL_JSON).unwrap();
assert!(!response.is_full_response());
}
#[test]
fn get_burglar_alarm_on_partial_payload() {
let response = DataResponse::from_str(PARTIAL_JSON).unwrap();
let alarm = response.get_burglar_alarm(4, 0);
assert!(alarm.is_some());
}
#[test]
fn get_general_purpose_binary_data_on_partial_payload() {
let response = DataResponse::from_str(PARTIAL_JSON).unwrap();
let binary = response.get_general_purpose_binary(5, 0);
assert!(binary.is_some());
assert!(!binary.unwrap().get_status().unwrap());
}
#[test]
fn get_timestamp_on_partial_payload() {
let response = DataResponse::from_str(PARTIAL_JSON).unwrap();
assert_eq!(1456036584i64, response.get_timestamp().unwrap());
}
}