Developpement de la library pmoupnp
This commit is contained in:
13
pmoupnp/Cargo.toml
Normal file
13
pmoupnp/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "pmoupnp"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.42"
|
||||
url = "2.5.7"
|
||||
uuid = "1.18.1"
|
||||
hex = "0.4.3"
|
||||
base64 = "0.22.1"
|
||||
thiserror = "2.0.16"
|
||||
xmltree = "0.11.0"
|
||||
14
pmoupnp/src/lib.rs
Normal file
14
pmoupnp/src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
mod object_trait;
|
||||
|
||||
pub mod variable_types;
|
||||
pub mod state_variables;
|
||||
pub mod value_ranges;
|
||||
|
||||
pub use crate::object_trait::UpnpObject;
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UpnpObjectType {
|
||||
name: String,
|
||||
object_type: String,
|
||||
}
|
||||
39
pmoupnp/src/object_trait.rs
Normal file
39
pmoupnp/src/object_trait.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use xmltree::{Element, EmitterConfig};
|
||||
|
||||
use crate::UpnpObjectType;
|
||||
|
||||
|
||||
pub trait UpnpObject {
|
||||
fn as_upnp_object_type(&self) -> &UpnpObjectType;
|
||||
fn to_xml_element(&self) -> Element;
|
||||
|
||||
fn get_name(&self)-> &String {
|
||||
return &self.as_upnp_object_type().name;
|
||||
}
|
||||
|
||||
fn get_object_type(&self) -> &String {
|
||||
&self.as_upnp_object_type().object_type
|
||||
}
|
||||
|
||||
fn to_xml(&self) -> String {
|
||||
let elem = self.to_xml_element();
|
||||
|
||||
// Configurer l'indentation
|
||||
let config = EmitterConfig::new()
|
||||
.perform_indent(true)
|
||||
.indent_string(" "); // 2 espaces
|
||||
|
||||
// Sérialiser dans un buffer
|
||||
let mut buf = Vec::new();
|
||||
// écrire l'élément
|
||||
elem.write_with_config(&mut buf, config)
|
||||
.expect("Failed to write XML");
|
||||
|
||||
// Préfixer avec l'en-tête XML
|
||||
let mut xml_string = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".to_string();
|
||||
xml_string.push_str(&String::from_utf8(buf).expect("Invalid UTF-8"));
|
||||
|
||||
xml_string
|
||||
}
|
||||
|
||||
}
|
||||
28
pmoupnp/src/state_variables/errors.rs
Normal file
28
pmoupnp/src/state_variables/errors.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum StateVariableError {
|
||||
#[error("Conversion error: {0}")]
|
||||
ConversionError(String),
|
||||
|
||||
#[error("Validation error: {0}")]
|
||||
ValidationError(String),
|
||||
|
||||
#[error("Range error: {0}")]
|
||||
RangeError(String),
|
||||
|
||||
#[error("Type error: {0}")]
|
||||
TypeError(String),
|
||||
|
||||
#[error("Parse error: {0}")]
|
||||
ParseError(String),
|
||||
|
||||
#[error("Event condition error: {0}")]
|
||||
EventConditionError(String),
|
||||
|
||||
#[error("Arithmetic error: {0}")]
|
||||
ArithmeticError(String),
|
||||
|
||||
#[error("Unknown error: {0}")]
|
||||
Unknown(String),
|
||||
}
|
||||
58
pmoupnp/src/state_variables/instance_methods.rs
Normal file
58
pmoupnp/src/state_variables/instance_methods.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use xmltree::Element;
|
||||
|
||||
use crate::{
|
||||
state_variables::{StateVarInstance, StateVariable, UpnpVariable},
|
||||
variable_types::StateValue,
|
||||
UpnpObject,
|
||||
UpnpObjectType
|
||||
};
|
||||
|
||||
impl UpnpVariable for StateVarInstance {
|
||||
fn get_definition(&self) -> &StateVariable {
|
||||
return &self.definition;
|
||||
}
|
||||
}
|
||||
|
||||
impl UpnpObject for StateVarInstance {
|
||||
fn as_upnp_object_type(&self) -> &UpnpObjectType {
|
||||
return &self.object;
|
||||
}
|
||||
|
||||
fn to_xml_element(&self) -> Element {
|
||||
self.get_definition().to_xml_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl StateVarInstance {
|
||||
pub fn new(from: &StateVariable) -> Self {
|
||||
Self {
|
||||
object: UpnpObjectType {
|
||||
name: from.object.name.clone(),
|
||||
object_type: "StateVarInstance".to_string(),
|
||||
},
|
||||
definition: from.clone(),
|
||||
value: from.get_default(),
|
||||
old_value: from.get_default(),
|
||||
last_modified: Utc::now(),
|
||||
last_notification: Utc::now()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_value(&mut self, new_value: StateValue) {
|
||||
self.old_value = self.value.clone();
|
||||
self.value = new_value;
|
||||
self.last_modified = Utc::now(); // mise à jour automatique
|
||||
}
|
||||
|
||||
/// Accès à la valeur
|
||||
pub fn value(&self) -> &StateValue {
|
||||
&self.value
|
||||
}
|
||||
|
||||
/// Accès au timestamp
|
||||
pub fn last_modified(&self) -> DateTime<Utc> {
|
||||
self.last_modified
|
||||
}
|
||||
|
||||
}
|
||||
47
pmoupnp/src/state_variables/mod.rs
Normal file
47
pmoupnp/src/state_variables/mod.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
mod errors;
|
||||
mod variable_trait;
|
||||
mod variable_methods;
|
||||
mod instance_methods;
|
||||
|
||||
use std::{collections::HashMap, sync::{Arc, RwLock}};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
pub use errors::{StateVariableError};
|
||||
pub use crate::state_variables::variable_trait::UpnpVariable;
|
||||
|
||||
|
||||
use crate::{value_ranges::ValueRange, variable_types::{StateValue, StateVarType}, UpnpObjectType};
|
||||
|
||||
/// Type pour les fonctions de condition d'événement
|
||||
pub type StateConditionFunc = Arc<dyn Fn(&StateVarInstance) -> bool + Send + Sync>;
|
||||
|
||||
/// Type pour les fonctions de parsing de valeurs depuis des chaînes
|
||||
pub type StringValueParser = Arc<dyn Fn(&str) -> Result<StateValue, StateVariableError> + Send + Sync>;
|
||||
|
||||
/// Type pour les fonctions de sérialisation de valeurs vers des chaînes
|
||||
pub type ValueSerializer = Arc<dyn Fn(&StateValue) -> Result<String, StateVariableError> + Send + Sync>;
|
||||
|
||||
|
||||
pub struct StateVariable {
|
||||
object: UpnpObjectType,
|
||||
value_type: StateVarType,
|
||||
step: Option<StateValue>,
|
||||
modifiable: bool,
|
||||
event_conditions: Arc<RwLock<HashMap<String, StateConditionFunc>>>,
|
||||
description: String,
|
||||
default_value: Option<StateValue>,
|
||||
value_range: Option<ValueRange>,
|
||||
allowed_values: Arc<RwLock<Vec<StateValue>>>,
|
||||
send_events: bool,
|
||||
parse: Option<StringValueParser>,
|
||||
marshal: Option<ValueSerializer>,
|
||||
}
|
||||
|
||||
pub struct StateVarInstance {
|
||||
object: UpnpObjectType,
|
||||
definition: StateVariable,
|
||||
value: StateValue,
|
||||
old_value: StateValue,
|
||||
last_modified: DateTime<Utc>,
|
||||
last_notification: DateTime<Utc>,
|
||||
}
|
||||
353
pmoupnp/src/state_variables/variable_methods.rs
Normal file
353
pmoupnp/src/state_variables/variable_methods.rs
Normal file
@@ -0,0 +1,353 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use xmltree::{Element, XMLNode};
|
||||
|
||||
use crate::{
|
||||
UpnpObject, UpnpObjectType,
|
||||
state_variables::{
|
||||
StateConditionFunc, StateVariable, StringValueParser, ValueSerializer,
|
||||
variable_trait::UpnpVariable,
|
||||
},
|
||||
value_ranges::ValueRange,
|
||||
variable_types::{StateValue, StateValueError, StateVarType, UpnpVarType},
|
||||
};
|
||||
|
||||
impl UpnpObject for StateVariable {
|
||||
fn as_upnp_object_type(&self) -> &UpnpObjectType {
|
||||
return &self.object;
|
||||
}
|
||||
|
||||
fn to_xml_element(&self) -> Element {
|
||||
// Création de l'élément racine <stateVariable>
|
||||
let mut root = Element::new("stateVariable");
|
||||
root.attributes.insert(
|
||||
"sendEvents".to_string(),
|
||||
if self.send_events { "yes" } else { "no" }.to_string(),
|
||||
);
|
||||
|
||||
// <name>
|
||||
let mut name_elem = Element::new("name");
|
||||
name_elem
|
||||
.children
|
||||
.push(XMLNode::Text(self.get_name().clone()));
|
||||
|
||||
// <dataType>
|
||||
let mut datatype_elem = Element::new("dataType");
|
||||
datatype_elem
|
||||
.children
|
||||
.push(XMLNode::Text(self.value_type.to_string())); // StateVarType doit impl Display
|
||||
|
||||
// <defaultValue> si défini
|
||||
if let Some(default) = &self.default_value {
|
||||
let mut def_elem = Element::new("defaultValue");
|
||||
def_elem.children.push(XMLNode::Text(default.to_string()));
|
||||
root.children.push(XMLNode::Element(def_elem));
|
||||
}
|
||||
|
||||
// <allowedValueList> si défini
|
||||
if let Ok(av) = self.allowed_values.read() {
|
||||
if !av.is_empty() {
|
||||
let mut list_elem = Element::new("allowedValueList");
|
||||
for val in av.iter() {
|
||||
let mut val_elem = Element::new("allowedValue");
|
||||
val_elem.children.push(XMLNode::Text(val.to_string()));
|
||||
list_elem.children.push(XMLNode::Element(val_elem));
|
||||
}
|
||||
root.children.push(XMLNode::Element(list_elem));
|
||||
}
|
||||
}
|
||||
|
||||
// <allowedValueRange> si défini
|
||||
if let Some(range) = &self.value_range {
|
||||
let mut range_elem = Element::new("allowedValueRange");
|
||||
|
||||
let mut min_elem = Element::new("minimum");
|
||||
min_elem.children.push(XMLNode::Text(range.get_minimum().to_string()));
|
||||
range_elem.children.push(XMLNode::Element(min_elem));
|
||||
|
||||
let mut max_elem = Element::new("maximum");
|
||||
max_elem.children.push(XMLNode::Text(range.get_maximum().to_string()));
|
||||
range_elem.children.push(XMLNode::Element(max_elem));
|
||||
|
||||
if let Some(step) = &self.step {
|
||||
let mut step_elem = Element::new("step");
|
||||
step_elem.children.push(XMLNode::Text(step.to_string()));
|
||||
range_elem.children.push(XMLNode::Element(step_elem));
|
||||
}
|
||||
|
||||
root.children.push(XMLNode::Element(range_elem));
|
||||
}
|
||||
|
||||
// Ajouter les enfants communs
|
||||
root.children.push(XMLNode::Element(name_elem));
|
||||
root.children.push(XMLNode::Element(datatype_elem));
|
||||
|
||||
root
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for StateVariable {
|
||||
fn clone(&self) -> Self {
|
||||
// clone safe des structures protégées par RwLock en prenant un read lock
|
||||
let event_conditions_clone = {
|
||||
// si le lock est "poisoned" on panic - tu peux adapter la gestion si tu veux
|
||||
let guard = self
|
||||
.event_conditions
|
||||
.read()
|
||||
.expect("RwLock poisoned during clone");
|
||||
// nécessite que Key: Clone, Value: Clone
|
||||
Arc::new(RwLock::new(guard.clone()))
|
||||
};
|
||||
|
||||
let allowed_values_clone = {
|
||||
let guard = self
|
||||
.allowed_values
|
||||
.read()
|
||||
.expect("RwLock poisoned during clone");
|
||||
Arc::new(RwLock::new(guard.clone()))
|
||||
};
|
||||
|
||||
Self {
|
||||
object: self.object.clone(),
|
||||
value_type: self.value_type.clone(),
|
||||
step: self.step.clone(),
|
||||
modifiable: self.modifiable,
|
||||
event_conditions: event_conditions_clone,
|
||||
description: self.description.clone(),
|
||||
default_value: self.default_value.clone(),
|
||||
value_range: self.value_range.clone(),
|
||||
allowed_values: allowed_values_clone,
|
||||
send_events: self.send_events,
|
||||
// parse et marshal sont typiquement des Arc<dyn ...> — on clone l'Arc (shallow).
|
||||
// Deep-cloner une closure ou un trait-objet n'est pas possible en général.
|
||||
parse: self.parse.clone(),
|
||||
marshal: self.marshal.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpnpVarType for StateVariable {
|
||||
fn as_state_var_type(&self) -> StateVarType {
|
||||
self.value_type // utilise ton From<&StateValue> existant
|
||||
}
|
||||
}
|
||||
|
||||
impl UpnpVariable for StateVariable {
|
||||
fn get_definition(&self) -> &StateVariable {
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
impl StateVariable {
|
||||
pub fn new(vartype: StateVarType, name: String) -> StateVariable {
|
||||
Self {
|
||||
object: UpnpObjectType {
|
||||
name,
|
||||
object_type: "StateVariable".to_string(),
|
||||
},
|
||||
value_type: vartype.clone(),
|
||||
step: None,
|
||||
modifiable: true,
|
||||
event_conditions: Arc::new(RwLock::new(HashMap::new())),
|
||||
description: "".to_string(),
|
||||
default_value: None,
|
||||
value_range: None,
|
||||
allowed_values: Arc::new(RwLock::new(Vec::new())),
|
||||
send_events: false,
|
||||
parse: None,
|
||||
marshal: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_step(&mut self, step: StateValue) -> Result<(), StateValueError> {
|
||||
if self.as_state_var_type() != step.as_state_var_type() {
|
||||
return Err(StateValueError::TypeError("Bad step type".to_string()));
|
||||
}
|
||||
|
||||
self.step = Some(step);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_range(&mut self, min: &StateValue, max: &StateValue) -> Result<(), StateValueError> {
|
||||
if self.as_state_var_type() != min.as_state_var_type() {
|
||||
return Err(StateValueError::TypeError("Bad range type".to_string()));
|
||||
}
|
||||
|
||||
let range = ValueRange::new(min, max)?; // ? propage l'erreur si elle existe
|
||||
self.value_range = Some(range);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_minimum(&mut self, min: &StateValue) -> Result<(), StateValueError> {
|
||||
if !self.has_range() {
|
||||
return Err(StateValueError::RangeError(
|
||||
"No range specified for this variable".to_string(),
|
||||
));
|
||||
}
|
||||
if self
|
||||
.value_range
|
||||
.as_ref()
|
||||
.expect("Range is not defined")
|
||||
.as_state_var_type()
|
||||
!= min.as_state_var_type()
|
||||
{
|
||||
return Err(StateValueError::TypeError(
|
||||
"new minimum is not the same than state variable".to_string(),
|
||||
));
|
||||
}
|
||||
self.value_range
|
||||
.as_mut()
|
||||
.expect("Range is not defined")
|
||||
.set_minimum(min);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn update_maximum(&mut self, min: &StateValue) -> Result<(), StateValueError> {
|
||||
if !self.has_range() {
|
||||
return Err(StateValueError::RangeError(
|
||||
"No range specified for this variable".to_string(),
|
||||
));
|
||||
}
|
||||
if self
|
||||
.value_range
|
||||
.as_ref()
|
||||
.expect("Range is not defined")
|
||||
.as_state_var_type()
|
||||
!= min.as_state_var_type()
|
||||
{
|
||||
return Err(StateValueError::TypeError(
|
||||
"new minimum is not the same than state variable".to_string(),
|
||||
));
|
||||
}
|
||||
self.value_range
|
||||
.as_mut()
|
||||
.expect("Range is not defined")
|
||||
.set_maximum(min);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn get_range(&self) -> Option<&ValueRange> {
|
||||
return self.value_range.as_ref();
|
||||
}
|
||||
|
||||
pub fn set_modifiable(&mut self) {
|
||||
self.modifiable = true;
|
||||
}
|
||||
|
||||
pub fn set_not_modifiable(&mut self) {
|
||||
self.modifiable = false;
|
||||
}
|
||||
|
||||
pub fn add_event_condition(&self, name: String, func: StateConditionFunc) {
|
||||
// on lock en écriture
|
||||
let mut guard = self.event_conditions.write().unwrap();
|
||||
guard.insert(name, func);
|
||||
// le lock est automatiquement relâché ici (RAII)
|
||||
}
|
||||
|
||||
pub fn remove_event_condition(&self, name: &str) {
|
||||
let mut guard = self.event_conditions.write().unwrap();
|
||||
guard.remove(name);
|
||||
}
|
||||
|
||||
pub fn clear_event_conditions(&mut self) {
|
||||
let mut guard = self.event_conditions.write().unwrap();
|
||||
guard.clear()
|
||||
}
|
||||
|
||||
pub fn set_description(&mut self, description: String) {
|
||||
self.description = description;
|
||||
}
|
||||
|
||||
pub fn set_default(&mut self, value: StateValue) -> Result<(), StateValueError> {
|
||||
if self.as_state_var_type() != value.as_state_var_type() {
|
||||
return Err(StateValueError::TypeError(
|
||||
"value does not have the right type".to_string(),
|
||||
));
|
||||
}
|
||||
self.default_value = Some(value.clone());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn unset_default(&mut self) {
|
||||
self.default_value = None;
|
||||
}
|
||||
|
||||
pub fn extend_allowed_values(&mut self, values: &[StateValue]) -> Result<(), StateValueError> {
|
||||
let mut av = self
|
||||
.allowed_values
|
||||
.write()
|
||||
.map_err(|_| StateValueError::TypeError("Lock poisoned".to_string()))?;
|
||||
|
||||
for v in values {
|
||||
if self.as_state_var_type() == v.as_state_var_type() {
|
||||
av.push(v.clone());
|
||||
} else {
|
||||
return Err(StateValueError::TypeError(
|
||||
"new allowed value does not have the right type".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn push_allowed_value(&mut self, value: &StateValue) -> Result<(), StateValueError> {
|
||||
let mut av = self
|
||||
.allowed_values
|
||||
.write()
|
||||
.map_err(|_| StateValueError::TypeError("Lock poisoned".to_string()))?;
|
||||
|
||||
if self.as_state_var_type() == value.as_state_var_type() {
|
||||
av.push(value.clone());
|
||||
} else {
|
||||
return Err(StateValueError::TypeError(
|
||||
"new allowed value does not have the right type".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn set_send_notification(&mut self) {
|
||||
self.send_events = true;
|
||||
}
|
||||
|
||||
pub fn unset_send_notification(&mut self) {
|
||||
self.send_events = false;
|
||||
}
|
||||
|
||||
pub fn set_value_parseur(&mut self, parser: StringValueParser) -> Result<(), StateValueError> {
|
||||
if self.as_state_var_type() == StateVarType::String {
|
||||
self.parse = Some(parser);
|
||||
return Ok(());
|
||||
}
|
||||
return Err(StateValueError::TypeError(
|
||||
"Only String variables can have a parser".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn unset_value_parser(&mut self) {
|
||||
self.parse = None;
|
||||
}
|
||||
|
||||
pub fn set_value_marshaler(
|
||||
&mut self,
|
||||
marshaler: ValueSerializer,
|
||||
) -> Result<(), StateValueError> {
|
||||
if self.as_state_var_type() == StateVarType::String {
|
||||
self.marshal = Some(marshaler);
|
||||
return Ok(());
|
||||
}
|
||||
return Err(StateValueError::TypeError(
|
||||
"Only String variables can have a marshaler".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn unset_value_marshaler(&mut self) {
|
||||
self.marshal = None;
|
||||
}
|
||||
}
|
||||
73
pmoupnp/src/state_variables/variable_trait.rs
Normal file
73
pmoupnp/src/state_variables/variable_trait.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use crate::{
|
||||
state_variables::StateVariable,
|
||||
variable_types::{StateValue, UpnpVarType},
|
||||
};
|
||||
|
||||
pub trait UpnpVariable {
|
||||
fn get_definition(&self) -> &StateVariable;
|
||||
|
||||
fn has_step(&self) -> bool {
|
||||
return self.get_definition().step.is_some();
|
||||
}
|
||||
|
||||
fn get_step(&self) -> Option<StateValue> {
|
||||
return self.get_definition().step.clone();
|
||||
}
|
||||
|
||||
fn has_range(&self) -> bool {
|
||||
return self.get_definition().value_range.is_some();
|
||||
}
|
||||
|
||||
fn is_modifiable(&self) -> bool {
|
||||
return self.get_definition().modifiable;
|
||||
}
|
||||
|
||||
fn has_event_conditions(&self) -> bool {
|
||||
return self.get_definition().event_conditions.read().unwrap().len() > 0;
|
||||
}
|
||||
|
||||
fn has_event_condition(&self, name: &String) -> bool {
|
||||
let guard = self.get_definition().event_conditions.read().unwrap();
|
||||
return guard.contains_key(name);
|
||||
}
|
||||
|
||||
fn has_description(&self) -> bool {
|
||||
return !String::is_empty(&self.get_definition().description);
|
||||
}
|
||||
|
||||
fn get_description(&self) -> String {
|
||||
return self.get_definition().description.clone();
|
||||
}
|
||||
|
||||
fn has_default(&self) -> bool {
|
||||
return self.get_definition().default_value.is_some();
|
||||
}
|
||||
|
||||
fn get_default(&self) -> StateValue {
|
||||
self.get_definition()
|
||||
.default_value
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.get_definition().as_state_var_type().default_value())
|
||||
}
|
||||
|
||||
fn has_allowed_values(&self) -> bool {
|
||||
return self.get_definition().allowed_values.read().unwrap().len() > 0;
|
||||
}
|
||||
|
||||
fn is_an_allowed_values(&self, value: &StateValue) -> bool {
|
||||
let guard = self.get_definition().allowed_values.read().unwrap();
|
||||
return guard.contains(value);
|
||||
}
|
||||
|
||||
fn is_sending_notification(&self) -> bool {
|
||||
self.get_definition().send_events
|
||||
}
|
||||
|
||||
fn has_value_parser(&self) -> bool {
|
||||
self.get_definition().parse.is_some()
|
||||
}
|
||||
|
||||
fn has_value_marshaler(&self) -> bool {
|
||||
self.get_definition().marshal.is_some()
|
||||
}
|
||||
}
|
||||
69
pmoupnp/src/value_ranges/methods.rs
Normal file
69
pmoupnp/src/value_ranges/methods.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::{
|
||||
value_ranges::ValueRange,
|
||||
variable_types::{StateValue, StateValueError, StateVarType, UpnpVarType},
|
||||
};
|
||||
|
||||
impl UpnpVarType for ValueRange {
|
||||
fn as_state_var_type(&self) -> StateVarType {
|
||||
self.min.as_state_var_type() // utilise ton From<&StateValue> existant
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueRange {
|
||||
pub fn new(min: &StateValue, max: &StateValue) -> Result<Self, StateValueError> {
|
||||
if min.as_state_var_type() != max.as_state_var_type() {
|
||||
return Err(StateValueError::TypeError(
|
||||
"min and max do not belong the same time".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Vérifier que min <= max
|
||||
if let Some(cmp) = min.partial_cmp(max) {
|
||||
if cmp == Ordering::Greater {
|
||||
return Err(StateValueError::RangeError(
|
||||
"Minimum cannot be greater than maximum".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
min: min.clone(),
|
||||
max: max.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_minimum(self: &ValueRange) -> StateValue {
|
||||
return self.min.clone();
|
||||
}
|
||||
|
||||
pub fn set_minimum(&mut self, value: &StateValue) {
|
||||
self.min = value.clone()
|
||||
}
|
||||
|
||||
pub fn get_maximum(self: &ValueRange) -> StateValue {
|
||||
return self.max.clone();
|
||||
}
|
||||
|
||||
pub fn set_maximum(&mut self, value: &StateValue) {
|
||||
self.max = value.clone()
|
||||
}
|
||||
|
||||
pub fn is_in_range(&self, value: &StateValue) -> bool {
|
||||
if self.as_state_var_type() == value.as_state_var_type()
|
||||
&& let Some(cmp) = self.min.partial_cmp(value)
|
||||
{
|
||||
if cmp == Ordering::Greater {
|
||||
return false;
|
||||
}
|
||||
if let Some(cmp2) = self.max.partial_cmp(value) {
|
||||
if cmp2 == Ordering::Less {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
10
pmoupnp/src/value_ranges/mod.rs
Normal file
10
pmoupnp/src/value_ranges/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
mod methods;
|
||||
|
||||
|
||||
use crate::variable_types::StateValue;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ValueRange {
|
||||
min: StateValue,
|
||||
max: StateValue,
|
||||
}
|
||||
37
pmoupnp/src/variable_types/cast.rs
Normal file
37
pmoupnp/src/variable_types/cast.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use crate::variable_types::{StateValue, StateVarType, StateValueError};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
impl StateValue {
|
||||
pub fn try_cast(&self, target: StateVarType) -> Result<StateValue, StateValueError> {
|
||||
|
||||
let source = StateVarType::from(self);
|
||||
|
||||
// Identité (même type)
|
||||
if source == target {
|
||||
return Ok(self.clone());
|
||||
}
|
||||
|
||||
match (self, target) {
|
||||
(val, StateVarType::String) => Ok(StateValue::String(val.to_string())),
|
||||
|
||||
(_,StateVarType::UI1) => Ok(StateValue::UI1(u8::try_from(self)?)),
|
||||
(_,StateVarType::UI2) => Ok(StateValue::UI2(u16::try_from(self)?)),
|
||||
(_,StateVarType::UI4) => Ok(StateValue::UI4(u32::try_from(self)?)),
|
||||
(_,StateVarType::I1) => Ok(StateValue::I1(i8::try_from(self)?)),
|
||||
(_,StateVarType::I2) => Ok(StateValue::I2(i16::try_from(self)?)),
|
||||
(_,StateVarType::I4) => Ok(StateValue::I4(i32::try_from(self)?)),
|
||||
(_,StateVarType::Int) => Ok(StateValue::Int(i32::try_from(self)?)),
|
||||
|
||||
(_,StateVarType::R8) => Ok(StateValue::R8(f64::try_from(self)?)),
|
||||
(_,StateVarType::Number) => Ok(StateValue::Number(f64::try_from(self)?)),
|
||||
(_,StateVarType::Fixed14_4) => Ok(StateValue::Fixed14_4(f64::try_from(self)?)),
|
||||
(_,StateVarType::R4) => Ok(StateValue::R4(f32::try_from(self)?)),
|
||||
|
||||
// --- Pas encore implémenté pour les autres types ---
|
||||
(val, target) => Err(StateValueError::TypeError(format!(
|
||||
"Cannot cast {:?} to {:?}",
|
||||
val, target
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
43
pmoupnp/src/variable_types/default_value.rs
Normal file
43
pmoupnp/src/variable_types/default_value.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use chrono::{DateTime, FixedOffset, NaiveDate, NaiveTime};
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::variable_types::{StateValue, StateVarType};
|
||||
|
||||
impl StateVarType {
|
||||
pub fn default_value(&self) -> StateValue {
|
||||
match self {
|
||||
StateVarType::UI1 => StateValue::UI1(0),
|
||||
StateVarType::UI2 => StateValue::UI2(0),
|
||||
StateVarType::UI4 => StateValue::UI4(0),
|
||||
StateVarType::I1 => StateValue::I1(0),
|
||||
StateVarType::I2 => StateValue::I2(0),
|
||||
StateVarType::I4 => StateValue::I4(0),
|
||||
StateVarType::Int => StateValue::Int(0),
|
||||
StateVarType::R4 => StateValue::R4(0.0),
|
||||
StateVarType::R8 => StateValue::R8(0.0),
|
||||
StateVarType::Number => StateValue::Number(0.0),
|
||||
StateVarType::Fixed14_4 => StateValue::Fixed14_4(0.0),
|
||||
StateVarType::Char => StateValue::Char('\0'),
|
||||
StateVarType::String => StateValue::String(String::new()),
|
||||
StateVarType::Boolean => StateValue::Boolean(false),
|
||||
StateVarType::BinBase64 => StateValue::BinBase64(String::new()),
|
||||
StateVarType::BinHex => StateValue::BinHex(String::new()),
|
||||
StateVarType::Date => StateValue::Date(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()),
|
||||
StateVarType::DateTime => StateValue::DateTime(DateTime::from_timestamp(0, 0).unwrap().naive_utc().into()),
|
||||
StateVarType::DateTimeTZ => StateValue::DateTimeTZ(
|
||||
DateTime::from_naive_utc_and_offset(
|
||||
DateTime::from_timestamp(0, 0).unwrap().naive_utc(),
|
||||
FixedOffset::east_opt(0).unwrap())
|
||||
),
|
||||
StateVarType::Time => StateValue::Time(NaiveTime::from_hms_opt(0, 0, 0).unwrap()),
|
||||
StateVarType::TimeTZ => StateValue::TimeTZ(
|
||||
DateTime::from_naive_utc_and_offset(
|
||||
DateTime::from_timestamp(0, 0).unwrap().naive_utc(),
|
||||
FixedOffset::east_opt(0).unwrap())
|
||||
),
|
||||
StateVarType::UUID => StateValue::UUID(Uuid::nil()),
|
||||
StateVarType::URI => StateValue::URI(Url::parse("http://localhost").unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
35
pmoupnp/src/variable_types/display_type.rs
Normal file
35
pmoupnp/src/variable_types/display_type.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::variable_types::StateVarType;
|
||||
|
||||
|
||||
impl fmt::Display for StateVarType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s = match self {
|
||||
StateVarType::UI1 => "ui1",
|
||||
StateVarType::UI2 => "ui2",
|
||||
StateVarType::UI4 => "ui4",
|
||||
StateVarType::I1 => "i1",
|
||||
StateVarType::I2 => "i2",
|
||||
StateVarType::I4 => "i4",
|
||||
StateVarType::Int => "int",
|
||||
StateVarType::R4 => "r4",
|
||||
StateVarType::R8 => "r8",
|
||||
StateVarType::Number => "number",
|
||||
StateVarType::Fixed14_4 => "fixed.14.4",
|
||||
StateVarType::Char => "char",
|
||||
StateVarType::String => "string",
|
||||
StateVarType::Boolean => "boolean",
|
||||
StateVarType::BinBase64 => "bin.base64",
|
||||
StateVarType::BinHex => "bin.hex",
|
||||
StateVarType::Date => "date",
|
||||
StateVarType::DateTime => "dateTime",
|
||||
StateVarType::DateTimeTZ => "dateTime.tz",
|
||||
StateVarType::Time => "time",
|
||||
StateVarType::TimeTZ => "time.tz",
|
||||
StateVarType::UUID => "uuid",
|
||||
StateVarType::URI => "uri",
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
45
pmoupnp/src/variable_types/display_value.rs
Normal file
45
pmoupnp/src/variable_types/display_value.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use std::fmt;
|
||||
use base64::engine::general_purpose;
|
||||
use base64::Engine;
|
||||
|
||||
use crate::variable_types::StateValue;
|
||||
|
||||
|
||||
impl fmt::Display for StateValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
// Numériques
|
||||
StateValue::UI1(v) => write!(f, "{}", v),
|
||||
StateValue::UI2(v) => write!(f, "{}", v),
|
||||
StateValue::UI4(v) => write!(f, "{}", v),
|
||||
StateValue::I1(v) => write!(f, "{}", v),
|
||||
StateValue::I2(v) => write!(f, "{}", v),
|
||||
StateValue::I4(v) => write!(f, "{}", v),
|
||||
StateValue::Int(v) => write!(f, "{}", v),
|
||||
StateValue::R4(v) => write!(f, "{}", v),
|
||||
StateValue::R8(v) => write!(f, "{}", v),
|
||||
StateValue::Number(v) => write!(f, "{}", v),
|
||||
StateValue::Fixed14_4(v) => write!(f, "{}", v),
|
||||
|
||||
// Types déjà Display
|
||||
StateValue::Char(v) => write!(f, "{}", v),
|
||||
StateValue::String(v) => write!(f, "{}", v),
|
||||
StateValue::UUID(v) => write!(f, "{}", v),
|
||||
StateValue::URI(v) => write!(f, "{}", v),
|
||||
|
||||
// Booléen : 1 ou 0
|
||||
StateValue::Boolean(v) => write!(f, "{}", if *v { "1" } else { "0" }),
|
||||
|
||||
// Encodages binaires
|
||||
StateValue::BinBase64(v) => write!(f, "{}", general_purpose::URL_SAFE.encode(v)),
|
||||
StateValue::BinHex(v) => write!(f, "{}", hex::encode(v)),
|
||||
|
||||
// Dates et temps
|
||||
StateValue::Date(v) => write!(f, "{}", v.format("%Y-%m-%d")),
|
||||
StateValue::DateTime(v) => write!(f, "{}", v.format("%Y-%m-%dT%H:%M:%S")),
|
||||
StateValue::DateTimeTZ(v) => write!(f, "{}", v.to_rfc3339()),
|
||||
StateValue::Time(v) => write!(f, "{}", v.format("%H:%M:%S")),
|
||||
StateValue::TimeTZ(v) => write!(f, "{}", v.format("%H:%M:%S%z")),
|
||||
}
|
||||
}
|
||||
}
|
||||
28
pmoupnp/src/variable_types/errors.rs
Normal file
28
pmoupnp/src/variable_types/errors.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum StateValueError {
|
||||
#[error("Conversion error: {0}")]
|
||||
ConversionError(String),
|
||||
|
||||
#[error("Validation error: {0}")]
|
||||
ValidationError(String),
|
||||
|
||||
#[error("Range error: {0}")]
|
||||
RangeError(String),
|
||||
|
||||
#[error("Type error: {0}")]
|
||||
TypeError(String),
|
||||
|
||||
#[error("Parse error: {0}")]
|
||||
ParseError(String),
|
||||
|
||||
#[error("Event condition error: {0}")]
|
||||
EventConditionError(String),
|
||||
|
||||
#[error("Arithmetic error: {0}")]
|
||||
ArithmeticError(String),
|
||||
|
||||
#[error("Unknown error: {0}")]
|
||||
Unknown(String),
|
||||
}
|
||||
36
pmoupnp/src/variable_types/fromstr.rs
Normal file
36
pmoupnp/src/variable_types/fromstr.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use std::str::FromStr;
|
||||
use crate::variable_types::StateVarType;
|
||||
|
||||
|
||||
impl FromStr for StateVarType {
|
||||
type Err = String; // Type d'erreur personnalisé
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"ui1" => Ok(StateVarType::UI1),
|
||||
"ui2" => Ok(StateVarType::UI2),
|
||||
"ui4" => Ok(StateVarType::UI4),
|
||||
"i1" => Ok(StateVarType::I1),
|
||||
"i2" => Ok(StateVarType::I2),
|
||||
"i4" => Ok(StateVarType::I4),
|
||||
"int" => Ok(StateVarType::Int),
|
||||
"r4" => Ok(StateVarType::R4),
|
||||
"r8" => Ok(StateVarType::R8),
|
||||
"number" => Ok(StateVarType::Number),
|
||||
"fixed.14.4" => Ok(StateVarType::Fixed14_4),
|
||||
"char" => Ok(StateVarType::Char),
|
||||
"string" => Ok(StateVarType::String),
|
||||
"boolean" => Ok(StateVarType::Boolean),
|
||||
"bin.base64" => Ok(StateVarType::BinBase64),
|
||||
"bin.hex" => Ok(StateVarType::BinHex),
|
||||
"date" => Ok(StateVarType::Date),
|
||||
"datetime" => Ok(StateVarType::DateTime),
|
||||
"datetime.tz" => Ok(StateVarType::DateTimeTZ),
|
||||
"time" => Ok(StateVarType::Time),
|
||||
"time.tz" => Ok(StateVarType::TimeTZ),
|
||||
"uuid" => Ok(StateVarType::UUID),
|
||||
"uri" => Ok(StateVarType::URI),
|
||||
_ => Err(format!("Type inconnu: {}", s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
96
pmoupnp/src/variable_types/mod.rs
Normal file
96
pmoupnp/src/variable_types/mod.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
mod type_methods;
|
||||
mod value_methods;
|
||||
mod type_trait;
|
||||
mod value_trait;
|
||||
mod fromstr;
|
||||
mod cast;
|
||||
mod display_type;
|
||||
mod display_value;
|
||||
mod default_value;
|
||||
mod errors;
|
||||
|
||||
mod values_from_type;
|
||||
|
||||
mod values_from_u8;
|
||||
mod values_from_u16;
|
||||
mod values_from_u32;
|
||||
mod values_from_i8;
|
||||
mod values_from_i16;
|
||||
mod values_from_i32;
|
||||
mod values_from_i64;
|
||||
|
||||
mod values_from_f32;
|
||||
mod values_from_f64;
|
||||
|
||||
mod values_from_uri;
|
||||
mod values_from_uuid;
|
||||
mod values_from_naivedate;
|
||||
mod values_from_naivedatetime;
|
||||
mod values_from_naivetime;
|
||||
mod values_from_datetime;
|
||||
mod values_from_vec_u8;
|
||||
|
||||
use std::fmt::{Debug};
|
||||
|
||||
use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime};
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use errors::StateValueError;
|
||||
pub use type_trait::UpnpVarType;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum StateVarType {
|
||||
UI1, // Unsigned 8-bit integer
|
||||
UI2, // Unsigned 16-bit integer
|
||||
UI4, // Unsigned 32-bit integer
|
||||
I1, // Signed 8-bit integer
|
||||
I2, // Signed 16-bit integer
|
||||
I4, // Signed 32-bit integer
|
||||
Int, // Synonymous with i4
|
||||
R4, // 32-bit floating point
|
||||
R8, // 64-bit floating point
|
||||
Number, // Synonymous with r8
|
||||
Fixed14_4, // Fixed-point decimal
|
||||
Char, // Single Unicode character
|
||||
String, // Character string
|
||||
Boolean, // Boolean value
|
||||
BinBase64, // Base64-encoded binary
|
||||
BinHex, // Hex-encoded binary
|
||||
Date, // Date (YYYY-MM-DD)
|
||||
DateTime, // DateTime without timezone
|
||||
DateTimeTZ, // DateTime with timezone
|
||||
Time, // Time without timezone
|
||||
TimeTZ, // Time with timezone
|
||||
UUID, // Universally unique identifier
|
||||
URI, // Uniform Resource Identifier
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StateValue {
|
||||
UI1(u8),
|
||||
UI2(u16),
|
||||
UI4(u32),
|
||||
I1(i8),
|
||||
I2(i16),
|
||||
I4(i32),
|
||||
Int(i32),
|
||||
R4(f32),
|
||||
R8(f64),
|
||||
Number(f64),
|
||||
Fixed14_4(f64),
|
||||
Char(char),
|
||||
String(String),
|
||||
Boolean(bool),
|
||||
BinBase64(String),
|
||||
BinHex(String),
|
||||
Date(NaiveDate),
|
||||
DateTime(NaiveDateTime),
|
||||
DateTimeTZ(DateTime<FixedOffset>),
|
||||
Time(NaiveTime),
|
||||
TimeTZ(DateTime<FixedOffset>),
|
||||
UUID(Uuid),
|
||||
URI(Url),
|
||||
}
|
||||
|
||||
|
||||
109
pmoupnp/src/variable_types/type_methods.rs
Normal file
109
pmoupnp/src/variable_types/type_methods.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
use crate::variable_types::{type_trait::UpnpVarType, StateVarType};
|
||||
|
||||
impl UpnpVarType for StateVarType {
|
||||
fn as_state_var_type(&self) -> StateVarType {
|
||||
*self
|
||||
}
|
||||
|
||||
fn bit_size(&self) -> Option<usize> {
|
||||
match self {
|
||||
StateVarType::UI1 | StateVarType::I1 => Some(8),
|
||||
StateVarType::UI2 | StateVarType::I2 => Some(16),
|
||||
StateVarType::UI4 | StateVarType::I4 | StateVarType::Int | StateVarType::R4 => Some(32),
|
||||
StateVarType::R8 | StateVarType::Number | StateVarType::Fixed14_4 => Some(64),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_numeric(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
StateVarType::UI1
|
||||
| StateVarType::UI2
|
||||
| StateVarType::UI4
|
||||
| StateVarType::I1
|
||||
| StateVarType::I2
|
||||
| StateVarType::I4
|
||||
| StateVarType::Int
|
||||
| StateVarType::R4
|
||||
| StateVarType::R8
|
||||
| StateVarType::Number
|
||||
| StateVarType::Fixed14_4
|
||||
)
|
||||
}
|
||||
|
||||
fn is_integer(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
StateVarType::UI1
|
||||
| StateVarType::UI2
|
||||
| StateVarType::UI4
|
||||
| StateVarType::I1
|
||||
| StateVarType::I2
|
||||
| StateVarType::I4
|
||||
| StateVarType::Int
|
||||
)
|
||||
}
|
||||
|
||||
fn is_signed_int(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
StateVarType::I1 | StateVarType::I2 | StateVarType::I4 | StateVarType::Int
|
||||
)
|
||||
}
|
||||
|
||||
fn is_unsigned_int(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
StateVarType::UI1 | StateVarType::UI2 | StateVarType::UI4
|
||||
)
|
||||
}
|
||||
|
||||
fn is_float(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
StateVarType::R4
|
||||
| StateVarType::R8
|
||||
| StateVarType::Number
|
||||
| StateVarType::Fixed14_4
|
||||
)
|
||||
}
|
||||
|
||||
fn is_bool(&self) -> bool {
|
||||
matches!(self, StateVarType::Boolean)
|
||||
}
|
||||
|
||||
fn is_string(&self) -> bool {
|
||||
matches!(self, StateVarType::String
|
||||
| StateVarType::Char
|
||||
| StateVarType::BinHex
|
||||
| StateVarType::BinBase64)
|
||||
}
|
||||
|
||||
fn is_time(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
StateVarType::Date
|
||||
| StateVarType::DateTime
|
||||
| StateVarType::DateTimeTZ
|
||||
| StateVarType::Time
|
||||
| StateVarType::TimeTZ
|
||||
)
|
||||
}
|
||||
|
||||
fn is_uuid(&self) -> bool {
|
||||
matches!(self, StateVarType::UUID)
|
||||
}
|
||||
|
||||
fn is_uri(&self) -> bool {
|
||||
matches!(self, StateVarType::URI)
|
||||
}
|
||||
|
||||
fn is_binary(&self) -> bool {
|
||||
matches!(self, StateVarType::BinBase64 | StateVarType::BinHex)
|
||||
}
|
||||
|
||||
fn is_comparable(&self) -> bool {
|
||||
!self.is_binary()
|
||||
}
|
||||
}
|
||||
57
pmoupnp/src/variable_types/type_trait.rs
Normal file
57
pmoupnp/src/variable_types/type_trait.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use crate::variable_types::StateVarType;
|
||||
|
||||
pub trait UpnpVarType {
|
||||
fn as_state_var_type(&self) -> StateVarType;
|
||||
|
||||
fn bit_size(&self) -> Option<usize> {
|
||||
self.as_state_var_type().bit_size()
|
||||
}
|
||||
|
||||
fn is_numeric(&self) -> bool {
|
||||
self.as_state_var_type().is_numeric()
|
||||
}
|
||||
|
||||
fn is_integer(&self) -> bool {
|
||||
self.as_state_var_type().is_integer()
|
||||
}
|
||||
|
||||
fn is_signed_int(&self) -> bool {
|
||||
self.as_state_var_type().is_signed_int()
|
||||
}
|
||||
|
||||
fn is_unsigned_int(&self) -> bool {
|
||||
self.as_state_var_type().is_unsigned_int()
|
||||
}
|
||||
|
||||
fn is_float(&self) -> bool {
|
||||
self.as_state_var_type().is_float()
|
||||
}
|
||||
|
||||
fn is_bool(&self) -> bool {
|
||||
self.as_state_var_type().is_bool()
|
||||
}
|
||||
|
||||
fn is_string(&self) -> bool {
|
||||
self.as_state_var_type().is_string()
|
||||
}
|
||||
|
||||
fn is_time(&self) -> bool {
|
||||
self.as_state_var_type().is_time()
|
||||
}
|
||||
|
||||
fn is_uuid(&self) -> bool {
|
||||
self.as_state_var_type().is_uuid()
|
||||
}
|
||||
|
||||
fn is_uri(&self) -> bool {
|
||||
self.as_state_var_type().is_uri()
|
||||
}
|
||||
|
||||
fn is_binary(&self) -> bool {
|
||||
self.as_state_var_type().is_binary()
|
||||
}
|
||||
|
||||
fn is_comparable(&self) -> bool {
|
||||
self.as_state_var_type().is_comparable()
|
||||
}
|
||||
}
|
||||
89
pmoupnp/src/variable_types/value_methods.rs
Normal file
89
pmoupnp/src/variable_types/value_methods.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::variable_types::{StateValue, StateVarType, type_trait::UpnpVarType};
|
||||
|
||||
|
||||
impl UpnpVarType for StateValue {
|
||||
fn as_state_var_type(&self) -> StateVarType {
|
||||
StateVarType::from(self) // utilise ton From<&StateValue> existant
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for StateValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(a, b) if a.is_integer() && b.is_integer() => {
|
||||
if let (Ok(ia), Ok(ib)) = (i64::try_from(a), i64::try_from(b)) {
|
||||
return ia == ib;
|
||||
};
|
||||
return false;
|
||||
}
|
||||
(a, b) if a.is_float() && b.is_float() => {
|
||||
if let (Ok(a), Ok(b)) = (f64::try_from(self), f64::try_from(other)) {
|
||||
return a == b; // NaN respecte la sémantique IEEE (NaN != NaN)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
(a, b) if a.is_string() && b.is_string() => {
|
||||
let (a, b) = (self.to_string(), other.to_string());
|
||||
return a == b; // NaN respecte la sémantique IEEE (NaN != NaN)
|
||||
}
|
||||
(StateValue::Date(a), StateValue::Date(b)) => {
|
||||
return a == b;
|
||||
}
|
||||
(StateValue::Time(a), StateValue::Time(b)) => {
|
||||
return a == b;
|
||||
}
|
||||
(StateValue::DateTime(a), StateValue::DateTime(b)) => {
|
||||
return a == b;
|
||||
}
|
||||
(StateValue::DateTimeTZ(a), StateValue::DateTimeTZ(b)) => {
|
||||
return a == b;
|
||||
}
|
||||
(StateValue::TimeTZ(a), StateValue::TimeTZ(b)) => {
|
||||
return a == b;
|
||||
}
|
||||
|
||||
(_, _) => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for StateValue {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
match (self, other) {
|
||||
(a, b) if a.is_integer() && b.is_integer() => {
|
||||
if let (Ok(ia), Ok(ib)) = (i64::try_from(a), i64::try_from(b)) {
|
||||
return Some(ia.cmp(&ib));
|
||||
};
|
||||
return None;
|
||||
}
|
||||
(a, b) if a.is_float() && b.is_float() => {
|
||||
if let (Ok(a), Ok(b)) = (f64::try_from(self), f64::try_from(other)) {
|
||||
return a.partial_cmp(&b); // NaN respecte la sémantique IEEE (NaN != NaN)
|
||||
}
|
||||
return None;
|
||||
}
|
||||
(a, b) if a.is_string() && b.is_string() => {
|
||||
let (a, b) = (self.to_string(), other.to_string());
|
||||
return Some(a.cmp(&b)); // NaN respecte la sémantique IEEE (NaN != NaN)
|
||||
}
|
||||
(StateValue::Date(a), StateValue::Date(b)) => {
|
||||
return Some(a.cmp(&b));
|
||||
}
|
||||
(StateValue::Time(a), StateValue::Time(b)) => {
|
||||
return Some(a.cmp(&b));
|
||||
}
|
||||
(StateValue::DateTime(a), StateValue::DateTime(b)) => {
|
||||
return Some(a.cmp(&b));
|
||||
}
|
||||
(StateValue::DateTimeTZ(a), StateValue::DateTimeTZ(b)) => {
|
||||
return Some(a.cmp(&b));
|
||||
}
|
||||
(StateValue::TimeTZ(a), StateValue::TimeTZ(b)) => {
|
||||
return Some(a.cmp(&b));
|
||||
}
|
||||
(_, _) => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
2
pmoupnp/src/variable_types/value_trait.rs
Normal file
2
pmoupnp/src/variable_types/value_trait.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub trait UpnpValue {
|
||||
}
|
||||
33
pmoupnp/src/variable_types/values_from_datetime.rs
Normal file
33
pmoupnp/src/variable_types/values_from_datetime.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use std::convert::TryFrom;
|
||||
use chrono::{DateTime,FixedOffset};
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
impl TryFrom<&StateValue> for DateTime<FixedOffset> {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
StateValue::DateTimeTZ(v) => Ok(v.clone()),
|
||||
StateValue::TimeTZ(v) => Ok(v.clone()),
|
||||
StateValue::String(v) => DateTime::parse_from_rfc3339(v)
|
||||
.map_err(|e| StateValueError::ParseError(format!("Cannot parse DateTimeTZ from string '{}': {}", v, e))),
|
||||
_ => Err(StateValueError::TypeError("Cannot cast to DateTime<FixedOffset>".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StateValue> for DateTime<FixedOffset> {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
DateTime::<FixedOffset>::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DateTime<FixedOffset>> for StateValue {
|
||||
|
||||
fn from(value: DateTime<FixedOffset>) -> Self {
|
||||
StateValue::DateTimeTZ(value)
|
||||
}
|
||||
}
|
||||
|
||||
82
pmoupnp/src/variable_types/values_from_f32.rs
Normal file
82
pmoupnp/src/variable_types/values_from_f32.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
|
||||
impl TryFrom<&StateValue> for f32 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
|
||||
const MAX_EXACT: i32 = 1 << f32::MANTISSA_DIGITS;
|
||||
match value {
|
||||
// --- Signed integers ---
|
||||
StateValue::I1(v) => Ok(*v as f32),
|
||||
StateValue::I2(v) => Ok(*v as f32),
|
||||
StateValue::I4(v)
|
||||
if *v > -MAX_EXACT &&
|
||||
*v < MAX_EXACT => Ok(*v as f32),
|
||||
StateValue::Int(v)
|
||||
if *v >= -MAX_EXACT &&
|
||||
*v <= MAX_EXACT as i32 => Ok(*v as f32),
|
||||
|
||||
// --- Unsigned integers ---
|
||||
StateValue::UI1(v) => Ok(*v as f32),
|
||||
StateValue::UI2(v) => Ok(*v as f32),
|
||||
StateValue::UI4(v) if *v <= MAX_EXACT as u32 => Ok(*v as f32),
|
||||
StateValue::UI4(_) => Err(StateValueError::TypeError(
|
||||
"Cannot cast UI4 to f32: out of range".into(),
|
||||
)),
|
||||
|
||||
// --- Floats ---
|
||||
StateValue::R4(v) => Ok(*v), // déjà un f32
|
||||
StateValue::R8(v)
|
||||
if ! v.is_finite() ||
|
||||
(*v <= f32::MAX as f64 &&
|
||||
*v >= f32::MIN as f64) => Ok(*v as f32),
|
||||
StateValue::R8(_) => Err(StateValueError::TypeError(
|
||||
"Cannot cast R8 to f32: out of range".into(),
|
||||
)),
|
||||
StateValue::Number(v)
|
||||
if ! v.is_finite() ||
|
||||
(*v <= f32::MAX as f64 &&
|
||||
*v >= f32::MIN as f64) => Ok(*v as f32),
|
||||
StateValue::Number(_) => Err(StateValueError::TypeError(
|
||||
"Cannot cast Number to f32: out of range".into(),
|
||||
)),
|
||||
StateValue::Fixed14_4(v)
|
||||
if ! v.is_finite() ||
|
||||
(*v <= f32::MAX as f64 &&
|
||||
*v >= f32::MIN as f64) => Ok(*v as f32),
|
||||
StateValue::Fixed14_4(_) => Err(StateValueError::TypeError(
|
||||
"Cannot cast Fixed14_4 to f32: out of range".into(),
|
||||
)),
|
||||
|
||||
StateValue::Boolean(v) => Ok((*v as i32) as Self),
|
||||
|
||||
StateValue::String(s) => s.parse::<f32>().map_err(|_| {
|
||||
StateValueError::TypeError(format!("Cannot parse '{}' as f32", s))
|
||||
}),
|
||||
|
||||
// --- Par défaut : erreur ---
|
||||
_ => Err(StateValueError::TypeError(
|
||||
"Cannot cast to f32".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StateValue> for f32 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
f32::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for StateValue {
|
||||
|
||||
fn from(value: f32) -> Self {
|
||||
StateValue::R4(value)
|
||||
}
|
||||
}
|
||||
|
||||
53
pmoupnp/src/variable_types/values_from_f64.rs
Normal file
53
pmoupnp/src/variable_types/values_from_f64.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
impl TryFrom<&StateValue> for f64 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
// --- Signed integers ---
|
||||
StateValue::I1(v) => Ok(*v as f64),
|
||||
StateValue::I2(v) => Ok(*v as f64),
|
||||
StateValue::I4(v) => Ok(*v as f64),
|
||||
StateValue::Int(v) => Ok(*v as f64),
|
||||
|
||||
// --- Unsigned integers ---
|
||||
StateValue::UI1(v) => Ok(*v as f64),
|
||||
StateValue::UI2(v) => Ok(*v as f64),
|
||||
StateValue::UI4(v) => Ok(*v as f64),
|
||||
|
||||
// --- Floats ---
|
||||
StateValue::R4(v) => Ok(*v as f64),
|
||||
StateValue::R8(v) => Ok(*v),
|
||||
StateValue::Number(v) => Ok(*v),
|
||||
StateValue::Fixed14_4(v) => Ok(*v),
|
||||
|
||||
StateValue::Boolean(v) => Ok((*v as i32) as Self),
|
||||
|
||||
StateValue::String(s) => s.parse::<f64>().map_err(|_| {
|
||||
StateValueError::TypeError(format!("Cannot parse '{}' as f64", s))
|
||||
}),
|
||||
|
||||
// --- Par défaut : erreur ---
|
||||
_ => Err(StateValueError::TypeError(
|
||||
"Cannot cast to f64".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StateValue> for f64 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
f64::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for StateValue {
|
||||
fn from(value: f64) -> Self {
|
||||
StateValue::R8(value)
|
||||
}
|
||||
}
|
||||
46
pmoupnp/src/variable_types/values_from_i16.rs
Normal file
46
pmoupnp/src/variable_types/values_from_i16.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
// Implémentations TryFrom<StateValue> pour types numériques
|
||||
|
||||
impl TryFrom<&StateValue> for i16 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
StateValue::I1(v) => Ok(*v as i16),
|
||||
StateValue::I2(v) => Ok(*v),
|
||||
StateValue::I4(v) if *v <= i16::MAX as i32 && *v >= i16::MIN as i32 => Ok(*v as i16),
|
||||
StateValue::Int(v) if *v <= i16::MAX as i32 && *v >= i16::MIN as i32 => Ok(*v as i16),
|
||||
|
||||
StateValue::UI1(v) => Ok(*v as i16),
|
||||
StateValue::UI2(v) if *v <= i16::MAX as u16 => Ok(*v as i16),
|
||||
StateValue::UI4(v) if *v <= i16::MAX as u32 => Ok(*v as i16),
|
||||
StateValue::Boolean(v) => Ok(*v as Self),
|
||||
|
||||
StateValue::String(s) => s.parse::<i16>().map_err(|_| {
|
||||
StateValueError::TypeError(format!("Cannot parse '{}' as i16", s))
|
||||
}),
|
||||
|
||||
_ => Err(StateValueError::TypeError("Cannot cast to i32".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StateValue> for i16 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
i16::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i16> for StateValue {
|
||||
|
||||
fn from(value: i16) -> Self {
|
||||
StateValue::I2(value)
|
||||
}
|
||||
}
|
||||
|
||||
46
pmoupnp/src/variable_types/values_from_i32.rs
Normal file
46
pmoupnp/src/variable_types/values_from_i32.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
|
||||
impl TryFrom<&StateValue> for i32 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
// signés
|
||||
StateValue::I1(v) => Ok(*v as i32),
|
||||
StateValue::I2(v) => Ok(*v as i32),
|
||||
StateValue::I4(v) => Ok(*v),
|
||||
StateValue::Int(v) => Ok(*v),
|
||||
|
||||
// non signés
|
||||
StateValue::UI1(v) => Ok(*v as i32),
|
||||
StateValue::UI2(v) => Ok(*v as i32),
|
||||
StateValue::UI4(v) if *v <= i32::MAX as u32 =>Ok(*v as i32),
|
||||
StateValue::Boolean(v) => Ok(*v as Self),
|
||||
|
||||
StateValue::String(s) => s.parse::<i32>().map_err(|_| {
|
||||
StateValueError::TypeError(format!("Cannot parse '{}' as i32", s))
|
||||
}),
|
||||
|
||||
_ => Err(StateValueError::TypeError("Cannot cast to i32".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StateValue> for i32 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
i32::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for StateValue {
|
||||
|
||||
fn from(value: i32) -> Self {
|
||||
StateValue::I4(value)
|
||||
}
|
||||
}
|
||||
|
||||
45
pmoupnp/src/variable_types/values_from_i64.rs
Normal file
45
pmoupnp/src/variable_types/values_from_i64.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use std::convert::TryFrom;
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
impl TryFrom<&StateValue> for i64 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
// signés
|
||||
StateValue::I1(v) => Ok(*v as i64),
|
||||
StateValue::I2(v) => Ok(*v as i64),
|
||||
StateValue::I4(v) => Ok(*v as i64),
|
||||
StateValue::Int(v) => Ok(*v as i64),
|
||||
|
||||
// non signés
|
||||
StateValue::UI1(v) => Ok(*v as i64),
|
||||
StateValue::UI2(v) => Ok(*v as i64),
|
||||
StateValue::UI4(v) => Ok(*v as i64), // toujours dans l'intervalle d'un i64
|
||||
|
||||
// booléen
|
||||
StateValue::Boolean(v) => Ok(*v as i64),
|
||||
|
||||
// chaîne → i64
|
||||
StateValue::String(s) => s.parse::<i64>().map_err(|_| {
|
||||
StateValueError::TypeError(format!("Cannot parse '{}' as i64", s))
|
||||
}),
|
||||
|
||||
_ => Err(StateValueError::TypeError("Cannot cast to i64".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StateValue> for i64 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
i64::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for StateValue {
|
||||
fn from(value: i64) -> Self {
|
||||
StateValue::Int(value as i32) // ⚠️ choix à discuter : Int est i32, pas i64
|
||||
}
|
||||
}
|
||||
46
pmoupnp/src/variable_types/values_from_i8.rs
Normal file
46
pmoupnp/src/variable_types/values_from_i8.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
// Implémentations TryFrom<StateValue> pour types numériques
|
||||
|
||||
impl TryFrom<&StateValue> for i8 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
StateValue::I1(v) if *v >= 0 => Ok(*v as i8),
|
||||
StateValue::I2(v) if *v <= i8::MAX as i16 && *v >= i8::MIN as i16 => Ok(*v as i8),
|
||||
StateValue::I4(v) if *v <= i8::MAX as i32 && *v >= i8::MIN as i32=> Ok(*v as i8),
|
||||
StateValue::Int(v) if *v <= i8::MAX as i32 && *v >= i8::MIN as i32=> Ok(*v as i8),
|
||||
|
||||
StateValue::UI1(v) if *v <= i8::MAX as u8 => Ok(*v as i8),
|
||||
StateValue::UI2(v) if *v <= i8::MAX as u16 => Ok(*v as i8),
|
||||
StateValue::UI4(v) if *v <= i8::MAX as u32 => Ok(*v as i8),
|
||||
StateValue::Boolean(v) => Ok(*v as Self),
|
||||
|
||||
StateValue::String(s) => s.parse::<i8>().map_err(|_| {
|
||||
StateValueError::TypeError(format!("Cannot parse '{}' as i8", s))
|
||||
}),
|
||||
|
||||
_ => Err(StateValueError::TypeError("Cannot cast to i8".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StateValue> for i8 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
i8::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i8> for StateValue {
|
||||
|
||||
fn from(value: i8) -> Self {
|
||||
StateValue::I1(value)
|
||||
}
|
||||
}
|
||||
|
||||
32
pmoupnp/src/variable_types/values_from_naivedate.rs
Normal file
32
pmoupnp/src/variable_types/values_from_naivedate.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use std::convert::TryFrom;
|
||||
use chrono::NaiveDate;
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
impl TryFrom<&StateValue> for NaiveDate {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
StateValue::Date(v) => Ok(v.clone()),
|
||||
StateValue::String(v) => NaiveDate::parse_from_str(v, "%Y-%m-%d")
|
||||
.map_err(|e| StateValueError::ParseError(format!("Cannot parse Date from string '{}': {}", v, e))),
|
||||
_ => Err(StateValueError::TypeError("Cannot cast to NaiveDate".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StateValue> for NaiveDate {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
NaiveDate::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NaiveDate> for StateValue {
|
||||
|
||||
fn from(value: NaiveDate) -> Self {
|
||||
StateValue::Date(value)
|
||||
}
|
||||
}
|
||||
|
||||
31
pmoupnp/src/variable_types/values_from_naivedatetime.rs
Normal file
31
pmoupnp/src/variable_types/values_from_naivedatetime.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use std::convert::TryFrom;
|
||||
use chrono::NaiveDateTime;
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
impl TryFrom<&StateValue> for NaiveDateTime {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
StateValue::DateTime(v) => Ok(v.clone()),
|
||||
StateValue::String(v) => NaiveDateTime::parse_from_str(&v, "%Y-%m-%dT%H:%M:%S")
|
||||
.map_err(|e| StateValueError::ParseError(format!("Cannot parse DateTime from string '{}': {}", v, e))),
|
||||
_ => Err(StateValueError::TypeError("Cannot cast to NaiveDateTime".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StateValue> for NaiveDateTime {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
NaiveDateTime::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NaiveDateTime> for StateValue {
|
||||
|
||||
fn from(value: NaiveDateTime) -> Self {
|
||||
StateValue::DateTime(value)
|
||||
}
|
||||
}
|
||||
31
pmoupnp/src/variable_types/values_from_naivetime.rs
Normal file
31
pmoupnp/src/variable_types/values_from_naivetime.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use std::convert::TryFrom;
|
||||
use chrono::NaiveTime;
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
impl TryFrom<&StateValue> for NaiveTime {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
StateValue::Time(v) => Ok(v.clone()),
|
||||
StateValue::String(v) => NaiveTime::parse_from_str(&v, "%H:%M:%S")
|
||||
.map_err(|e| StateValueError::ParseError(format!("Cannot parse Time from string '{}': {}", v, e))),
|
||||
_ => Err(StateValueError::TypeError("Cannot cast to NaiveTime".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StateValue> for NaiveTime {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
NaiveTime::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NaiveTime> for StateValue {
|
||||
|
||||
fn from(value: NaiveTime) -> Self {
|
||||
StateValue::Time(value)
|
||||
}
|
||||
}
|
||||
37
pmoupnp/src/variable_types/values_from_type.rs
Normal file
37
pmoupnp/src/variable_types/values_from_type.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use crate::variable_types::{StateValue, StateVarType};
|
||||
|
||||
impl From<&StateValue> for StateVarType {
|
||||
fn from(value: &StateValue) -> Self {
|
||||
match value {
|
||||
StateValue::UI1(_) => StateVarType::UI1,
|
||||
StateValue::UI2(_) => StateVarType::UI2,
|
||||
StateValue::UI4(_) => StateVarType::UI4,
|
||||
StateValue::I1(_) => StateVarType::I1,
|
||||
StateValue::I2(_) => StateVarType::I2,
|
||||
StateValue::I4(_) => StateVarType::I4,
|
||||
StateValue::Int(_) => StateVarType::Int,
|
||||
StateValue::R4(_) => StateVarType::R4,
|
||||
StateValue::R8(_) => StateVarType::R8,
|
||||
StateValue::Number(_) => StateVarType::Number,
|
||||
StateValue::Fixed14_4(_) => StateVarType::Fixed14_4,
|
||||
StateValue::Char(_) => StateVarType::Char,
|
||||
StateValue::String(_) => StateVarType::String,
|
||||
StateValue::BinBase64(_) => StateVarType::BinBase64,
|
||||
StateValue::BinHex(_) => StateVarType::BinHex,
|
||||
StateValue::URI(_) => StateVarType::URI,
|
||||
StateValue::UUID(_) => StateVarType::UUID,
|
||||
StateValue::Date(_) => StateVarType::Date,
|
||||
StateValue::DateTime(_) => StateVarType::DateTime,
|
||||
StateValue::DateTimeTZ(_) => StateVarType::DateTimeTZ,
|
||||
StateValue::Time(_) => StateVarType::Time,
|
||||
StateValue::TimeTZ(_) => StateVarType::TimeTZ,
|
||||
StateValue::Boolean(_) => StateVarType::Boolean,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StateValue> for StateVarType {
|
||||
fn from(value: StateValue) -> Self {
|
||||
StateVarType::from(&value)
|
||||
}
|
||||
}
|
||||
47
pmoupnp/src/variable_types/values_from_u16.rs
Normal file
47
pmoupnp/src/variable_types/values_from_u16.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
// Implémentations TryFrom<StateValue> pour types numériques
|
||||
|
||||
impl TryFrom<&StateValue> for u16 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
StateValue::UI1(v) => Ok(*v as Self),
|
||||
StateValue::UI2(v) => Ok(*v),
|
||||
StateValue::UI4(v) if *v <= i16::MAX as u32 => Ok(*v as Self),
|
||||
StateValue::I1(v) if *v >= 0 => Ok(*v as Self),
|
||||
StateValue::I2(v) if *v >= 0 => Ok(*v as Self),
|
||||
StateValue::I4(v) if *v >= 0 && *v <= u16::MAX as i32 => Ok(*v as Self),
|
||||
StateValue::Int(v) if *v >= 0 && *v <= u16::MAX as i32 => Ok(*v as Self),
|
||||
StateValue::Boolean(v) => Ok(*v as Self),
|
||||
|
||||
StateValue::String(s) => s.parse::<u16>().map_err(|_| {
|
||||
StateValueError::TypeError(format!("Cannot parse '{}' as u16", s))
|
||||
}),
|
||||
|
||||
_ => Err(StateValueError::TypeError("Cannot cast to u16".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StateValue> for u16 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
u16::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<u16> for StateValue {
|
||||
|
||||
fn from(value: u16) -> Self {
|
||||
StateValue::UI2(value)
|
||||
}
|
||||
}
|
||||
|
||||
45
pmoupnp/src/variable_types/values_from_u32.rs
Normal file
45
pmoupnp/src/variable_types/values_from_u32.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
impl TryFrom<&StateValue> for u32 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
StateValue::UI1(v) => Ok(*v as Self),
|
||||
StateValue::UI2(v) => Ok(*v as Self),
|
||||
StateValue::UI4(v) => Ok(*v),
|
||||
StateValue::I1(v) if *v >= 0 => Ok(*v as Self),
|
||||
StateValue::I2(v) if *v >= 0 => Ok(*v as Self),
|
||||
StateValue::I4(v) if *v >= 0 => Ok(*v as Self),
|
||||
StateValue::Int(v) if *v >= 0 => Ok(*v as Self),
|
||||
StateValue::Boolean(v) => Ok(*v as Self),
|
||||
|
||||
StateValue::String(s) => s.parse::<u32>().map_err(|_| {
|
||||
StateValueError::TypeError(format!("Cannot parse '{}' as u32", s))
|
||||
}),
|
||||
|
||||
_ => Err(StateValueError::TypeError("Cannot cast to u32".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StateValue> for u32 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
u32::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl From<u32> for StateValue {
|
||||
|
||||
fn from(value: u32) -> Self {
|
||||
StateValue::UI4(value)
|
||||
}
|
||||
}
|
||||
|
||||
45
pmoupnp/src/variable_types/values_from_u8.rs
Normal file
45
pmoupnp/src/variable_types/values_from_u8.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
// Implémentations TryFrom<StateValue> pour types numériques
|
||||
|
||||
impl TryFrom<&StateValue> for u8 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
StateValue::UI1(v) => Ok(*v),
|
||||
StateValue::UI2(v) if *v <= u8::MAX as u16 => Ok(*v as u8),
|
||||
StateValue::UI4(v) if *v <= i8::MAX as u32 => Ok(*v as u8),
|
||||
StateValue::I1(v) if *v >= 0 => Ok(*v as u8),
|
||||
StateValue::I2(v) if *v >= 0 && *v <= u8::MAX as i16 => Ok(*v as u8),
|
||||
StateValue::I4(v) if *v >= 0 && *v <= u8::MAX as i32 => Ok(*v as u8),
|
||||
StateValue::Int(v) if *v >= 0 && *v <= u8::MAX as i32 => Ok(*v as u8),
|
||||
StateValue::Boolean(v) => Ok(*v as Self),
|
||||
|
||||
StateValue::String(s) => s.parse::<u8>().map_err(|_| {
|
||||
StateValueError::TypeError(format!("Cannot parse '{}' as u8", s))
|
||||
}),
|
||||
|
||||
_ => Err(StateValueError::TypeError("Cannot cast to u8".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StateValue> for u8 {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
u8::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for StateValue {
|
||||
|
||||
fn from(value: u8) -> Self {
|
||||
StateValue::UI1(value)
|
||||
}
|
||||
}
|
||||
|
||||
31
pmoupnp/src/variable_types/values_from_uri.rs
Normal file
31
pmoupnp/src/variable_types/values_from_uri.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use std::convert::TryFrom;
|
||||
use url::Url;
|
||||
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
impl TryFrom<StateValue> for Url {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
// Si déjà un URI encodé comme StateValue::URI
|
||||
StateValue::URI(v) => Ok(v),
|
||||
|
||||
// Si c'est une String, on tente un parse
|
||||
StateValue::String(v) => {
|
||||
Url::parse(&v)
|
||||
.map_err(|_| StateValueError::TypeError("Invalid URI string".into()))
|
||||
}
|
||||
|
||||
// Autres types : erreur
|
||||
_ => Err(StateValueError::TypeError("Cannot cast to Url".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Url> for StateValue {
|
||||
|
||||
fn from(value: Url) -> Self {
|
||||
StateValue::URI(value)
|
||||
}
|
||||
}
|
||||
31
pmoupnp/src/variable_types/values_from_uuid.rs
Normal file
31
pmoupnp/src/variable_types/values_from_uuid.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use std::convert::TryFrom;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
impl TryFrom<StateValue> for Uuid {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
// Si déjà un URI encodé comme StateValue::URI
|
||||
StateValue::UUID(v) => Ok(v),
|
||||
|
||||
// Si c'est une String, on tente un parse
|
||||
StateValue::String(v) => {
|
||||
Uuid::parse_str(&v)
|
||||
.map_err(|_| StateValueError::TypeError("Invalid UUID string".into()))
|
||||
}
|
||||
|
||||
// Autres types : erreur
|
||||
_ => Err(StateValueError::TypeError("Cannot cast to Uuid".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Uuid> for StateValue {
|
||||
|
||||
fn from(value: Uuid) -> Self {
|
||||
StateValue::UUID(value)
|
||||
}
|
||||
}
|
||||
45
pmoupnp/src/variable_types/values_from_vec_u8.rs
Normal file
45
pmoupnp/src/variable_types/values_from_vec_u8.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use std::convert::TryFrom;
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
|
||||
impl TryFrom<&StateValue> for Vec<u8> {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: &StateValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
// Déjà un vecteur binaire
|
||||
StateValue::BinBase64(v) => STANDARD.decode(v).map_err(
|
||||
|e| StateValueError::ParseError(format!("Base64 decode error: {}", e))),
|
||||
StateValue::BinHex(v) => hex::decode(v).map_err(
|
||||
|e| StateValueError::ParseError(format!("BinHex decode error: {}", e))),
|
||||
|
||||
// Conversion depuis une chaîne encodée
|
||||
StateValue::String(s) => {
|
||||
// Essayer Base64
|
||||
if let Ok(bytes) = STANDARD.decode(s) {
|
||||
return Ok(bytes);
|
||||
}
|
||||
// Essayer Hex
|
||||
if let Ok(bytes) = hex::decode(s) {
|
||||
return Ok(bytes);
|
||||
}
|
||||
Err(StateValueError::ParseError(format!(
|
||||
"Cannot parse string '{}' as binary",
|
||||
s
|
||||
)))
|
||||
}
|
||||
|
||||
_ => Err(StateValueError::TypeError(
|
||||
"Cannot cast to binary Vec<u8>".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StateValue> for Vec<u8> {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(value: StateValue) -> Result<Self, Self::Error> {
|
||||
Vec::<u8>::try_from(&value)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user