diff --git a/Cargo.lock b/Cargo.lock index 60acc528..38b0b4fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -793,6 +793,29 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1960,6 +1983,30 @@ dependencies = [ "libc", ] +[[package]] +name = "jiff" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -2002,7 +2049,11 @@ name = "kuksa-common" version = "0.6.0-dev.0" dependencies = [ "databroker-proto", + "env_logger", "http", + "log", + "prost 0.12.6", + "prost-types", "tokio", "tokio-stream", "tonic 0.11.0", @@ -2535,6 +2586,21 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "powerfmt" version = "0.2.0" diff --git a/databroker-cli/src/kuksa_cli.rs b/databroker-cli/src/kuksa_cli.rs index e52acb40..1a91fb28 100644 --- a/databroker-cli/src/kuksa_cli.rs +++ b/databroker-cli/src/kuksa_cli.rs @@ -1,5 +1,5 @@ /******************************************************************************** -* Copyright (c) 2023 Contributors to the Eclipse Foundation +* Copyright (c) 2025 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -13,6 +13,7 @@ use databroker_proto::kuksa::val as proto; use kuksa::*; +use kuksa_common::ClientTraitV1; use prost_types::Timestamp; use tokio_stream::StreamExt; @@ -40,7 +41,7 @@ const CLI_COMMANDS: &[(&str, &str, &str)] = &[ ("actuate", " ", "Set actuator signal"), ( "subscribe", - "", + " [[PATH] ...]", "Subscribe to signals with QUERY, if you use kuksa feature comma separated list", ), ("publish", " ", "Publish signal value"), @@ -67,6 +68,35 @@ fn print_usage(command: impl AsRef) { } } +async fn handle_get_metadata( + paths: Vec<&str>, + client: &mut KuksaClient, +) -> Result>, Box> { + match client + .get_metadata( + paths + .iter() + .map(|&s| s.to_string()) // Convert each &str to String + .collect(), + ) + .await + { + Ok(data_entries) => Ok(Some(data_entries)), + Err(kuksa_common::ClientError::Status(status)) => { + cli::print_resp_err("get metadata", &status)?; + Ok(None) + } + Err(kuksa_common::ClientError::Connection(msg)) => { + cli::print_error("get metadata", msg)?; + Ok(None) + } + Err(kuksa_common::ClientError::Function(msg)) => { + cli::print_resp_err_fmt("get metadata", format_args!("Error {msg:?}"))?; + Ok(None) + } + } +} + async fn handle_actuate_command( path: &str, value: &str, @@ -77,21 +107,7 @@ async fn handle_actuate_command( return Ok(()); } - let datapoint_entries = match client.get_metadata(vec![path]).await { - Ok(data_entries) => Some(data_entries), - Err(ClientError::Status(status)) => { - cli::print_resp_err("metadata", &status)?; - None - } - Err(ClientError::Connection(msg)) => { - cli::print_error("metadata", msg)?; - None - } - Err(ClientError::Function(msg)) => { - cli::print_resp_err_fmt("actuate", format_args!("Error {msg:?}"))?; - None - } - }; + let datapoint_entries = handle_get_metadata(vec![path], client).await.unwrap(); if let Some(entries) = datapoint_entries { for entry in entries { @@ -142,21 +158,7 @@ async fn handle_publish_command( value: &str, client: &mut KuksaClient, ) -> Result<(), Box> { - let datapoint_entries = match client.get_metadata(vec![path]).await { - Ok(data_entries) => Some(data_entries), - Err(kuksa_common::ClientError::Status(status)) => { - cli::print_resp_err("metadata", &status)?; - None - } - Err(kuksa_common::ClientError::Connection(msg)) => { - cli::print_error("metadata", msg)?; - None - } - Err(kuksa_common::ClientError::Function(msg)) => { - cli::print_resp_err_fmt("publish", format_args!("Error {msg:?}"))?; - None - } - }; + let datapoint_entries = handle_get_metadata(vec![path], client).await.unwrap(); if let Some(entries) = datapoint_entries { for entry in entries { @@ -329,20 +331,9 @@ pub async fn kuksa_main(_cli: Cli) -> Result<(), Box> { let pattern = vec!["**"]; - match client.get_metadata(pattern).await { - Ok(metadata) => { - interface - .set_completer(Arc::new(CliCompleter::from_metadata(&metadata))); - } - Err(kuksa_common::ClientError::Status(status)) => { - cli::print_resp_err("metadata", &status)?; - } - Err(kuksa_common::ClientError::Connection(msg)) => { - cli::print_error("metadata", msg)?; - } - Err(kuksa_common::ClientError::Function(msg)) => { - cli::print_resp_err_fmt("metadata", format_args!("Error {msg:?}"))?; - } + let data_entries = handle_get_metadata(pattern, &mut client).await?; + if let Some(entries) = data_entries { + interface.set_completer(Arc::new(CliCompleter::from_metadata(&entries))); } } Err(err) => { @@ -432,24 +423,12 @@ pub async fn kuksa_main(_cli: Cli) -> Result<(), Box> { match client.basic_client.set_access_token(args) { Ok(()) => { cli::print_info("Access token set.")?; - match client.get_metadata(vec![]).await { - Ok(metadata) => { - interface.set_completer(Arc::new( - CliCompleter::from_metadata(&metadata), - )); - } - Err(kuksa_common::ClientError::Status(status)) => { - cli::print_resp_err("metadata", &status)?; - } - Err(kuksa_common::ClientError::Connection(msg)) => { - cli::print_error("metadata", msg)?; - } - Err(kuksa_common::ClientError::Function(msg)) => { - cli::print_resp_err_fmt( - "metadata", - format_args!("Error {msg:?}"), - )?; - } + if let Some(entries) = + handle_get_metadata(vec![], &mut client).await.unwrap() + { + interface.set_completer(Arc::new( + CliCompleter::from_metadata(&entries), + )); } } Err(err) => { @@ -470,24 +449,12 @@ pub async fn kuksa_main(_cli: Cli) -> Result<(), Box> { Ok(token) => match client.basic_client.set_access_token(token) { Ok(()) => { cli::print_info("Access token set.")?; - match client.get_metadata(vec![]).await { - Ok(metadata) => { - interface.set_completer(Arc::new( - CliCompleter::from_metadata(&metadata), - )); - } - Err(kuksa_common::ClientError::Status(status)) => { - cli::print_resp_err("metadata", &status)?; - } - Err(kuksa_common::ClientError::Connection(msg)) => { - cli::print_error("metadata", msg)?; - } - Err(kuksa_common::ClientError::Function(msg)) => { - cli::print_resp_err_fmt( - cmd, - format_args!("Error {msg:?}"), - )?; - } + if let Some(entries) = + handle_get_metadata(vec![], &mut client).await.unwrap() + { + interface.set_completer(Arc::new( + CliCompleter::from_metadata(&entries), + )); } } Err(err) => { @@ -536,7 +503,15 @@ pub async fn kuksa_main(_cli: Cli) -> Result<(), Box> { let input = args.split_whitespace().collect::>(); - match client.subscribe(input).await { + match client + .subscribe( + input + .iter() + .map(|&s| s.to_string()) // Convert each &str to String + .collect(), + ) + .await + { Ok(mut subscription) => { let iface = interface.clone(); tokio::spawn(async move { @@ -684,24 +659,12 @@ pub async fn kuksa_main(_cli: Cli) -> Result<(), Box> { } }; if client.basic_client.is_connected() { - match client.get_metadata(vec!["**"]).await { - Ok(metadata) => { - interface.set_completer(Arc::new( - CliCompleter::from_metadata(&metadata), - )); - } - Err(kuksa_common::ClientError::Status(status)) => { - cli::print_resp_err("metadata", &status)?; - } - Err(kuksa_common::ClientError::Connection(msg)) => { - cli::print_error("metadata", msg)?; - } - Err(kuksa_common::ClientError::Function(msg)) => { - cli::print_resp_err_fmt( - cmd, - format_args!("Error {msg:?}"), - )?; - } + if let Some(entries) = + handle_get_metadata(vec!["**"], &mut client).await.unwrap() + { + interface.set_completer(Arc::new( + CliCompleter::from_metadata(&entries), + )); } } }; @@ -713,63 +676,47 @@ pub async fn kuksa_main(_cli: Cli) -> Result<(), Box> { if paths.is_empty() { cli::print_info("If you want to list metadata of signals, use `metadata PATTERN`")?; - } else { - match client.get_metadata(paths).await { - Ok(metadata) => { - cli::print_resp_ok(cmd)?; - if !metadata.is_empty() { - let max_len_path = - metadata.iter().fold(0, |mut max_len, item| { - if item.path.len() > max_len { - max_len = item.path.len(); - } - max_len - }); + } else if let Some(entries) = + handle_get_metadata(paths, &mut client).await.unwrap() + { + cli::print_resp_ok(cmd)?; + if !entries.is_empty() { + let max_len_path = + entries.iter().fold(0, |mut max_len, item| { + if item.path.len() > max_len { + max_len = item.path.len(); + } + max_len + }); - cli::print_info(format!( - "{: { - cli::print_resp_err(cmd, &status)?; - continue; - } - Err(kuksa_common::ClientError::Connection(msg)) => { - cli::print_error(cmd, msg)?; - continue; - } - Err(kuksa_common::ClientError::Function(msg)) => { - cli::print_resp_err_fmt( - cmd, - format_args!("Error {msg:?}"), - )?; - } } } } diff --git a/databroker-cli/src/sdv_cli.rs b/databroker-cli/src/sdv_cli.rs index 37823c52..1884f7c7 100644 --- a/databroker-cli/src/sdv_cli.rs +++ b/databroker-cli/src/sdv_cli.rs @@ -1,5 +1,5 @@ /******************************************************************************** -* Copyright (c) 2023 Contributors to the Eclipse Foundation +* Copyright (c) 2025 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -12,6 +12,7 @@ ********************************************************************************/ use databroker_proto::sdv::databroker as proto; +use kuksa_common::SDVClientTraitV1; use kuksa_sdv::*; use prost_types::Timestamp; @@ -451,7 +452,7 @@ pub async fn sdv_main(_cli: Cli) -> Result<(), Box> { } let ts = Timestamp::from(SystemTime::now()); let datapoints = HashMap::from([( - metadata.id, + metadata.name.clone(), proto::v1::Datapoint { timestamp: Some(ts), value: Some(data_value.unwrap()), diff --git a/databroker/tests/read_write_values.rs b/databroker/tests/read_write_values.rs index dad19144..b893225a 100644 --- a/databroker/tests/read_write_values.rs +++ b/databroker/tests/read_write_values.rs @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2025 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -17,6 +17,7 @@ use std::{collections::HashMap, future, time::SystemTime, vec}; use cucumber::{cli, gherkin::Step, given, then, when, writer, World as _}; use databroker::broker; use databroker_proto::kuksa::val::v1::{datapoint::Value, DataType, Datapoint}; +use kuksa_common::ClientTraitV1; use tracing::debug; use world::{DataBrokerWorld, ValueType}; diff --git a/lib/Cargo.lock b/lib/Cargo.lock index cb36ca58..90efaec1 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -26,6 +26,56 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "anyhow" version = "1.0.96" @@ -185,10 +235,33 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "ctrlc" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" +dependencies = [ + "nix", + "windows-sys 0.59.0", +] + [[package]] name = "databroker-examples" version = "0.1.0" dependencies = [ + "ctrlc", "databroker-proto", "kuksa", "kuksa-common", @@ -217,6 +290,29 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -446,6 +542,12 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -470,6 +572,30 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "jiff" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "kuksa" version = "0.6.0-dev.0" @@ -487,7 +613,11 @@ name = "kuksa-common" version = "0.6.0-dev.0" dependencies = [ "databroker-proto", + "env_logger", "http", + "log", + "prost 0.12.6", + "prost-types", "tokio", "tokio-stream", "tonic", @@ -592,6 +722,18 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.8.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "object" version = "0.36.7" @@ -678,6 +820,21 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1271,6 +1428,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "want" version = "0.3.1" diff --git a/lib/common/Cargo.toml b/lib/common/Cargo.toml index 617bc77c..e83478f4 100644 --- a/lib/common/Cargo.toml +++ b/lib/common/Cargo.toml @@ -26,6 +26,12 @@ tokio = { workspace = true, features = [ ] } tokio-stream = { workspace = true, features = ["sync"] } http = "0.2.8" +log = "0.4" +env_logger = "0.11" + +[dev-dependencies] +prost = "0.12" +prost-types = "0.12" [lib] name = "kuksa_common" diff --git a/lib/common/src/conversion.rs b/lib/common/src/conversion.rs new file mode 100644 index 00000000..3d1e428c --- /dev/null +++ b/lib/common/src/conversion.rs @@ -0,0 +1,2637 @@ +/******************************************************************************** +* Copyright (c) 2025 Contributors to the Eclipse Foundation +* +* See the NOTICE file(s) distributed with this work for additional +* information regarding copyright ownership. +* +* This program and the accompanying materials are made available under the +* terms of the Apache License 2.0 which is available at +* http://www.apache.org/licenses/LICENSE-2.0 +* +* SPDX-License-Identifier: Apache-2.0 +********************************************************************************/ + +use crate::types::MetadataTypeV2; +use crate::types::{ + ActuateResponseSDVTypeV1, ActuateResponseTypeV1, ActuateResponseTypeV2, GetResponseSDVTypeV1, + GetResponseTypeV1, MetadataResponseSDVTypeV1, MetadataResponseTypeV1, MetadataResponseTypeV2, + MultipleGetResponseTypeV2, MultipleUpdateActuationTypeV2, PathSDVTypeV1, PathTypeV1, + PathsTypeV2, PublishResponseSDVTypeV1, PublishResponseTypeV1, SensorUpdateSDVTypeV1, + SensorUpdateTypeV1, SensorUpdateTypeV2, SubscribeResponseSDVTypeV1, SubscribeResponseTypeV1, + SubscribeResponseTypeV2, SubscribeSDVTypeV1, SubscribeTypeV1, UpdateActuationTypeV1, +}; +use databroker_proto::kuksa::val::v1::{self as protoV1}; +use databroker_proto::kuksa::val::v2::{self as protoV2}; +use databroker_proto::sdv::databroker::v1 as SDVprotoV1; +use log::warn; +use std::collections::HashMap; + +// Idea: in the future we could use databroker internal datapoint structure and define it here then the conversion from databroker can be reused + +fn find_common_root(paths: Vec) -> String { + if paths.is_empty() { + return String::new(); + } + + let split_paths: Vec> = paths.iter().map(|s| s.split('.').collect()).collect(); + + // Take the first path as a reference + let first_path = &split_paths[0]; + + let mut common_root = Vec::new(); + + for (i, segment) in first_path.iter().enumerate() { + if split_paths.iter().all(|path| path.get(i) == Some(segment)) { + common_root.push(*segment); + } else { + break; + } + } + + common_root.join(".") +} + +pub trait ConvertToSDV { + fn convert_to_sdv(self) -> T; +} + +// because the type of SensorUpdate(SDV)TypeV1 and UpdateActuation(SDV)TypeV1 the implementation is only once needed. +// Hence no impl ConvertToV1 for UpdateActuationSDVTypeV1{} +impl ConvertToSDV for SensorUpdateTypeV1 { + fn convert_to_sdv(self) -> SensorUpdateSDVTypeV1 { + self.into_iter() + .map(|(key, datapoint)| { + let value = datapoint.clone().convert_to_sdv(); + ( + key, + SDVprotoV1::Datapoint { + timestamp: datapoint.timestamp, + value, + }, + ) + }) + .collect() + } +} + +impl ConvertToSDV for PathTypeV1 { + fn convert_to_sdv(self) -> PathSDVTypeV1 { + self + } +} + +impl ConvertToSDV for SubscribeTypeV1 { + fn convert_to_sdv(self) -> SubscribeSDVTypeV1 { + unimplemented!("SQL queries not supported anymore") + } +} + +impl ConvertToSDV for PublishResponseTypeV1 { + fn convert_to_sdv(self) -> PublishResponseSDVTypeV1 { + SDVprotoV1::UpdateDatapointsReply { + errors: HashMap::new(), + } + } +} + +impl ConvertToSDV for GetResponseTypeV1 { + fn convert_to_sdv(self) -> GetResponseSDVTypeV1 { + let transformed_map: HashMap = self + .into_iter() + .filter_map(|data_entry| { + // Check if the value is Some + if let Some(entry) = &data_entry.value { + let value = entry.clone().convert_to_sdv(); + let dp = SDVprotoV1::Datapoint { + value, + timestamp: entry.timestamp.clone(), + }; + + Some((data_entry.path, dp)) + } else { + eprintln!("No data entries found for path: {}", data_entry.path); + None + } + }) + .collect(); + transformed_map + } +} + +impl ConvertToSDV for SubscribeResponseTypeV1 { + fn convert_to_sdv(self) -> SubscribeResponseSDVTypeV1 { + unimplemented!("Not possible to convert stream objects!") + } +} + +impl ConvertToSDV for ActuateResponseTypeV1 { + fn convert_to_sdv(self) -> ActuateResponseSDVTypeV1 { + unimplemented!("Not possible to convert stream objects!") + } +} + +impl ConvertToSDV for MetadataResponseTypeV1 { + fn convert_to_sdv(self) -> MetadataResponseSDVTypeV1 { + unimplemented!("Less information in metadata. It makes no sense to convert!") + } +} + +impl ConvertToSDV> for protoV1::Datapoint { + fn convert_to_sdv(self) -> Option { + if let Some(value) = self.value { + match value { + protoV1::datapoint::Value::String(val) => { + Some(SDVprotoV1::datapoint::Value::StringValue(val)) + } + protoV1::datapoint::Value::Bool(val) => { + Some(SDVprotoV1::datapoint::Value::BoolValue(val)) + } + protoV1::datapoint::Value::Int32(val) => { + Some(SDVprotoV1::datapoint::Value::Int32Value(val)) + } + protoV1::datapoint::Value::Int64(val) => { + Some(SDVprotoV1::datapoint::Value::Int64Value(val)) + } + protoV1::datapoint::Value::Uint32(val) => { + Some(SDVprotoV1::datapoint::Value::Uint32Value(val)) + } + protoV1::datapoint::Value::Uint64(val) => { + Some(SDVprotoV1::datapoint::Value::Uint64Value(val)) + } + protoV1::datapoint::Value::Float(val) => { + Some(SDVprotoV1::datapoint::Value::FloatValue(val)) + } + protoV1::datapoint::Value::Double(val) => { + Some(SDVprotoV1::datapoint::Value::DoubleValue(val)) + } + protoV1::datapoint::Value::StringArray(string_array) => Some( + SDVprotoV1::datapoint::Value::StringArray(SDVprotoV1::StringArray { + values: string_array.values, + }), + ), + protoV1::datapoint::Value::BoolArray(bool_array) => Some( + SDVprotoV1::datapoint::Value::BoolArray(SDVprotoV1::BoolArray { + values: bool_array.values, + }), + ), + protoV1::datapoint::Value::Int32Array(int32_array) => Some( + SDVprotoV1::datapoint::Value::Int32Array(SDVprotoV1::Int32Array { + values: int32_array.values, + }), + ), + protoV1::datapoint::Value::Int64Array(int64_array) => Some( + SDVprotoV1::datapoint::Value::Int64Array(SDVprotoV1::Int64Array { + values: int64_array.values, + }), + ), + protoV1::datapoint::Value::Uint32Array(uint32_array) => Some( + SDVprotoV1::datapoint::Value::Uint32Array(SDVprotoV1::Uint32Array { + values: uint32_array.values, + }), + ), + protoV1::datapoint::Value::Uint64Array(uint64_array) => Some( + SDVprotoV1::datapoint::Value::Uint64Array(SDVprotoV1::Uint64Array { + values: uint64_array.values, + }), + ), + protoV1::datapoint::Value::FloatArray(float_array) => Some( + SDVprotoV1::datapoint::Value::FloatArray(SDVprotoV1::FloatArray { + values: float_array.values, + }), + ), + protoV1::datapoint::Value::DoubleArray(double_array) => Some( + SDVprotoV1::datapoint::Value::DoubleArray(SDVprotoV1::DoubleArray { + values: double_array.values, + }), + ), + } + } else { + None + } + } +} + +pub trait ConvertToV1 { + fn convert_to_v1(self) -> T; +} + +// because the type of SensorUpdate(SDV)TypeV1 and UpdateActuation(SDV)TypeV1 the implementation is only once needed. +// Hence no impl ConvertToV1 for UpdateActuationSDVTypeV1{} +impl ConvertToV1 for SensorUpdateSDVTypeV1 { + fn convert_to_v1(self) -> SensorUpdateTypeV1 { + self.into_iter() + .map(|(key, datapoint)| { + let value = datapoint.clone().convert_to_v1(); + ( + key, + protoV1::Datapoint { + timestamp: datapoint.timestamp, + value, + }, + ) + }) + .collect() + } +} + +impl ConvertToV1 for PathSDVTypeV1 { + fn convert_to_v1(self) -> PathTypeV1 { + self + } +} + +impl ConvertToV1 for SubscribeSDVTypeV1 { + fn convert_to_v1(self) -> SubscribeTypeV1 { + unimplemented!("SQL queries not supported anymore") + } +} + +impl ConvertToV1 for PublishResponseSDVTypeV1 { + fn convert_to_v1(self) -> PublishResponseTypeV1 {} +} + +impl ConvertToV1 for GetResponseSDVTypeV1 { + fn convert_to_v1(self) -> GetResponseTypeV1 { + self.into_iter() + .map(|(key, datapoint)| { + let value = Some(protoV1::Datapoint { + value: datapoint.clone().convert_to_v1(), + timestamp: datapoint.timestamp, + }); + protoV1::DataEntry { + path: key, + value, + actuator_target: None, + metadata: None, + } + }) + .collect() + } +} + +// because the type of SubscribeResponse(SDV)TypeV1 and ProvideResponse(SDV)TypeV1 the implementation is only once needed. +// Hence no impl ConvertToV1 for ProvideResponseSDVTypeV1{} +impl ConvertToV1 for SubscribeResponseSDVTypeV1 { + fn convert_to_v1(self) -> SubscribeResponseTypeV1 { + unimplemented!("Not possible to convert stream objects!") + } +} + +impl ConvertToV1 for ActuateResponseSDVTypeV1 { + fn convert_to_v1(self) -> ActuateResponseTypeV1 { + unimplemented!("Not possible to convert stream objects!") + } +} + +impl ConvertToV1 for MetadataResponseSDVTypeV1 { + fn convert_to_v1(self) -> MetadataResponseTypeV1 { + let transformed_metadata: Vec = self + .into_iter() + .map(|metadata| protoV1::DataEntry { + path: metadata.clone().name, + value: None, + actuator_target: None, + metadata: Some(metadata.convert_to_v1()), + }) + .collect(); + transformed_metadata + } +} + +impl ConvertToV1 for SDVprotoV1::Metadata { + fn convert_to_v1(self) -> protoV1::Metadata { + let data_type = self.data_type().convert_to_v1(); + let entry_type = self.entry_type().convert_to_v1(); + let value_restriction = match self.allowed { + Some(val) => match val.values { + Some(values) => match values { + SDVprotoV1::allowed::Values::StringValues(string_array) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::String( + protoV1::ValueRestrictionString { + allowed_values: string_array.values, + }, + )), + }) + } + SDVprotoV1::allowed::Values::Int32Values(int32_array) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Signed( + protoV1::ValueRestrictionInt { + allowed_values: int32_array + .values + .iter() + .map(|&x| x as i64) + .collect(), + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + )), + }) + } + SDVprotoV1::allowed::Values::Int64Values(int64_array) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Signed( + protoV1::ValueRestrictionInt { + allowed_values: int64_array.values, + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + )), + }) + } + SDVprotoV1::allowed::Values::Uint32Values(uint32_array) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Unsigned( + protoV1::ValueRestrictionUint { + allowed_values: uint32_array + .values + .iter() + .map(|&x| x as u64) + .collect(), + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + )), + }) + } + SDVprotoV1::allowed::Values::Uint64Values(uint64_array) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Unsigned( + protoV1::ValueRestrictionUint { + allowed_values: uint64_array.values, + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + )), + }) + } + SDVprotoV1::allowed::Values::FloatValues(float_array) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::FloatingPoint( + protoV1::ValueRestrictionFloat { + allowed_values: float_array + .values + .iter() + .map(|&x| x as f64) + .collect(), + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + )), + }) + } + SDVprotoV1::allowed::Values::DoubleValues(double_array) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::FloatingPoint( + protoV1::ValueRestrictionFloat { + allowed_values: double_array.values, + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + )), + }) + } + }, + None => None, + }, + None => None, + }; + + protoV1::Metadata { + data_type: data_type.into(), + entry_type: entry_type.into(), + description: Some(self.description), + comment: None, + deprecation: None, + unit: None, + value_restriction, + entry_specific: None, + } + } +} + +impl ConvertToV1> for Option { + fn convert_to_v1(self) -> Option { + match self { + Some(val) => match val.typed_value { + Some(typed_value) => match typed_value { + SDVprotoV1::value_restriction::TypedValue::Int32(val) => Some(val as i64), + SDVprotoV1::value_restriction::TypedValue::Int64(val) => Some(val), + _ => panic!("wrong datatype!"), + }, + None => None, + }, + None => None, + } + } +} + +impl ConvertToV1> for Option { + fn convert_to_v1(self) -> Option { + match self { + Some(val) => match val.typed_value { + Some(typed_value) => match typed_value { + SDVprotoV1::value_restriction::TypedValue::Float(val) => Some(val as f64), + SDVprotoV1::value_restriction::TypedValue::Double(val) => Some(val), + _ => panic!("wrong datatype!"), + }, + None => None, + }, + None => None, + } + } +} + +impl ConvertToV1> for Option { + fn convert_to_v1(self) -> Option { + match self { + Some(val) => match val.typed_value { + Some(typed_value) => match typed_value { + SDVprotoV1::value_restriction::TypedValue::Uint32(val) => Some(val as u64), + SDVprotoV1::value_restriction::TypedValue::Uint64(val) => Some(val), + _ => panic!("wrong datatype!"), + }, + None => None, + }, + None => None, + } + } +} + +impl ConvertToV1 for SDVprotoV1::EntryType { + fn convert_to_v1(self) -> protoV1::EntryType { + match self { + SDVprotoV1::EntryType::Unspecified => protoV1::EntryType::Unspecified, + SDVprotoV1::EntryType::Sensor => protoV1::EntryType::Sensor, + SDVprotoV1::EntryType::Actuator => protoV1::EntryType::Actuator, + SDVprotoV1::EntryType::Attribute => protoV1::EntryType::Attribute, + } + } +} + +impl ConvertToV1 for SDVprotoV1::DataType { + fn convert_to_v1(self) -> protoV1::DataType { + match self { + SDVprotoV1::DataType::String => protoV1::DataType::String, + SDVprotoV1::DataType::Bool => protoV1::DataType::Boolean, + SDVprotoV1::DataType::Int8 => protoV1::DataType::Int8, + SDVprotoV1::DataType::Int16 => protoV1::DataType::Int16, + SDVprotoV1::DataType::Int32 => protoV1::DataType::Int32, + SDVprotoV1::DataType::Int64 => protoV1::DataType::Int64, + SDVprotoV1::DataType::Uint8 => protoV1::DataType::Uint8, + SDVprotoV1::DataType::Uint16 => protoV1::DataType::Uint16, + SDVprotoV1::DataType::Uint32 => protoV1::DataType::Uint32, + SDVprotoV1::DataType::Uint64 => protoV1::DataType::Uint64, + SDVprotoV1::DataType::Float => protoV1::DataType::Float, + SDVprotoV1::DataType::Double => protoV1::DataType::Double, + SDVprotoV1::DataType::StringArray => protoV1::DataType::StringArray, + SDVprotoV1::DataType::BoolArray => protoV1::DataType::BooleanArray, + SDVprotoV1::DataType::Int8Array => protoV1::DataType::Int8Array, + SDVprotoV1::DataType::Int16Array => protoV1::DataType::Int16Array, + SDVprotoV1::DataType::Int32Array => protoV1::DataType::Int32Array, + SDVprotoV1::DataType::Int64Array => protoV1::DataType::Int64Array, + SDVprotoV1::DataType::Uint8Array => protoV1::DataType::Uint8Array, + SDVprotoV1::DataType::Uint16Array => protoV1::DataType::Uint16Array, + SDVprotoV1::DataType::Uint32Array => protoV1::DataType::Uint32Array, + SDVprotoV1::DataType::Uint64Array => protoV1::DataType::Uint64Array, + SDVprotoV1::DataType::FloatArray => protoV1::DataType::FloatArray, + SDVprotoV1::DataType::DoubleArray => protoV1::DataType::DoubleArray, + } + } +} + +impl ConvertToV1> for SDVprotoV1::Datapoint { + fn convert_to_v1(self) -> Option { + if let Some(value) = self.value { + match value { + SDVprotoV1::datapoint::Value::StringValue(val) => { + Some(protoV1::datapoint::Value::String(val)) + } + SDVprotoV1::datapoint::Value::BoolValue(val) => { + Some(protoV1::datapoint::Value::Bool(val)) + } + SDVprotoV1::datapoint::Value::Int32Value(val) => { + Some(protoV1::datapoint::Value::Int32(val)) + } + SDVprotoV1::datapoint::Value::Int64Value(val) => { + Some(protoV1::datapoint::Value::Int64(val)) + } + SDVprotoV1::datapoint::Value::Uint32Value(val) => { + Some(protoV1::datapoint::Value::Uint32(val)) + } + SDVprotoV1::datapoint::Value::Uint64Value(val) => { + Some(protoV1::datapoint::Value::Uint64(val)) + } + SDVprotoV1::datapoint::Value::FloatValue(val) => { + Some(protoV1::datapoint::Value::Float(val)) + } + SDVprotoV1::datapoint::Value::DoubleValue(val) => { + Some(protoV1::datapoint::Value::Double(val)) + } + SDVprotoV1::datapoint::Value::StringArray(string_array) => Some( + protoV1::datapoint::Value::StringArray(protoV1::StringArray { + values: string_array.values, + }), + ), + SDVprotoV1::datapoint::Value::BoolArray(bool_array) => { + Some(protoV1::datapoint::Value::BoolArray(protoV1::BoolArray { + values: bool_array.values, + })) + } + SDVprotoV1::datapoint::Value::Int32Array(int32_array) => { + Some(protoV1::datapoint::Value::Int32Array(protoV1::Int32Array { + values: int32_array.values, + })) + } + SDVprotoV1::datapoint::Value::Int64Array(int64_array) => { + Some(protoV1::datapoint::Value::Int64Array(protoV1::Int64Array { + values: int64_array.values, + })) + } + SDVprotoV1::datapoint::Value::Uint32Array(uint32_array) => Some( + protoV1::datapoint::Value::Uint32Array(protoV1::Uint32Array { + values: uint32_array.values, + }), + ), + SDVprotoV1::datapoint::Value::Uint64Array(uint64_array) => Some( + protoV1::datapoint::Value::Uint64Array(protoV1::Uint64Array { + values: uint64_array.values, + }), + ), + SDVprotoV1::datapoint::Value::FloatArray(float_array) => { + Some(protoV1::datapoint::Value::FloatArray(protoV1::FloatArray { + values: float_array.values, + })) + } + SDVprotoV1::datapoint::Value::DoubleArray(double_array) => Some( + protoV1::datapoint::Value::DoubleArray(protoV1::DoubleArray { + values: double_array.values, + }), + ), + SDVprotoV1::datapoint::Value::FailureValue(_) => None, + } + } else { + None + } + } +} + +impl ConvertToV1 for ActuateResponseTypeV2 { + fn convert_to_v1(self) -> ActuateResponseTypeV1 { + self + } +} + +impl ConvertToV1 for SubscribeResponseTypeV2 { + fn convert_to_v1(self) -> SubscribeResponseTypeV1 { + unimplemented!("Not possible to convert stream objects!") + } +} + +impl ConvertToV1 for MetadataResponseTypeV2 { + fn convert_to_v1(self) -> MetadataResponseTypeV1 { + let transformed_vec = self + .iter() + .map(|metadata| protoV1::DataEntry { + path: metadata.path.clone(), + value: None, + actuator_target: None, + metadata: Some(metadata.clone().convert_to_v1()), + }) + .collect(); + transformed_vec + } +} + +impl ConvertToV1 for protoV2::Metadata { + fn convert_to_v1(self) -> protoV1::Metadata { + protoV1::Metadata { + data_type: self.data_type, + entry_type: self.entry_type, + description: Some(self.description), + comment: Some(self.comment), + deprecation: Some(self.deprecation), + unit: Some(self.unit), + value_restriction: match self.allowed_values { + Some(value) => { + match value.typed_value { + Some(typed_value) => { + match typed_value { + protoV2::value::TypedValue::String(val) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::String( + protoV1::ValueRestrictionString { + allowed_values: vec![val], // Wrap single value in Vec + }, + )), + }) + } + protoV2::value::TypedValue::Bool(_) => { + panic!("Boolean values are not supported in ValueRestriction") + } + protoV2::value::TypedValue::Int32(val) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Signed( + protoV1::ValueRestrictionInt { + allowed_values: vec![val as i64], // Convert to i64 + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + )), + }) + } + protoV2::value::TypedValue::Int64(val) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Signed( + protoV1::ValueRestrictionInt { + allowed_values: vec![val], + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + )), + }) + } + protoV2::value::TypedValue::Uint32(val) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Unsigned( + protoV1::ValueRestrictionUint { + allowed_values: vec![val as u64], // Convert to u64 + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + )), + }) + } + protoV2::value::TypedValue::Uint64(val) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Unsigned( + protoV1::ValueRestrictionUint { + allowed_values: vec![val], + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + )), + }) + } + protoV2::value::TypedValue::Float(val) => { + Some(protoV1::ValueRestriction { + r#type: Some( + protoV1::value_restriction::Type::FloatingPoint( + protoV1::ValueRestrictionFloat { + allowed_values: vec![val as f64], // Convert to f64 + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + ), + ), + }) + } + protoV2::value::TypedValue::Double(val) => { + Some(protoV1::ValueRestriction { + r#type: Some( + protoV1::value_restriction::Type::FloatingPoint( + protoV1::ValueRestrictionFloat { + allowed_values: vec![val], + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + ), + ), + }) + } + protoV2::value::TypedValue::StringArray(string_array) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::String( + protoV1::ValueRestrictionString { + allowed_values: string_array.values, // Use existing Vec + }, + )), + }) + } + protoV2::value::TypedValue::BoolArray(_) => { + panic!("Boolean arrays are not supported in ValueRestriction") + } + protoV2::value::TypedValue::Int32Array(int32_array) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Signed( + protoV1::ValueRestrictionInt { + allowed_values: int32_array + .values + .iter() + .map(|&x| x as i64) + .collect(), + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + )), + }) + } + protoV2::value::TypedValue::Int64Array(int64_array) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Signed( + protoV1::ValueRestrictionInt { + allowed_values: int64_array.values, + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + )), + }) + } + protoV2::value::TypedValue::Uint32Array(uint32_array) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Unsigned( + protoV1::ValueRestrictionUint { + allowed_values: uint32_array + .values + .iter() + .map(|&x| x as u64) + .collect(), + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + )), + }) + } + protoV2::value::TypedValue::Uint64Array(uint64_array) => { + Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Unsigned( + protoV1::ValueRestrictionUint { + allowed_values: uint64_array.values, + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + )), + }) + } + protoV2::value::TypedValue::FloatArray(float_array) => { + Some(protoV1::ValueRestriction { + r#type: Some( + protoV1::value_restriction::Type::FloatingPoint( + protoV1::ValueRestrictionFloat { + allowed_values: float_array + .values + .iter() + .map(|&x| x as f64) + .collect(), + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + ), + ), + }) + } + protoV2::value::TypedValue::DoubleArray(double_array) => { + Some(protoV1::ValueRestriction { + r#type: Some( + protoV1::value_restriction::Type::FloatingPoint( + protoV1::ValueRestrictionFloat { + allowed_values: double_array.values, + min: self.min.convert_to_v1(), + max: self.max.convert_to_v1(), + }, + ), + ), + }) + } + } + } + None => None, + } + } + None => None, + }, + entry_specific: None, + } + } +} + +impl ConvertToV1> for Option { + fn convert_to_v1(self) -> Option { + match self { + Some(val) => match val.typed_value { + Some(typed_value) => match typed_value { + protoV2::value::TypedValue::Int32(val) => Some(val as i64), + protoV2::value::TypedValue::Int64(val) => Some(val), + _ => panic!("wrong datatype!"), + }, + None => None, + }, + None => None, + } + } +} + +impl ConvertToV1> for Option { + fn convert_to_v1(self) -> Option { + match self { + Some(val) => match val.typed_value { + Some(typed_value) => match typed_value { + protoV2::value::TypedValue::Float(val) => Some(val as f64), + protoV2::value::TypedValue::Double(val) => Some(val), + _ => panic!("wrong datatype!"), + }, + None => None, + }, + None => None, + } + } +} + +impl ConvertToV1> for Option { + fn convert_to_v1(self) -> Option { + match self { + Some(val) => match val.typed_value { + Some(typed_value) => match typed_value { + protoV2::value::TypedValue::Uint32(val) => Some(val as u64), + protoV2::value::TypedValue::Uint64(val) => Some(val), + _ => panic!("wrong datatype!"), + }, + None => None, + }, + None => None, + } + } +} + +impl ConvertToV1> for Option { + fn convert_to_v1(self) -> Option { + match self { + Some(value) => match value.typed_value { + Some(protoV2::value::TypedValue::String(val)) => { + Some(protoV1::datapoint::Value::String(val)) + } + Some(protoV2::value::TypedValue::Bool(val)) => { + Some(protoV1::datapoint::Value::Bool(val)) + } + Some(protoV2::value::TypedValue::Int32(val)) => { + Some(protoV1::datapoint::Value::Int32(val)) + } + Some(protoV2::value::TypedValue::Int64(val)) => { + Some(protoV1::datapoint::Value::Int64(val)) + } + Some(protoV2::value::TypedValue::Uint32(val)) => { + Some(protoV1::datapoint::Value::Uint32(val)) + } + Some(protoV2::value::TypedValue::Uint64(val)) => { + Some(protoV1::datapoint::Value::Uint64(val)) + } + Some(protoV2::value::TypedValue::Float(val)) => { + Some(protoV1::datapoint::Value::Float(val)) + } + Some(protoV2::value::TypedValue::Double(val)) => { + Some(protoV1::datapoint::Value::Double(val)) + } + Some(protoV2::value::TypedValue::StringArray(arr)) => { + Some(protoV1::datapoint::Value::StringArray( + protoV1::StringArray { values: arr.values }, + )) + } + Some(protoV2::value::TypedValue::BoolArray(arr)) => { + Some(protoV1::datapoint::Value::BoolArray(protoV1::BoolArray { + values: arr.values, + })) + } + Some(protoV2::value::TypedValue::Int32Array(arr)) => { + Some(protoV1::datapoint::Value::Int32Array(protoV1::Int32Array { + values: arr.values, + })) + } + Some(protoV2::value::TypedValue::Int64Array(arr)) => { + Some(protoV1::datapoint::Value::Int64Array(protoV1::Int64Array { + values: arr.values, + })) + } + Some(protoV2::value::TypedValue::Uint32Array(arr)) => { + Some(protoV1::datapoint::Value::Uint32Array( + protoV1::Uint32Array { values: arr.values }, + )) + } + Some(protoV2::value::TypedValue::Uint64Array(arr)) => { + Some(protoV1::datapoint::Value::Uint64Array( + protoV1::Uint64Array { values: arr.values }, + )) + } + Some(protoV2::value::TypedValue::FloatArray(arr)) => { + Some(protoV1::datapoint::Value::FloatArray(protoV1::FloatArray { + values: arr.values, + })) + } + Some(protoV2::value::TypedValue::DoubleArray(arr)) => { + Some(protoV1::datapoint::Value::DoubleArray( + protoV1::DoubleArray { values: arr.values }, + )) + } + None => None, + }, + None => None, + } + } +} + +impl ConvertToV1> for Option { + fn convert_to_v1(self) -> Option { + match self { + Some(datapoint) => Some(protoV1::Datapoint { + timestamp: datapoint.timestamp, + value: datapoint.value.convert_to_v1(), + }), + None => None, + } + } +} + +impl ConvertToV1 for MultipleGetResponseTypeV2 { + fn convert_to_v1(self) -> GetResponseTypeV1 { + warn!("This method is deprecated and conversion can not figure out which path the datapoint maps to. Dev must do this in his own code. E.g. by going through the requested paths."); + self.iter() + .map(|value| protoV1::DataEntry { + path: "Unknown".to_string(), + value: Some(value.clone()).convert_to_v1(), + actuator_target: None, + metadata: None, + }) + .collect() + } +} + +pub trait ConvertToV2 { + fn convert_to_v2(self) -> T; +} + +impl ConvertToV2 for protoV1::Datapoint { + fn convert_to_v2(self) -> SensorUpdateTypeV2 { + match self.value { + Some(value) => match value { + protoV1::datapoint::Value::String(val) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::String(val)), + }, + protoV1::datapoint::Value::Bool(val) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Bool(val)), + }, + protoV1::datapoint::Value::Int32(val) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Int32(val)), + }, + protoV1::datapoint::Value::Int64(val) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Int64(val)), + }, + protoV1::datapoint::Value::Uint32(val) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Uint32(val)), + }, + protoV1::datapoint::Value::Uint64(val) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Uint64(val)), + }, + protoV1::datapoint::Value::Float(val) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Float(val)), + }, + protoV1::datapoint::Value::Double(val) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Double(val)), + }, + protoV1::datapoint::Value::StringArray(string_array) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::StringArray( + protoV2::StringArray { + values: string_array.values, + }, + )), + }, + protoV1::datapoint::Value::BoolArray(bool_array) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::BoolArray(protoV2::BoolArray { + values: bool_array.values, + })), + }, + protoV1::datapoint::Value::Int32Array(int32_array) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Int32Array( + protoV2::Int32Array { + values: int32_array.values, + }, + )), + }, + protoV1::datapoint::Value::Int64Array(int64_array) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Int64Array( + protoV2::Int64Array { + values: int64_array.values, + }, + )), + }, + protoV1::datapoint::Value::Uint32Array(uint32_array) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Uint32Array( + protoV2::Uint32Array { + values: uint32_array.values, + }, + )), + }, + protoV1::datapoint::Value::Uint64Array(uint64_array) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Uint64Array( + protoV2::Uint64Array { + values: uint64_array.values, + }, + )), + }, + protoV1::datapoint::Value::FloatArray(float_array) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::FloatArray( + protoV2::FloatArray { + values: float_array.values, + }, + )), + }, + protoV1::datapoint::Value::DoubleArray(double_array) => protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::DoubleArray( + protoV2::DoubleArray { + values: double_array.values, + }, + )), + }, + }, + None => protoV2::Value { typed_value: None }, + } + } +} + +// Since SubscribeTypeV2 is PathsTypeV2 we do not need to have a separate conversion for that one +impl ConvertToV2 for PathTypeV1 { + fn convert_to_v2(self) -> PathsTypeV2 { + self + } +} + +impl ConvertToV2 for PathTypeV1 { + fn convert_to_v2(self) -> MetadataTypeV2 { + // in the future find_common_root() could also provide filters, like everything or something like "branch1.*, branch2.*" + (find_common_root(self), "*".to_string()) + } +} + +impl ConvertToV2 for SubscribeResponseTypeV1 { + fn convert_to_v2(self) -> SubscribeResponseTypeV2 { + unimplemented!("Not possible to convert stream objects!") + } +} + +impl ConvertToV2 for UpdateActuationTypeV1 { + fn convert_to_v2(self) -> MultipleUpdateActuationTypeV2 { + let transformed_map: HashMap = self + .iter() + .map(|(key, value)| (key.clone(), value.clone().convert_to_v2())) + .collect(); + transformed_map + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_find_common_root() { + // Case 1: Common root is Vehicle.ADAS + let paths = vec![ + "Vehicle.ADAS.ABS".to_string(), + "Vehicle.ADAS.CruiseControl".to_string(), + ]; + assert_eq!(find_common_root(paths), "Vehicle.ADAS"); + + // Case 2: Common root is Vehicle + let paths = vec![ + "Vehicle.ADAS.ABS".to_string(), + "Vehicle.ADAS.CruiseControl".to_string(), + "Vehicle.Speed".to_string(), + ]; + assert_eq!(find_common_root(paths), "Vehicle"); + + // Case 3: Common root is Vehicle.ADAS.ABS + let paths = vec![ + "Vehicle.ADAS.ABS.Speed".to_string(), + "Vehicle.ADAS.ABS.Brake".to_string(), + ]; + assert_eq!(find_common_root(paths), "Vehicle.ADAS.ABS"); + + // Case 4: Single path + let paths = vec!["Vehicle.ADAS.CruiseControl.Speed".to_string()]; + assert_eq!(find_common_root(paths), "Vehicle.ADAS.CruiseControl.Speed"); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// ConvertToSDV Tests (SDV to V1) + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // impl ConvertToSDV for SensorUpdateTypeV1 {} + // only check one possibility since test for impl ConvertToSDV> for protoV1::Datapoint {} covers that + #[test] + fn test_convert_to_sdv_sensor_update_v1() { + let mut sensor_update: SensorUpdateTypeV1 = HashMap::new(); + + let datapoint = protoV1::Datapoint { + timestamp: None, + value: Some(protoV1::datapoint::Value::Int32(42)), + }; + sensor_update.insert("temperature".to_string(), datapoint.clone()); + + let converted: SensorUpdateSDVTypeV1 = sensor_update.convert_to_sdv(); + + assert_eq!(converted.len(), 1); + assert!(converted.contains_key("temperature")); + + let converted_datapoint = converted.get("temperature").unwrap(); + assert_eq!(converted_datapoint.timestamp, None); + + match &converted_datapoint.value { + Some(SDVprotoV1::datapoint::Value::Int32Value(val)) => assert_eq!(*val, 42), + _ => panic!("Unexpected value type"), + } + } + + // impl ConvertToSDV for PathTypeV1 {} + #[test] + fn test_convert_to_sdv_path_v1() { + let path: PathTypeV1 = vec![ + "sensor/temperature".to_string(), + "sensor/humidity".to_string(), + ]; + let converted: PathSDVTypeV1 = path.clone().convert_to_sdv(); + + assert_eq!(converted, path); + } + + // impl ConvertToSDV for PublishResponseTypeV1 {} + #[test] + fn test_convert_to_sdv_publish_response_v1() { + let response: PublishResponseTypeV1 = (); // Leere Einheit als Dummy-Wert + let converted: PublishResponseSDVTypeV1 = response.convert_to_sdv(); + + assert!(converted.errors.is_empty()); + } + + // impl ConvertToSDV for GetResponseTypeV1 {} + // only check one possibility since test for impl ConvertToSDV> for protoV1::Datapoint {} covers that + #[test] + fn test_convert_to_sdv_get_response_v1() { + let get_response: GetResponseTypeV1 = vec![ + protoV1::DataEntry { + path: "sensor/temperature".to_string(), + value: Some(protoV1::Datapoint { + timestamp: None, + value: Some(protoV1::datapoint::Value::Float(23.5)), + }), + actuator_target: None, + metadata: None, + }, + protoV1::DataEntry { + path: "sensor/humidity".to_string(), + value: Some(protoV1::Datapoint { + timestamp: None, + value: Some(protoV1::datapoint::Value::Float(45.2)), + }), + actuator_target: None, + metadata: None, + }, + protoV1::DataEntry { + path: "sensor/pressure".to_string(), + value: None, + actuator_target: None, + metadata: None, + }, + ]; + + let converted: GetResponseSDVTypeV1 = get_response.convert_to_sdv(); + + assert_eq!(converted.len(), 2); + assert!(converted.contains_key("sensor/temperature")); + assert!(converted.contains_key("sensor/humidity")); + assert!(!converted.contains_key("sensor/pressure")); + + assert_eq!(converted["sensor/temperature"].timestamp, None); + assert_eq!(converted["sensor/humidity"].timestamp, None); + + assert_eq!( + converted["sensor/temperature"].value, + Some(SDVprotoV1::datapoint::Value::FloatValue(23.5)) + ); + assert_eq!( + converted["sensor/humidity"].value, + Some(SDVprotoV1::datapoint::Value::FloatValue(45.2)) + ); + } + + // impl ConvertToSDV> for protoV1::Datapoint {} + #[test] + fn test_convert_to_sdv_datapoint_v1() { + let test_cases = vec![ + ( + protoV1::datapoint::Value::String("test".to_string()), + SDVprotoV1::datapoint::Value::StringValue("test".to_string()), + ), + ( + protoV1::datapoint::Value::Bool(true), + SDVprotoV1::datapoint::Value::BoolValue(true), + ), + ( + protoV1::datapoint::Value::Int32(42), + SDVprotoV1::datapoint::Value::Int32Value(42), + ), + ( + protoV1::datapoint::Value::Int64(424242), + SDVprotoV1::datapoint::Value::Int64Value(424242), + ), + ( + protoV1::datapoint::Value::Uint32(100), + SDVprotoV1::datapoint::Value::Uint32Value(100), + ), + ( + protoV1::datapoint::Value::Uint64(1000), + SDVprotoV1::datapoint::Value::Uint64Value(1000), + ), + ( + protoV1::datapoint::Value::Float(3.2), + SDVprotoV1::datapoint::Value::FloatValue(3.2), + ), + ( + protoV1::datapoint::Value::Double(6.2), + SDVprotoV1::datapoint::Value::DoubleValue(6.2), + ), + ( + protoV1::datapoint::Value::StringArray(protoV1::StringArray { + values: vec!["a".to_string(), "b".to_string()], + }), + SDVprotoV1::datapoint::Value::StringArray(SDVprotoV1::StringArray { + values: vec!["a".to_string(), "b".to_string()], + }), + ), + ( + protoV1::datapoint::Value::BoolArray(protoV1::BoolArray { + values: vec![true, false], + }), + SDVprotoV1::datapoint::Value::BoolArray(SDVprotoV1::BoolArray { + values: vec![true, false], + }), + ), + ( + protoV1::datapoint::Value::Int32Array(protoV1::Int32Array { + values: vec![1, 2, 3], + }), + SDVprotoV1::datapoint::Value::Int32Array(SDVprotoV1::Int32Array { + values: vec![1, 2, 3], + }), + ), + ( + protoV1::datapoint::Value::Int64Array(protoV1::Int64Array { + values: vec![4, 5, 6], + }), + SDVprotoV1::datapoint::Value::Int64Array(SDVprotoV1::Int64Array { + values: vec![4, 5, 6], + }), + ), + ( + protoV1::datapoint::Value::Uint32Array(protoV1::Uint32Array { + values: vec![7, 8, 9], + }), + SDVprotoV1::datapoint::Value::Uint32Array(SDVprotoV1::Uint32Array { + values: vec![7, 8, 9], + }), + ), + ( + protoV1::datapoint::Value::Uint64Array(protoV1::Uint64Array { + values: vec![10, 11, 12], + }), + SDVprotoV1::datapoint::Value::Uint64Array(SDVprotoV1::Uint64Array { + values: vec![10, 11, 12], + }), + ), + ( + protoV1::datapoint::Value::FloatArray(protoV1::FloatArray { + values: vec![1.1, 2.2, 3.3], + }), + SDVprotoV1::datapoint::Value::FloatArray(SDVprotoV1::FloatArray { + values: vec![1.1, 2.2, 3.3], + }), + ), + ( + protoV1::datapoint::Value::DoubleArray(protoV1::DoubleArray { + values: vec![4.4, 5.5, 6.6], + }), + SDVprotoV1::datapoint::Value::DoubleArray(SDVprotoV1::DoubleArray { + values: vec![4.4, 5.5, 6.6], + }), + ), + ]; + + for (input, expected) in test_cases { + let datapoint = protoV1::Datapoint { + timestamp: None, + value: Some(input), + }; + let converted = datapoint.convert_to_sdv(); + assert_eq!(converted, Some(expected)); + } + + // Test None case + let datapoint_none = protoV1::Datapoint { + timestamp: None, + value: None, + }; + assert_eq!(datapoint_none.convert_to_sdv(), None); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// ConvertToV1 Tests (SDV to V1) + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // impl ConvertToV1 for SensorUpdateSDVTypeV1 {} + // only check one possibility since test for impl ConvertToV1> for SDVprotoV1::Datapoint {} covers that + #[test] + fn test_convert_to_v1_sensor_update_sdv() { + let mut input: SensorUpdateSDVTypeV1 = HashMap::new(); + input.insert( + "sensor1".to_string(), + SDVprotoV1::Datapoint { + timestamp: None, + value: Some(SDVprotoV1::datapoint::Value::Int32Value(42)), + }, + ); + + input.insert( + "sensor2".to_string(), + SDVprotoV1::Datapoint { + timestamp: None, + value: None, + }, + ); + + let output: SensorUpdateTypeV1 = input.convert_to_v1(); + + assert_eq!(output.len(), 2); + assert!(output.contains_key("sensor1")); + assert_eq!(output["sensor1"].timestamp, None); + assert_eq!( + output["sensor1"].value, + Some(protoV1::datapoint::Value::Int32(42)) + ); + + assert!(output.contains_key("sensor2")); + assert_eq!(output["sensor2"].timestamp, None); + assert_eq!(output["sensor2"].value, None); + } + + // impl ConvertToV1 for PathSDVTypeV1 {} + #[test] + fn test_convert_to_v1_path_sdv() { + let input: PathSDVTypeV1 = vec!["path1".to_string(), "path2".to_string()]; + let output: PathTypeV1 = input.convert_to_v1(); + assert_eq!(output, vec!["path1", "path2"]); + } + + // impl ConvertToV1 for GetResponseSDVTypeV1 {} + // only check one possibility since test for impl ConvertToV1> for SDVprotoV1::Datapoint {} covers that + #[test] + fn test_convert_to_v1_get_sdv() { + let mut sdv_response = GetResponseSDVTypeV1::new(); + + sdv_response.insert( + "Vehicle.TestUint32".to_string(), + SDVprotoV1::Datapoint { + value: Some(SDVprotoV1::datapoint::Value::Uint32Value(42)), + timestamp: None, + }, + ); + + let v1_response: GetResponseTypeV1 = sdv_response.convert_to_v1(); + + let expected = vec![protoV1::DataEntry { + path: "Vehicle.TestUint32".to_string(), + value: Some(protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::Uint32(42)), + timestamp: None, + }), + actuator_target: None, + metadata: None, + }]; + + assert_eq!(v1_response, expected); + } + + // impl ConvertToV1 for MetadataResponseSDVTypeV1 {} + // only check one possibility since test for impl ConvertToV1> for SDVprotoV1::Datapoint {} covers that + #[test] + fn test_convert_to_v1_metadata_response_sdv() { + let metadata_sdv = vec![ + SDVprotoV1::Metadata { + id: 1, + entry_type: SDVprotoV1::EntryType::Sensor.into(), + name: "Vehicle.Speed".to_string(), + data_type: SDVprotoV1::DataType::Uint32.into(), + change_type: SDVprotoV1::ChangeType::OnChange.into(), + description: "Speed of the vehicle".to_string(), + allowed: None, + min: None, + max: None, + }, + SDVprotoV1::Metadata { + id: 2, + entry_type: SDVprotoV1::EntryType::Actuator.into(), + name: "Vehicle.Battery".to_string(), + data_type: SDVprotoV1::DataType::Float.into(), + change_type: SDVprotoV1::ChangeType::Continuous.into(), + description: "Battery level".to_string(), + allowed: None, + min: None, + max: None, + }, + ]; + + let expected_metadata = vec![ + protoV1::DataEntry { + path: "Vehicle.Speed".to_string(), + value: None, + actuator_target: None, + metadata: Some(protoV1::Metadata { + data_type: protoV1::DataType::Uint32.into(), + entry_type: protoV1::EntryType::Sensor.into(), + description: Some("Speed of the vehicle".to_string()), + comment: None, + deprecation: None, + unit: None, + value_restriction: None, + entry_specific: None, + }), + }, + protoV1::DataEntry { + path: "Vehicle.Battery".to_string(), + value: None, + actuator_target: None, + metadata: Some(protoV1::Metadata { + data_type: protoV1::DataType::Float.into(), + entry_type: protoV1::EntryType::Actuator.into(), + description: Some("Battery level".to_string()), + comment: None, + deprecation: None, + unit: None, + value_restriction: None, + entry_specific: None, + }), + }, + ]; + + let result: Vec = metadata_sdv.convert_to_v1(); + + assert_eq!(result, expected_metadata); + } + + #[test] + fn test_convert_to_v1_metadata_sdv() { + let metadata_cases = vec![ + ( + SDVprotoV1::Metadata { + data_type: SDVprotoV1::DataType::Int32.into(), + entry_type: SDVprotoV1::EntryType::Sensor.into(), + description: "Temperature sensor".to_string(), + allowed: None, + id: 1, + name: "Test1".to_string(), + change_type: SDVprotoV1::ChangeType::OnChange.into(), + min: None, + max: None, + }, + protoV1::Metadata { + data_type: protoV1::DataType::Int32.into(), + entry_type: protoV1::EntryType::Sensor.into(), + description: Some("Temperature sensor".to_string()), + comment: None, + deprecation: None, + unit: None, + value_restriction: None, + entry_specific: None, + }, + ), + ( + SDVprotoV1::Metadata { + data_type: SDVprotoV1::DataType::Float.into(), + entry_type: SDVprotoV1::EntryType::Actuator.into(), + description: "Pressure actuator".to_string(), + allowed: Some(SDVprotoV1::Allowed { + values: Some(SDVprotoV1::allowed::Values::FloatValues( + SDVprotoV1::FloatArray { + values: vec![1.0, 2.5, 3.75], + }, + )), + }), + id: 2, + name: "Test2".to_string(), + change_type: SDVprotoV1::ChangeType::OnChange.into(), + min: Some(SDVprotoV1::ValueRestriction { + typed_value: Some(SDVprotoV1::value_restriction::TypedValue::Float(12.0)), + }), + max: Some(SDVprotoV1::ValueRestriction { + typed_value: Some(SDVprotoV1::value_restriction::TypedValue::Float(22.0)), + }), + }, + protoV1::Metadata { + data_type: protoV1::DataType::Float.into(), + entry_type: protoV1::EntryType::Actuator.into(), + description: Some("Pressure actuator".to_string()), + comment: None, + deprecation: None, + unit: None, + value_restriction: Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::FloatingPoint( + protoV1::ValueRestrictionFloat { + min: Some(12.0), + max: Some(22.0), + allowed_values: vec![1.0, 2.5, 3.75], + }, + )), + }), + entry_specific: None, + }, + ), + ( + SDVprotoV1::Metadata { + data_type: SDVprotoV1::DataType::Uint32.into(), + entry_type: SDVprotoV1::EntryType::Sensor.into(), + description: "Speed sensor".to_string(), + allowed: Some(SDVprotoV1::Allowed { + values: Some(SDVprotoV1::allowed::Values::Uint32Values( + SDVprotoV1::Uint32Array { + values: vec![0, 5, 10, 200], + }, + )), + }), + id: 3, + name: "Test3".to_string(), + change_type: SDVprotoV1::ChangeType::OnChange.into(), + min: Some(SDVprotoV1::ValueRestriction { + typed_value: Some(SDVprotoV1::value_restriction::TypedValue::Uint32(0)), + }), + max: Some(SDVprotoV1::ValueRestriction { + typed_value: Some(SDVprotoV1::value_restriction::TypedValue::Uint32(200)), + }), + }, + protoV1::Metadata { + data_type: protoV1::DataType::Uint32.into(), + entry_type: protoV1::EntryType::Sensor.into(), + description: Some("Speed sensor".to_string()), + comment: None, + deprecation: None, + unit: None, + value_restriction: Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Unsigned( + protoV1::ValueRestrictionUint { + min: Some(0), + max: Some(200), + allowed_values: vec![0, 5, 10, 200], + }, + )), + }), + entry_specific: None, + }, + ), + ( + SDVprotoV1::Metadata { + data_type: SDVprotoV1::DataType::Uint64.into(), + entry_type: SDVprotoV1::EntryType::Sensor.into(), + description: "Odometer reading".to_string(), + allowed: Some(SDVprotoV1::Allowed { + values: Some(SDVprotoV1::allowed::Values::Uint64Values( + SDVprotoV1::Uint64Array { + values: vec![0, 1, 2, 3, 4, 5, 6], + }, + )), + }), + id: 4, + name: "Test4".to_string(), + change_type: SDVprotoV1::ChangeType::OnChange.into(), + min: Some(SDVprotoV1::ValueRestriction { + typed_value: Some(SDVprotoV1::value_restriction::TypedValue::Uint64(0)), + }), + max: None, + }, + protoV1::Metadata { + data_type: protoV1::DataType::Uint64.into(), + entry_type: protoV1::EntryType::Sensor.into(), + description: Some("Odometer reading".to_string()), + comment: None, + deprecation: None, + unit: None, + value_restriction: Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Unsigned( + protoV1::ValueRestrictionUint { + min: Some(0), + max: None, + allowed_values: vec![0, 1, 2, 3, 4, 5, 6], + }, + )), + }), + entry_specific: None, + }, + ), + ( + SDVprotoV1::Metadata { + data_type: SDVprotoV1::DataType::Double.into(), + entry_type: SDVprotoV1::EntryType::Sensor.into(), + description: "Altitude sensor".to_string(), + allowed: Some(SDVprotoV1::Allowed { + values: Some(SDVprotoV1::allowed::Values::DoubleValues( + SDVprotoV1::DoubleArray { + values: vec![-500.0, -250.0, 0.0, 3000.0, 9000.0], + }, + )), + }), + id: 4, + name: "Test4".to_string(), + change_type: SDVprotoV1::ChangeType::OnChange.into(), + min: Some(SDVprotoV1::ValueRestriction { + typed_value: Some(SDVprotoV1::value_restriction::TypedValue::Double( + -500.0, + )), + }), + max: Some(SDVprotoV1::ValueRestriction { + typed_value: Some(SDVprotoV1::value_restriction::TypedValue::Double( + 9000.0, + )), + }), + }, + protoV1::Metadata { + data_type: protoV1::DataType::Double.into(), + entry_type: protoV1::EntryType::Sensor.into(), + description: Some("Altitude sensor".to_string()), + comment: None, + deprecation: None, + unit: None, + value_restriction: Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::FloatingPoint( + protoV1::ValueRestrictionFloat { + min: Some(-500.0), + max: Some(9000.0), + allowed_values: vec![-500.0, -250.0, 0.0, 3000.0, 9000.0], + }, + )), + }), + entry_specific: None, + }, + ), + ( + SDVprotoV1::Metadata { + data_type: SDVprotoV1::DataType::String.into(), + entry_type: SDVprotoV1::EntryType::Sensor.into(), + description: "Status message".to_string(), + allowed: Some(SDVprotoV1::Allowed { + values: Some(SDVprotoV1::allowed::Values::StringValues( + SDVprotoV1::StringArray { + values: vec![ + "STATUS_UNKNOWN".to_string(), + "STATUS_DEFINED".to_string(), + "STATUS_DONE".to_string(), + ], + }, + )), + }), + id: 5, + name: "Test5".to_string(), + change_type: SDVprotoV1::ChangeType::OnChange.into(), + min: None, + max: None, + }, + protoV1::Metadata { + data_type: protoV1::DataType::String.into(), + entry_type: protoV1::EntryType::Sensor.into(), + description: Some("Status message".to_string()), + comment: None, + deprecation: None, + unit: None, + value_restriction: Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::String( + protoV1::ValueRestrictionString { + allowed_values: vec![ + "STATUS_UNKNOWN".to_string(), + "STATUS_DEFINED".to_string(), + "STATUS_DONE".to_string(), + ], + }, + )), + }), + entry_specific: None, + }, + ), + ]; + for (input, expected) in metadata_cases.iter() { + let result = input.clone().convert_to_v1(); + assert_eq!( + result, *expected, + "Test failed for input: {:?}\nExpected: {:?}\nGot: {:?}", + input, expected, result + ); + } + } + + // impl ConvertToV1 for SDVprotoV1::EntryType {} + #[test] + fn test_convert_to_v1_entry_type_sdv() { + assert_eq!( + SDVprotoV1::EntryType::Sensor.convert_to_v1(), + protoV1::EntryType::Sensor + ); + assert_eq!( + SDVprotoV1::EntryType::Actuator.convert_to_v1(), + protoV1::EntryType::Actuator + ); + assert_eq!( + SDVprotoV1::EntryType::Attribute.convert_to_v1(), + protoV1::EntryType::Attribute + ); + assert_eq!( + SDVprotoV1::EntryType::Unspecified.convert_to_v1(), + protoV1::EntryType::Unspecified + ); + } + + // impl ConvertToV1 for SDVprotoV1::DataType {} + #[test] + fn test_convert_to_v1_data_type_sdv() { + use protoV1::DataType as V1; + use SDVprotoV1::DataType as SDV; + + let mappings = vec![ + (SDV::String, V1::String), + (SDV::Bool, V1::Boolean), + (SDV::Int8, V1::Int8), + (SDV::Int16, V1::Int16), + (SDV::Int32, V1::Int32), + (SDV::Int64, V1::Int64), + (SDV::Uint8, V1::Uint8), + (SDV::Uint16, V1::Uint16), + (SDV::Uint32, V1::Uint32), + (SDV::Uint64, V1::Uint64), + (SDV::Float, V1::Float), + (SDV::Double, V1::Double), + (SDV::StringArray, V1::StringArray), + (SDV::BoolArray, V1::BooleanArray), + (SDV::Int8Array, V1::Int8Array), + (SDV::Int16Array, V1::Int16Array), + (SDV::Int32Array, V1::Int32Array), + (SDV::Int64Array, V1::Int64Array), + (SDV::Uint8Array, V1::Uint8Array), + (SDV::Uint16Array, V1::Uint16Array), + (SDV::Uint32Array, V1::Uint32Array), + (SDV::Uint64Array, V1::Uint64Array), + (SDV::FloatArray, V1::FloatArray), + (SDV::DoubleArray, V1::DoubleArray), + ]; + + for (sdv_type, expected_v1) in mappings { + assert_eq!(sdv_type.convert_to_v1(), expected_v1); + } + } + + // impl ConvertToV1> for SDVprotoV1::Datapoint {} + #[test] + fn test_convert_to_v1_datapoint_sdv() { + use protoV1::datapoint::Value as V1Value; + use SDVprotoV1::datapoint::Value as SDVValue; + + let test_cases = vec![ + ( + SDVValue::StringValue("hello".to_string()), + V1Value::String("hello".to_string()), + ), + (SDVValue::BoolValue(true), V1Value::Bool(true)), + (SDVValue::Int32Value(67890), V1Value::Int32(67890)), + (SDVValue::Int64Value(9876543210), V1Value::Int64(9876543210)), + ( + SDVValue::Uint32Value(4294967295), + V1Value::Uint32(4294967295), + ), + ( + SDVValue::Uint64Value(18446744073709551615), + V1Value::Uint64(18446744073709551615), + ), + (SDVValue::FloatValue(3.2), V1Value::Float(3.2)), + (SDVValue::DoubleValue(2.8), V1Value::Double(2.8)), + ( + SDVValue::StringArray(SDVprotoV1::StringArray { + values: vec!["a".to_string(), "b".to_string()], + }), + V1Value::StringArray(protoV1::StringArray { + values: vec!["a".to_string(), "b".to_string()], + }), + ), + ( + SDVValue::BoolArray(SDVprotoV1::BoolArray { + values: vec![true, false, true], + }), + V1Value::BoolArray(protoV1::BoolArray { + values: vec![true, false, true], + }), + ), + ( + SDVValue::Int32Array(SDVprotoV1::Int32Array { + values: vec![10, 20, 30], + }), + V1Value::Int32Array(protoV1::Int32Array { + values: vec![10, 20, 30], + }), + ), + ( + SDVValue::Int64Array(SDVprotoV1::Int64Array { + values: vec![1000, 2000, 3000], + }), + V1Value::Int64Array(protoV1::Int64Array { + values: vec![1000, 2000, 3000], + }), + ), + ( + SDVValue::Uint32Array(SDVprotoV1::Uint32Array { + values: vec![100000, 200000, 300000], + }), + V1Value::Uint32Array(protoV1::Uint32Array { + values: vec![100000, 200000, 300000], + }), + ), + ( + SDVValue::Uint64Array(SDVprotoV1::Uint64Array { + values: vec![9000000000, 8000000000], + }), + V1Value::Uint64Array(protoV1::Uint64Array { + values: vec![9000000000, 8000000000], + }), + ), + ( + SDVValue::FloatArray(SDVprotoV1::FloatArray { + values: vec![1.1, 2.2, 3.3], + }), + V1Value::FloatArray(protoV1::FloatArray { + values: vec![1.1, 2.2, 3.3], + }), + ), + ( + SDVValue::DoubleArray(SDVprotoV1::DoubleArray { + values: vec![0.0001, 0.0002, 0.0003], + }), + V1Value::DoubleArray(protoV1::DoubleArray { + values: vec![0.0001, 0.0002, 0.0003], + }), + ), + ]; + + for (sdv_value, expected_v1) in test_cases { + let datapoint = SDVprotoV1::Datapoint { + value: Some(sdv_value), + timestamp: None, + }; + assert_eq!(datapoint.convert_to_v1(), Some(expected_v1)); + } + + let datapoint_failure = SDVprotoV1::Datapoint { + value: Some(SDVValue::FailureValue(0)), + timestamp: None, + }; + assert_eq!(datapoint_failure.convert_to_v1(), None); + + let datapoint_none = SDVprotoV1::Datapoint { + value: None, + timestamp: None, + }; + assert_eq!(datapoint_none.convert_to_v1(), None); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// ConvertToV1 Tests (V2 to V1) + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // impl ConvertToV1 for MetadataResponseTypeV2 {} + #[test] + fn test_convert_to_v1_metadata_response_v2() { + let metadata_response = vec![protoV2::Metadata { + data_type: protoV2::DataType::Int32.into(), + entry_type: protoV2::EntryType::Sensor.into(), + description: "Temperature sensor".to_string(), + id: 1, + min: None, + max: None, + path: "Vehicle.TestNone".to_string(), + comment: "".to_string(), + deprecation: "".to_string(), + unit: "".to_string(), + allowed_values: None, + min_sample_interval: None, + }]; + let expected_metadata_response = vec![protoV1::DataEntry { + metadata: Some(protoV1::Metadata { + data_type: protoV1::DataType::Int32.into(), + entry_type: protoV1::EntryType::Sensor.into(), + description: Some("Temperature sensor".to_string()), + value_restriction: None, + comment: Some("".to_string()), + deprecation: Some("".to_string()), + unit: Some("".to_string()), + entry_specific: None, + }), + path: "Vehicle.TestNone".to_string(), + value: None, + actuator_target: None, + }]; + + let result: MetadataResponseTypeV1 = metadata_response.convert_to_v1(); + assert_eq!(result.len(), 1); + assert_eq!(result, expected_metadata_response); + } + + // impl ConvertToV1 for protoV2::Metadata {} + #[test] + fn test_convert_to_v1_metadata_v2() { + let metadata_cases = vec![ + ( + protoV2::Metadata { + data_type: protoV2::DataType::Int32.into(), + entry_type: protoV2::EntryType::Sensor.into(), + description: "Temperature sensor".to_string(), + id: 1, + min: None, + max: None, + path: "Vehicle.TestNone".to_string(), + comment: "".to_string(), + deprecation: "".to_string(), + unit: "".to_string(), + allowed_values: None, + min_sample_interval: None, + }, + protoV1::Metadata { + data_type: protoV1::DataType::Int32.into(), + entry_type: protoV1::EntryType::Sensor.into(), + description: Some("Temperature sensor".to_string()), + value_restriction: None, + comment: Some("".to_string()), + deprecation: Some("".to_string()), + unit: Some("".to_string()), + entry_specific: None, + }, + ), + ( + protoV2::Metadata { + data_type: protoV2::DataType::Float.into(), + entry_type: protoV2::EntryType::Actuator.into(), + description: "Pressure actuator".to_string(), + id: 2, + min: Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Float(12.0)), + }), + max: Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Float(22.0)), + }), + path: "Vehicle.TestFloat".to_string(), + comment: "".to_string(), + deprecation: "".to_string(), + unit: "".to_string(), + allowed_values: Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::FloatArray( + protoV2::FloatArray { + values: vec![1.0, 2.5, 3.75], + }, + )), + }), + min_sample_interval: None, + }, + protoV1::Metadata { + data_type: protoV1::DataType::Float.into(), + entry_type: protoV1::EntryType::Actuator.into(), + description: Some("Pressure actuator".to_string()), + value_restriction: Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::FloatingPoint( + protoV1::ValueRestrictionFloat { + min: Some(12.0), + max: Some(22.0), + allowed_values: vec![1.0, 2.5, 3.75], + }, + )), + }), + comment: Some("".to_string()), + deprecation: Some("".to_string()), + unit: Some("".to_string()), + entry_specific: None, + }, + ), + ( + protoV2::Metadata { + data_type: protoV2::DataType::Uint32.into(), + entry_type: protoV2::EntryType::Sensor.into(), + description: "Speed sensor".to_string(), + id: 5, + min: Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Uint32(0)), + }), + max: Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Uint32(200)), + }), + path: "Vehicle.TestUint32".to_string(), + comment: "".to_string(), + deprecation: "".to_string(), + unit: "".to_string(), + allowed_values: Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Uint32Array( + protoV2::Uint32Array { + values: vec![0, 5, 100, 200], + }, + )), + }), + min_sample_interval: None, + }, + protoV1::Metadata { + data_type: protoV1::DataType::Uint32.into(), + entry_type: protoV1::EntryType::Sensor.into(), + description: Some("Speed sensor".to_string()), + value_restriction: Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Unsigned( + protoV1::ValueRestrictionUint { + min: Some(0), + max: Some(200), + allowed_values: vec![0, 5, 100, 200], + }, + )), + }), + comment: Some("".to_string()), + deprecation: Some("".to_string()), + unit: Some("".to_string()), + entry_specific: None, + }, + ), + ( + protoV2::Metadata { + data_type: protoV2::DataType::Uint64.into(), + entry_type: protoV2::EntryType::Sensor.into(), + description: "Odometer reading".to_string(), + id: 6, + min: Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Uint64(0)), + }), + max: None, + path: "Vehicle.TestUint64".to_string(), + comment: "".to_string(), + deprecation: "".to_string(), + unit: "km".to_string(), + allowed_values: Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Uint64Array( + protoV2::Uint64Array { + values: vec![0, 1, 2, 3, 4, 5, 6], + }, + )), + }), + min_sample_interval: None, + }, + protoV1::Metadata { + data_type: protoV1::DataType::Uint64.into(), + entry_type: protoV1::EntryType::Sensor.into(), + description: Some("Odometer reading".to_string()), + comment: Some("".to_string()), + deprecation: Some("".to_string()), + unit: Some("km".to_string()), + value_restriction: Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::Unsigned( + protoV1::ValueRestrictionUint { + min: Some(0), + max: None, + allowed_values: vec![0, 1, 2, 3, 4, 5, 6], + }, + )), + }), + entry_specific: None, + }, + ), + ( + protoV2::Metadata { + data_type: protoV2::DataType::Double.into(), + entry_type: protoV2::EntryType::Sensor.into(), + description: "Altitude sensor".to_string(), + id: 7, + min: Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Double(-500.0)), + }), + max: Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Double(9000.0)), + }), + path: "Vehicle.TestDouble".to_string(), + comment: "".to_string(), + deprecation: "".to_string(), + unit: "m".to_string(), + allowed_values: Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::DoubleArray( + protoV2::DoubleArray { + values: vec![-500.0, -250.0, 0.0, 3000.0, 9000.0], + }, + )), + }), + min_sample_interval: None, + }, + protoV1::Metadata { + data_type: protoV1::DataType::Double.into(), + entry_type: protoV1::EntryType::Sensor.into(), + description: Some("Altitude sensor".to_string()), + comment: Some("".to_string()), + deprecation: Some("".to_string()), + unit: Some("m".to_string()), + value_restriction: Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::FloatingPoint( + protoV1::ValueRestrictionFloat { + min: Some(-500.0), + max: Some(9000.0), + allowed_values: vec![-500.0, -250.0, 0.0, 3000.0, 9000.0], + }, + )), + }), + entry_specific: None, + }, + ), + ( + protoV2::Metadata { + data_type: protoV2::DataType::String.into(), + entry_type: protoV2::EntryType::Sensor.into(), + description: "Status message".to_string(), + id: 8, + min: None, + max: None, + path: "Vehicle.TestString".to_string(), + comment: "".to_string(), + deprecation: "".to_string(), + unit: "".to_string(), + allowed_values: Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::StringArray( + protoV2::StringArray { + values: vec![ + "STATUS_UNKNOWN".to_string(), + "STATUS_DEFINED".to_string(), + "STATUS_DONE".to_string(), + ], + }, + )), + }), + min_sample_interval: None, + }, + protoV1::Metadata { + data_type: protoV1::DataType::String.into(), + entry_type: protoV1::EntryType::Sensor.into(), + description: Some("Status message".to_string()), + comment: Some("".to_string()), + deprecation: Some("".to_string()), + unit: Some("".to_string()), + value_restriction: Some(protoV1::ValueRestriction { + r#type: Some(protoV1::value_restriction::Type::String( + protoV1::ValueRestrictionString { + allowed_values: vec![ + "STATUS_UNKNOWN".to_string(), + "STATUS_DEFINED".to_string(), + "STATUS_DONE".to_string(), + ], + }, + )), + }), + entry_specific: None, + }, + ), + ]; + + for (metadata_v2, expected_v1) in metadata_cases { + let output_v1: protoV1::Metadata = metadata_v2.convert_to_v1(); + assert_eq!(output_v1, expected_v1); + } + } + + // impl ConvertToV1> for Option {} + #[test] + fn test_convert_to_v1_value_v2() { + let cases = vec![ + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::String("test".into())), + }), + Some(protoV1::datapoint::Value::String("test".into())), + ), + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Bool(true)), + }), + Some(protoV1::datapoint::Value::Bool(true)), + ), + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Int32(42)), + }), + Some(protoV1::datapoint::Value::Int32(42)), + ), + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Int64(100)), + }), + Some(protoV1::datapoint::Value::Int64(100)), + ), + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Uint32(42)), + }), + Some(protoV1::datapoint::Value::Uint32(42)), + ), + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Uint64(100)), + }), + Some(protoV1::datapoint::Value::Uint64(100)), + ), + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Float(3.2)), + }), + Some(protoV1::datapoint::Value::Float(3.2)), + ), + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Double(2.7)), + }), + Some(protoV1::datapoint::Value::Double(2.7)), + ), + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::StringArray( + protoV2::StringArray { + values: vec!["a".into(), "b".into()], + }, + )), + }), + Some(protoV1::datapoint::Value::StringArray( + protoV1::StringArray { + values: vec!["a".into(), "b".into()], + }, + )), + ), + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::BoolArray(protoV2::BoolArray { + values: vec![true, false], + })), + }), + Some(protoV1::datapoint::Value::BoolArray(protoV1::BoolArray { + values: vec![true, false], + })), + ), + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Int32Array( + protoV2::Int32Array { + values: vec![1, 2, 3], + }, + )), + }), + Some(protoV1::datapoint::Value::Int32Array(protoV1::Int32Array { + values: vec![1, 2, 3], + })), + ), + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Int64Array( + protoV2::Int64Array { + values: vec![4, 5, 6], + }, + )), + }), + Some(protoV1::datapoint::Value::Int64Array(protoV1::Int64Array { + values: vec![4, 5, 6], + })), + ), + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Uint32Array( + protoV2::Uint32Array { + values: vec![7, 8, 9], + }, + )), + }), + Some(protoV1::datapoint::Value::Uint32Array( + protoV1::Uint32Array { + values: vec![7, 8, 9], + }, + )), + ), + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Uint64Array( + protoV2::Uint64Array { + values: vec![10, 11, 12], + }, + )), + }), + Some(protoV1::datapoint::Value::Uint64Array( + protoV1::Uint64Array { + values: vec![10, 11, 12], + }, + )), + ), + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::FloatArray( + protoV2::FloatArray { + values: vec![1.1, 2.2, 3.3], + }, + )), + }), + Some(protoV1::datapoint::Value::FloatArray(protoV1::FloatArray { + values: vec![1.1, 2.2, 3.3], + })), + ), + ( + Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::DoubleArray( + protoV2::DoubleArray { + values: vec![4.4, 5.5, 6.6], + }, + )), + }), + Some(protoV1::datapoint::Value::DoubleArray( + protoV1::DoubleArray { + values: vec![4.4, 5.5, 6.6], + }, + )), + ), + (None, None), + ]; + + for (input, expected) in cases { + assert_eq!( as ConvertToV1>>::convert_to_v1(input), expected); + } + } + + // impl ConvertToV1> for Option {} + // only check one possibility since test for // impl ConvertToV1> for Option covers that + #[test] + fn test_convert_to_v1_datapoint_v2() { + let cases: Vec<(Option, Option)> = vec![ + ( + Some(protoV2::Datapoint { + timestamp: None, + value: Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Int32(42)), + }), + }), + Some(protoV1::Datapoint { + timestamp: None, + value: Some(protoV1::datapoint::Value::Int32(42)), + }), + ), + ( + Some(protoV2::Datapoint { + timestamp: None, + value: None, + }), + Some(protoV1::Datapoint { + timestamp: None, + value: None, + }), + ), + (None, None), + ]; + + for (input, expected) in cases { + assert_eq!(input.convert_to_v1(), expected); + } + } + + // impl ConvertToV1 for MultipleGetResponseTypeV2 {} + // only check one possibility since test for impl ConvertToV1> for Option covers that + #[test] + fn test_convert_to_v1_multiple_get_response_v2() { + let cases: Vec<(MultipleGetResponseTypeV2, GetResponseTypeV1)> = vec![ + ( + vec![protoV2::Datapoint { + timestamp: None, + value: Some(protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Int32(42)), + }), + }], + vec![protoV1::DataEntry { + path: "Unknown".to_string(), + value: Some(protoV1::Datapoint { + timestamp: None, + value: Some(protoV1::datapoint::Value::Int32(42)), + }), + actuator_target: None, + metadata: None, + }], + ), + (vec![], vec![]), + ]; + + for (input, expected) in cases { + assert_eq!(input.convert_to_v1(), expected); + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// ConvertToV2 Tests (V1 to V2) + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // impl ConvertToV2 for protoV1::Datapoint {} + #[test] + fn test_convert_to_v2_datapoint_v1() { + let test_cases = vec![ + ( + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::String("test".to_string())), + timestamp: None, + }, + Some(protoV2::value::TypedValue::String("test".to_string())), + ), + ( + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::Bool(true)), + timestamp: None, + }, + Some(protoV2::value::TypedValue::Bool(true)), + ), + ( + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::Int32(42)), + timestamp: None, + }, + Some(protoV2::value::TypedValue::Int32(42)), + ), + ( + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::Int64(42)), + timestamp: None, + }, + Some(protoV2::value::TypedValue::Int64(42)), + ), + ( + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::Uint32(42)), + timestamp: None, + }, + Some(protoV2::value::TypedValue::Uint32(42)), + ), + ( + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::Uint64(42)), + timestamp: None, + }, + Some(protoV2::value::TypedValue::Uint64(42)), + ), + ( + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::Float(3.2)), + timestamp: None, + }, + Some(protoV2::value::TypedValue::Float(3.2)), + ), + ( + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::Double(3.2)), + timestamp: None, + }, + Some(protoV2::value::TypedValue::Double(3.2)), + ), + ( + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::StringArray( + protoV1::StringArray { + values: vec!["a".to_string(), "b".to_string()], + }, + )), + timestamp: None, + }, + Some(protoV2::value::TypedValue::StringArray( + protoV2::StringArray { + values: vec!["a".to_string(), "b".to_string()], + }, + )), + ), + ( + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::BoolArray(protoV1::BoolArray { + values: vec![true, false, true], + })), + timestamp: None, + }, + Some(protoV2::value::TypedValue::BoolArray(protoV2::BoolArray { + values: vec![true, false, true], + })), + ), + ( + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::Int32Array(protoV1::Int32Array { + values: vec![1, 2, 3], + })), + timestamp: None, + }, + Some(protoV2::value::TypedValue::Int32Array( + protoV2::Int32Array { + values: vec![1, 2, 3], + }, + )), + ), + ( + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::Int64Array(protoV1::Int64Array { + values: vec![1, 2, 3], + })), + timestamp: None, + }, + Some(protoV2::value::TypedValue::Int64Array( + protoV2::Int64Array { + values: vec![1, 2, 3], + }, + )), + ), + ( + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::FloatArray(protoV1::FloatArray { + values: vec![1.1, 2.2, 3.3], + })), + timestamp: None, + }, + Some(protoV2::value::TypedValue::FloatArray( + protoV2::FloatArray { + values: vec![1.1, 2.2, 3.3], + }, + )), + ), + ( + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::DoubleArray( + protoV1::DoubleArray { + values: vec![1.1, 2.2, 3.3], + }, + )), + timestamp: None, + }, + Some(protoV2::value::TypedValue::DoubleArray( + protoV2::DoubleArray { + values: vec![1.1, 2.2, 3.3], + }, + )), + ), + ( + protoV1::Datapoint { + value: None, + timestamp: None, + }, + None, + ), + ( + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::Int32(42)), + timestamp: Some(prost_types::Timestamp { + seconds: 1627845623, + nanos: 123456789, + }), + }, + Some(protoV2::value::TypedValue::Int32(42)), + ), + ]; + + for (dp_v1, expected_v2) in test_cases { + let result = dp_v1.convert_to_v2(); + assert_eq!(result.typed_value, expected_v2); + } + } + + // impl ConvertToV2 for PathTypeV1 {} + // impl ConvertToV2 for PathTypeV1 {} + #[test] + fn test_convert_to_v2_path_type_v1() { + let paths: PathTypeV1 = vec![ + "Vehicle.Engine.Speed".to_string(), + "Vehicle.Engine.RPM".to_string(), + ]; + let result: PathsTypeV2 = paths.convert_to_v2(); + assert_eq!( + result, + vec![ + "Vehicle.Engine.Speed".to_string(), + "Vehicle.Engine.RPM".to_string() + ] + ); + } + + // impl ConvertToV2 for PathTypeV1 {} + #[test] + fn test_convert_to_v2_metadata_v1() { + let paths: PathTypeV1 = vec![ + "Vehicle.Engine.Speed".to_string(), + "Vehicle.Engine.RPM".to_string(), + ]; + let result: MetadataTypeV2 = paths.convert_to_v2(); + assert_eq!(result, ("Vehicle.Engine".to_string(), "*".to_string())); + } + + // impl ConvertToV2 for UpdateActuationTypeV1 {} + // only check one possibility since test for impl ConvertToV2 for protoV1::Datapoint covers that + #[test] + fn test_convert_to_v2_update_actuation_v1() { + let mut update_map: UpdateActuationTypeV1 = HashMap::new(); + update_map.insert( + "Vehicle.Engine.Speed".to_string(), + protoV1::Datapoint { + value: Some(protoV1::datapoint::Value::Int32(100)), + timestamp: None, + }, + ); + let result: MultipleUpdateActuationTypeV2 = update_map.convert_to_v2(); + assert_eq!( + result.get("Vehicle.Engine.Speed"), + Some(&protoV2::Value { + typed_value: Some(protoV2::value::TypedValue::Int32(100)) + }) + ); + } +} diff --git a/lib/common/src/lib.rs b/lib/common/src/lib.rs index ac6a7390..baa49a24 100644 --- a/lib/common/src/lib.rs +++ b/lib/common/src/lib.rs @@ -1,5 +1,5 @@ /******************************************************************************** -* Copyright (c) 2023 Contributors to the Eclipse Foundation +* Copyright (c) 2025 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -11,12 +11,18 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -use std::convert::TryFrom; +pub mod conversion; +pub mod types; use databroker_proto::kuksa::val::v1::Error; use http::Uri; +use log::info; +use std::convert::TryFrom; +use std::sync::Once; use tokio_stream::wrappers::BroadcastStream; -use tonic::transport::Channel; +use tonic::{async_trait, transport::Channel}; + +static INIT: Once = Once::new(); #[derive(Debug)] pub struct Client { @@ -41,6 +47,204 @@ pub enum ClientError { Function(Vec), } +#[async_trait] +pub trait SDVClientTraitV1 { + type SensorUpdateType; + type UpdateActuationType; + type PathType; + type SubscribeType; + type PublishResponseType; + type GetResponseType; + type SubscribeResponseType; + type ProvideResponseType; + type ActuateResponseType; + type MetadataResponseType; + + // from deeply embedded layer providing sensor values (to keep backwards compatibility the naming is different for the corresponding interfaces) + // if we do not want to put in the effort just give an unimplemented error for the function + async fn update_datapoints( + &mut self, + datapoints: Self::SensorUpdateType, + ) -> Result; + + // from application getting sensor values (to keep backwards compatibility the naming is different for the corresponding interfaces) + // if we do not want to put in the effort just give an unimplemented error for the function + async fn get_datapoints( + &mut self, + paths: Self::PathType, + ) -> Result; + + // from povider side pick up actuation requests (to keep backwards compatibility the naming is different for the corresponding interfaces) + // if we do not want to put in the effort just give an unimplemented error for the function + async fn subscribe( + &mut self, + paths: Self::SubscribeType, + ) -> Result; + + // from application requesting an actuation (to keep backwards compatibility the naming is different for the corresponding interfaces) + // if we do not want to put in the effort just give an unimplemented error for the function + async fn set_datapoints( + &mut self, + datapoints: Self::UpdateActuationType, + ) -> Result; + + // general functions + async fn get_metadata( + &mut self, + paths: Self::PathType, + ) -> Result; +} + +#[async_trait] +pub trait ClientTraitV1 { + type SensorUpdateType; + type UpdateActuationType; + type PathType; + type SubscribeType; + type PublishResponseType; + type GetResponseType; + type SubscribeResponseType; + type ProvideResponseType; + type ActuateResponseType; + type MetadataResponseType; + + // from deeply embedded layer providing sensor values (to keep backwards compatibility the naming is different for the corresponding interfaces) + // if we do not want to put in the effort just give an unimplemented error for the function + async fn set_current_values( + &mut self, + datapoints: Self::SensorUpdateType, + ) -> Result; + + // from application getting sensor values (to keep backwards compatibility the naming is different for the corresponding interfaces) + // if we do not want to put in the effort just give an unimplemented error for the function + async fn get_current_values( + &mut self, + paths: Self::PathType, + ) -> Result; + + // from povider side pick up actuation requests (to keep backwards compatibility the naming is different for the corresponding interfaces) + // if we do not want to put in the effort just give an unimplemented error for the function + async fn subscribe_target_values( + &mut self, + paths: Self::PathType, + ) -> Result; + // get is unary + async fn get_target_values( + &mut self, + paths: Self::PathType, + ) -> Result; + + // from provider side: pick up actuation requests (to keep backwards compatibility the naming is different for the corresponding interfaces) + // if we do not want to put in the effort just give an unimplemented error for the function + async fn subscribe_current_values( + &mut self, + paths: Self::SubscribeType, + ) -> Result; + async fn subscribe( + &mut self, + paths: Self::SubscribeType, + ) -> Result; + + // from application requesting an actuation (to keep backwards compatibility the naming is different for the corresponding interfaces) + // if we do not want to put in the effort just give an unimplemented error for the function + async fn set_target_values( + &mut self, + datapoints: Self::UpdateActuationType, + ) -> Result; + + // general functions + async fn get_metadata( + &mut self, + paths: Self::PathType, + ) -> Result; +} + +#[async_trait] +pub trait ClientTraitV2 { + type SensorUpdateType; + type UpdateActuationType; + type MultipleUpdateActuationType; + type PathType; + type PathsType; + type IdsType; + type SubscribeType; + type SubscribeByIdType; + type PublishResponseType; + type GetResponseType; + type MultipleGetResponseType; + type SubscribeResponseType; + type SubscribeByIdResponseType; + type ProvideResponseType; + type ActuateResponseType; + type OpenProviderStreamResponseType; + type MetadataType; + type MetadataResponseType; + type ServerInfoType; + + // from deeply embedded layer providing sensor values (to keep backwards compatibility the naming is different for the corresponding interfaces) + // if we do not want to put in the effort just give an unimplemented error for the function + async fn publish_value( + &mut self, + signal_path: Self::PathType, + value: Self::SensorUpdateType, + ) -> Result; + + // from application getting sensor values (to keep backwards compatibility the naming is different for the corresponding interfaces) + // if we do not want to put in the effort just give an unimplemented error for the function + async fn get_value( + &mut self, + path: Self::PathType, + ) -> Result; + async fn get_values( + &mut self, + paths: Self::PathsType, + ) -> Result; + + // from povider side pick up actuation requests (to keep backwards compatibility the naming is different for the corresponding interfaces) + // if we do not want to put in the effort just give an unimplemented error for the function + async fn open_provider_stream( + &mut self, + buffer_size: Option, + ) -> Result; + + async fn provide_actuation( + &mut self, + paths: Self::PathType, + ) -> Result; + + // from povider side pick up actuation requests (to keep backwards compatibility the naming is different for the corresponding interfaces) + // if we do not want to put in the effort just give an unimplemented error for the function + async fn subscribe( + &mut self, + paths: Self::SubscribeType, + buffer_size: Option, + ) -> Result; + async fn subscribe_by_id( + &mut self, + signal_ids: Self::SubscribeByIdType, + buffer_size: Option, + ) -> Result; + + // from application requesting an actuation (to keep backwards compatibility the naming is different for the corresponding interfaces) + // if we do not want to put in the effort just give an unimplemented error for the function + async fn actuate( + &mut self, + signal_path: Self::PathType, + value: Self::UpdateActuationType, + ) -> Result; + async fn batch_actuate( + &mut self, + datapoints: Self::MultipleUpdateActuationType, + ) -> Result; + + // general functions + async fn list_metadata( + &mut self, + tuple: Self::MetadataType, + ) -> Result; + async fn get_server_info(&mut self) -> Result; +} + impl std::error::Error for ClientError {} impl std::fmt::Display for ClientError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -110,8 +314,16 @@ pub fn to_uri(uri: impl AsRef) -> Result { tonic::transport::Uri::from_parts(parts).map_err(|err| format!("{err}")) } +fn init_logger() { + INIT.call_once(|| { + env_logger::init(); + }); +} + impl Client { pub fn new(uri: Uri) -> Self { + init_logger(); + info!("Creating client with URI: {}", uri); Client { uri, token: None, diff --git a/lib/common/src/types.rs b/lib/common/src/types.rs new file mode 100644 index 00000000..4d262b06 --- /dev/null +++ b/lib/common/src/types.rs @@ -0,0 +1,89 @@ +/******************************************************************************** +* Copyright (c) 2025 Contributors to the Eclipse Foundation +* +* See the NOTICE file(s) distributed with this work for additional +* information regarding copyright ownership. +* +* This program and the accompanying materials are made available under the +* terms of the Apache License 2.0 which is available at +* http://www.apache.org/licenses/LICENSE-2.0 +* +* SPDX-License-Identifier: Apache-2.0 +********************************************************************************/ + +use std::collections::HashMap; + +use databroker_proto::kuksa::val::v1 as protoV1; +use databroker_proto::kuksa::val::v2 as protoV2; +use databroker_proto::sdv::databroker::v1 as SDVprotoV1; + +use tonic::Streaming; + +// Type aliases SDV +pub type SensorUpdateSDVTypeV1 = HashMap; +pub type UpdateActuationSDVTypeV1 = HashMap; +pub type PathSDVTypeV1 = Vec; +pub type SubscribeSDVTypeV1 = String; +pub type PublishResponseSDVTypeV1 = SDVprotoV1::UpdateDatapointsReply; +pub type GetResponseSDVTypeV1 = HashMap; +pub type SubscribeResponseSDVTypeV1 = Streaming; +pub type ProvideResponseSDVTypeV1 = Streaming; +pub type ActuateResponseSDVTypeV1 = SDVprotoV1::SetDatapointsReply; +pub type MetadataResponseSDVTypeV1 = Vec; + +// Type aliases V1 +pub type SensorUpdateTypeV1 = HashMap; +pub type UpdateActuationTypeV1 = HashMap; +pub type PathTypeV1 = Vec; +pub type SubscribeTypeV1 = PathTypeV1; +pub type PublishResponseTypeV1 = (); +pub type GetResponseTypeV1 = Vec; +pub type SubscribeResponseTypeV1 = Streaming; +pub type ProvideResponseTypeV1 = Streaming; +pub type ActuateResponseTypeV1 = (); +pub type MetadataResponseTypeV1 = GetResponseTypeV1; + +// Type aliases V2 +pub type SensorUpdateTypeV2 = protoV2::Value; +pub type UpdateActuationTypeV2 = SensorUpdateTypeV2; +pub type MultipleUpdateActuationTypeV2 = HashMap; +pub type PathTypeV2 = String; +pub type PathsTypeV2 = Vec; +pub type IdsTypeV2 = Vec; +pub type SubscribeTypeV2 = PathsTypeV2; +pub type SubscribeByIdTypeV2 = IdsTypeV2; +pub type PublishResponseTypeV2 = (); +pub type GetResponseTypeV2 = Option; +pub type MultipleGetResponseTypeV2 = Vec; +pub type SubscribeResponseTypeV2 = tonic::Streaming; +pub type SubscribeByIdResponseTypeV2 = tonic::Streaming; +pub type ProvideResponseTypeV2 = (); +pub type ActuateResponseTypeV2 = (); +pub type OpenProviderStreamResponseTypeV2 = OpenProviderStream; +pub type MetadataTypeV2 = (PathTypeV2, String); +pub type MetadataResponseTypeV2 = Vec; +pub type ServerInfoTypeV2 = ServerInfo; + +#[derive(Debug)] +pub struct ServerInfo { + pub name: String, + pub commit_hash: String, + pub version: String, +} + +pub struct OpenProviderStream { + pub sender: tokio::sync::mpsc::Sender, + pub receiver_stream: tonic::Streaming, +} + +impl OpenProviderStream { + pub fn new( + sender: tokio::sync::mpsc::Sender, + receiver_stream: tonic::Streaming, + ) -> Self { + OpenProviderStream { + sender, + receiver_stream, + } + } +} diff --git a/lib/databroker-examples/examples/kuksa_val_v2.rs b/lib/databroker-examples/examples/kuksa_val_v2.rs index 5f15a6a9..7fd93f11 100644 --- a/lib/databroker-examples/examples/kuksa_val_v2.rs +++ b/lib/databroker-examples/examples/kuksa_val_v2.rs @@ -20,6 +20,7 @@ use databroker_proto::kuksa::val::v2::{ open_provider_stream_response, OpenProviderStreamRequest, ProvideActuationRequest, SignalId, Value, }; +use kuksa_common::ClientTraitV2; use kuksa_val_v2::KuksaClientV2; use open_provider_stream_response::Action::BatchActuateStreamRequest; @@ -43,8 +44,8 @@ async fn main() { } async fn sample_get_signal(client: &mut KuksaClientV2) { - let signal = "Vehicle.Speed"; - let result = client.get_value(signal).await; + let signal = "Vehicle.Speed".to_string(); + let result = client.get_value(signal.clone()).await; match result { Ok(option) => match option { Some(datapoint) => { @@ -61,9 +62,9 @@ async fn sample_get_signal(client: &mut KuksaClientV2) { } async fn sample_get_signals(client: &mut KuksaClientV2) { - let path_speed = "Vehicle.Speed"; - let path_average_speed = "Vehicle.AverageSpeed"; - let signals = vec![path_speed, path_average_speed]; + let path_speed = "Vehicle.Speed".to_string(); + let path_average_speed = "Vehicle.AverageSpeed".to_string(); + let signals = vec![path_speed.clone(), path_average_speed.clone()]; let result = client.get_values(signals).await; match result { @@ -97,8 +98,8 @@ async fn sample_publish_value(client: &mut KuksaClientV2) { let value = Value { typed_value: Some(TypedValue::Float(120.0)), }; - let signal = "Vehicle.Speed"; - let result = client.publish_value(signal, value).await; + let signal = "Vehicle.Speed".to_string(); + let result = client.publish_value(signal.clone(), value).await; match result { Ok(_) => { println!("{} published successfully", signal); @@ -157,9 +158,9 @@ async fn sample_provide_actuation(client: &mut KuksaClientV2) { } async fn sample_subscribe(client: &mut KuksaClientV2) { - let path_speed = "Vehicle.Speed"; - let path_avg_speed = "Vehicle.AverageSpeed"; - let signals = vec![path_speed, path_avg_speed]; + let path_speed = "Vehicle.Speed".to_string(); + let path_avg_speed = "Vehicle.AverageSpeed".to_string(); + let signals = vec![path_speed.clone(), path_avg_speed.clone()]; let result = client.subscribe(signals, None).await; match result { @@ -169,11 +170,11 @@ async fn sample_subscribe(client: &mut KuksaClientV2) { match result { Ok(option) => { let response = option.unwrap(); - if let Some(speed) = response.entries.get(path_speed) { + if let Some(speed) = response.entries.get(&path_speed) { // do something with speed println!("{}: {:?}", path_speed, speed); }; - if let Some(average_speed) = response.entries.get(path_avg_speed) { + if let Some(average_speed) = response.entries.get(&path_avg_speed) { // do something with average_speed println!("{}: {:?}", path_avg_speed, average_speed); }; @@ -191,9 +192,9 @@ async fn sample_subscribe(client: &mut KuksaClientV2) { } async fn sample_subscribe_by_id(client: &mut KuksaClientV2) { - let path_speed = "Vehicle.Speed"; - let path_avg_speed = "Vehicle.AverageSpeed"; - let signals = vec![path_speed, path_avg_speed]; + let path_speed = "Vehicle.Speed".to_string(); + let path_avg_speed = "Vehicle.AverageSpeed".to_string(); + let signals = vec![path_speed.clone(), path_avg_speed.clone()]; let signal_ids_result = client.resolve_ids_for_paths(signals).await; match signal_ids_result { @@ -208,14 +209,14 @@ async fn sample_subscribe_by_id(client: &mut KuksaClientV2) { Ok(option) => { let response = option.unwrap(); if let Some(speed) = - response.entries.get(path_ids_map.get(path_speed).unwrap()) + response.entries.get(path_ids_map.get(&path_speed).unwrap()) { // do something with speed println!("{}: {:?}", path_speed, speed); }; if let Some(average_speed) = response .entries - .get(path_ids_map.get(path_avg_speed).unwrap()) + .get(path_ids_map.get(&path_avg_speed).unwrap()) { // do something with average_speed println!("{}: {:?}", path_avg_speed, average_speed); @@ -239,10 +240,10 @@ async fn sample_subscribe_by_id(client: &mut KuksaClientV2) { } async fn sample_list_metadata(client: &mut KuksaClientV2) { - let signal_path = "Vehicle.ADAS"; - let filter = "*"; + let signal_path = "Vehicle.ADAS".to_string(); + let filter = "*".to_string(); - let result = client.list_metadata(signal_path, filter).await; + let result = client.list_metadata((signal_path, filter)).await; match result { Ok(metadatas) => { for metadata in metadatas { diff --git a/lib/databroker-examples/examples/perf_setter.rs b/lib/databroker-examples/examples/perf_setter.rs index db803e0c..9b4621a3 100644 --- a/lib/databroker-examples/examples/perf_setter.rs +++ b/lib/databroker-examples/examples/perf_setter.rs @@ -92,7 +92,7 @@ async fn run_streaming_set_test(iterations: i32, n_th_message: i32) { println!( "Error setting datapoint {}: {:?}", error.0, - proto::v1::DatapointError::from_i32(error.1) + proto::v1::DatapointError::try_from(error.1) ) } } diff --git a/lib/databroker-examples/examples/slow_subscriber.rs b/lib/databroker-examples/examples/slow_subscriber.rs index dad429bd..d17e6c6d 100644 --- a/lib/databroker-examples/examples/slow_subscriber.rs +++ b/lib/databroker-examples/examples/slow_subscriber.rs @@ -13,13 +13,14 @@ use kuksa::KuksaClient; use kuksa_common::to_uri; +use kuksa_common::ClientTraitV1; use std::thread; use tokio::time::{sleep, Duration}; #[tokio::main] async fn main() { // Paths to subscribe - let paths = vec!["Vehicle.Speed"]; + let paths = vec!["Vehicle.Speed".to_string()]; // Initialize the KuksaClient let mut client: KuksaClient = KuksaClient::new(to_uri("127.0.0.1:55555").unwrap()); diff --git a/lib/kuksa/src/lib.rs b/lib/kuksa/src/lib.rs index 8bd1a156..9f0a0595 100644 --- a/lib/kuksa/src/lib.rs +++ b/lib/kuksa/src/lib.rs @@ -1,5 +1,5 @@ /******************************************************************************** -* Copyright (c) 2023 Contributors to the Eclipse Foundation +* Copyright (c) 2025 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -12,7 +12,9 @@ ********************************************************************************/ use http::Uri; -use std::collections::HashMap; +use kuksa_common::conversion::{ConvertToSDV, ConvertToV1}; +use kuksa_common::ClientTraitV1; +use tonic::async_trait; pub use databroker_proto::kuksa::val::{self as proto, v1::DataEntry}; @@ -104,83 +106,93 @@ impl KuksaClient { Err(err) => Err(ClientError::Status(err)), } } +} - pub async fn get_metadata(&mut self, paths: Vec<&str>) -> Result, ClientError> { - let mut metadata_result = Vec::new(); - - for path in paths { - match self - .get( - path, - proto::v1::View::Metadata, - vec![proto::v1::Field::Metadata.into()], - ) - .await - { - Ok(mut entry) => metadata_result.append(&mut entry), - Err(err) => return Err(err), - } - } - - Ok(metadata_result) +#[async_trait] +impl kuksa_common::SDVClientTraitV1 for KuksaClient { + type SensorUpdateType = kuksa_common::types::SensorUpdateSDVTypeV1; + type UpdateActuationType = kuksa_common::types::UpdateActuationSDVTypeV1; + type PathType = kuksa_common::types::PathSDVTypeV1; + type SubscribeType = kuksa_common::types::SubscribeSDVTypeV1; + type PublishResponseType = kuksa_common::types::PublishResponseSDVTypeV1; + type GetResponseType = kuksa_common::types::GetResponseSDVTypeV1; + type SubscribeResponseType = kuksa_common::types::SubscribeResponseSDVTypeV1; + type ProvideResponseType = kuksa_common::types::ProvideResponseSDVTypeV1; + type ActuateResponseType = kuksa_common::types::ActuateResponseSDVTypeV1; + type MetadataResponseType = kuksa_common::types::MetadataResponseSDVTypeV1; + + async fn update_datapoints( + &mut self, + datapoints: Self::SensorUpdateType, + ) -> Result { + let result = self + .set_current_values(datapoints.convert_to_v1()) + .await + .unwrap(); + let converted_result = result.convert_to_sdv(); + Ok(converted_result) } - pub async fn get_current_values( + async fn get_datapoints( &mut self, - paths: Vec, - ) -> Result, ClientError> { - let mut get_result = Vec::new(); - - for path in paths { - match self - .get( - &path, - proto::v1::View::CurrentValue, - vec![ - proto::v1::Field::Value.into(), - proto::v1::Field::Metadata.into(), - ], - ) - .await - { - Ok(mut entry) => get_result.append(&mut entry), - Err(err) => return Err(err), - } - } + paths: Self::PathType, + ) -> Result { + Ok(self + .get_current_values(paths.convert_to_v1()) + .await + .unwrap() + .convert_to_sdv()) + } - Ok(get_result) + async fn subscribe( + &mut self, + _paths: Self::SubscribeType, + ) -> Result { + unimplemented!("Subscribe mechanism has changed. SQL queries not supported anymore") } - pub async fn get_target_values( + async fn set_datapoints( &mut self, - paths: Vec, - ) -> Result, ClientError> { - let mut get_result = Vec::new(); + datapoints: Self::UpdateActuationType, + ) -> Result { + let result = self + .set_target_values(datapoints.convert_to_v1()) + .await + .unwrap(); + let converted_result = result.convert_to_sdv(); + Ok(converted_result) + } - for path in paths { - match self - .get( - &path, - proto::v1::View::TargetValue, - vec![ - proto::v1::Field::ActuatorTarget.into(), - proto::v1::Field::Metadata.into(), - ], - ) + async fn get_metadata( + &mut self, + paths: Self::PathType, + ) -> Result { + Ok( + kuksa_common::ClientTraitV1::get_metadata(self, paths.convert_to_v1()) .await - { - Ok(mut entry) => get_result.append(&mut entry), - Err(err) => return Err(err), - } - } - - Ok(get_result) + .unwrap() + .convert_to_sdv(), + ) } +} - pub async fn set_current_values( +#[async_trait] +impl kuksa_common::ClientTraitV1 for KuksaClient { + type SensorUpdateType = kuksa_common::types::SensorUpdateTypeV1; + type UpdateActuationType = kuksa_common::types::UpdateActuationTypeV1; + type PathType = kuksa_common::types::PathTypeV1; + type SubscribeType = Self::PathType; + type PublishResponseType = kuksa_common::types::PublishResponseTypeV1; + type GetResponseType = kuksa_common::types::GetResponseTypeV1; + type SubscribeResponseType = kuksa_common::types::SubscribeResponseTypeV1; + type ProvideResponseType = kuksa_common::types::ProvideResponseTypeV1; + type ActuateResponseType = kuksa_common::types::ActuateResponseTypeV1; + type MetadataResponseType = kuksa_common::types::MetadataResponseTypeV1; + + async fn set_current_values( &mut self, - datapoints: HashMap, - ) -> Result<(), ClientError> { + datapoints: Self::SensorUpdateType, + ) -> Result { for (path, datapoint) in datapoints { match self .set( @@ -207,70 +219,87 @@ impl KuksaClient { Ok(()) } - pub async fn set_target_values( + async fn get_current_values( &mut self, - datapoints: HashMap, - ) -> Result<(), ClientError> { - for (path, datapoint) in datapoints { + paths: Self::PathType, + ) -> Result { + let mut get_result = Vec::new(); + + for path in paths { match self - .set( - proto::v1::DataEntry { - path: path.clone(), - value: None, - actuator_target: Some(datapoint), - metadata: None, - }, + .get( + &path, + proto::v1::View::CurrentValue, vec![ - proto::v1::Field::ActuatorTarget.into(), - proto::v1::Field::Path.into(), + proto::v1::Field::Value.into(), + proto::v1::Field::Metadata.into(), ], ) .await { - Ok(_) => { - continue; - } + Ok(mut entry) => get_result.append(&mut entry), Err(err) => return Err(err), } } - Ok(()) + Ok(get_result) } - pub async fn set_metadata( + async fn subscribe_target_values( &mut self, - metadatas: HashMap, - ) -> Result<(), ClientError> { - for (path, metadata) in metadatas { + paths: Self::PathType, + ) -> Result { + let mut client = proto::v1::val_client::ValClient::with_interceptor( + self.basic_client.get_channel().await?.clone(), + self.basic_client.get_auth_interceptor(), + ); + let mut entries = Vec::new(); + for path in paths { + entries.push(proto::v1::SubscribeEntry { + path: path.to_string(), + view: proto::v1::View::TargetValue.into(), + fields: vec![proto::v1::Field::ActuatorTarget.into()], + }) + } + + let req = proto::v1::SubscribeRequest { entries }; + + match client.subscribe(req).await { + Ok(response) => Ok(response.into_inner()), + Err(err) => Err(ClientError::Status(err)), + } + } + + async fn get_target_values( + &mut self, + paths: Self::PathType, + ) -> Result { + let mut get_result = Vec::new(); + + for path in paths { match self - .set( - proto::v1::DataEntry { - path: path.clone(), - value: None, - actuator_target: None, - metadata: Some(metadata), - }, + .get( + &path, + proto::v1::View::TargetValue, vec![ + proto::v1::Field::ActuatorTarget.into(), proto::v1::Field::Metadata.into(), - proto::v1::Field::Path.into(), ], ) .await { - Ok(_) => { - continue; - } + Ok(mut entry) => get_result.append(&mut entry), Err(err) => return Err(err), } } - Ok(()) + Ok(get_result) } - pub async fn subscribe_current_values( + async fn subscribe_current_values( &mut self, - paths: Vec<&str>, - ) -> Result, ClientError> { + paths: Self::SubscribeType, + ) -> Result { let mut client = proto::v1::val_client::ValClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), @@ -296,61 +325,63 @@ impl KuksaClient { } } - //masking subscribe curent values with subscribe due to plugability - pub async fn subscribe( + async fn subscribe( &mut self, - paths: Vec<&str>, - ) -> Result, ClientError> { + paths: Self::SubscribeType, + ) -> Result { self.subscribe_current_values(paths).await } - pub async fn subscribe_target_values( + async fn set_target_values( &mut self, - paths: Vec<&str>, - ) -> Result, ClientError> { - let mut client = proto::v1::val_client::ValClient::with_interceptor( - self.basic_client.get_channel().await?.clone(), - self.basic_client.get_auth_interceptor(), - ); - let mut entries = Vec::new(); - for path in paths { - entries.push(proto::v1::SubscribeEntry { - path: path.to_string(), - view: proto::v1::View::TargetValue.into(), - fields: vec![proto::v1::Field::ActuatorTarget.into()], - }) + datapoints: Self::UpdateActuationType, + ) -> Result { + for (path, datapoint) in datapoints { + match self + .set( + proto::v1::DataEntry { + path: path.clone(), + value: None, + actuator_target: Some(datapoint), + metadata: None, + }, + vec![ + proto::v1::Field::ActuatorTarget.into(), + proto::v1::Field::Path.into(), + ], + ) + .await + { + Ok(_) => { + continue; + } + Err(err) => return Err(err), + } } - let req = proto::v1::SubscribeRequest { entries }; - - match client.subscribe(req).await { - Ok(response) => Ok(response.into_inner()), - Err(err) => Err(ClientError::Status(err)), - } + Ok(()) } - pub async fn subscribe_metadata( + async fn get_metadata( &mut self, - paths: Vec, - ) -> Result, ClientError> { - let mut client = proto::v1::val_client::ValClient::with_interceptor( - self.basic_client.get_channel().await?.clone(), - self.basic_client.get_auth_interceptor(), - ); - let mut entries = Vec::new(); + paths: Self::PathType, + ) -> Result { + let mut metadata_result = Vec::new(); + for path in paths { - entries.push(proto::v1::SubscribeEntry { - path: path.to_string(), - view: proto::v1::View::Metadata.into(), - fields: vec![proto::v1::Field::Metadata.into()], - }) + match self + .get( + &path, + proto::v1::View::Metadata, + vec![proto::v1::Field::Metadata.into()], + ) + .await + { + Ok(mut entry) => metadata_result.append(&mut entry), + Err(err) => return Err(err), + } } - let req = proto::v1::SubscribeRequest { entries }; - - match client.subscribe(req).await { - Ok(response) => Ok(response.into_inner()), - Err(err) => Err(ClientError::Status(err)), - } + Ok(metadata_result) } } diff --git a/lib/kuksa_val_v2/src/lib.rs b/lib/kuksa_val_v2/src/lib.rs index f0f30727..7900b157 100644 --- a/lib/kuksa_val_v2/src/lib.rs +++ b/lib/kuksa_val_v2/src/lib.rs @@ -10,50 +10,22 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -use databroker_proto::kuksa::val::v2::signal_id::Signal::Path; -use databroker_proto::kuksa::val::v2::val_client::ValClient; use databroker_proto::kuksa::val::v2::{ - ActuateRequest, GetValueRequest, ListMetadataRequest, PublishValueRequest, SubscribeRequest, - SubscribeResponse, + signal_id::Signal::Path, val_client::ValClient, ActuateRequest, BatchActuateRequest, Datapoint, + GetServerInfoRequest, GetValueRequest, GetValuesRequest, ListMetadataRequest, + PublishValueRequest, SignalId, SubscribeByIdRequest, SubscribeRequest, Value, }; -use databroker_proto::kuksa::val::v2::{ - BatchActuateRequest, GetServerInfoRequest, GetValuesRequest, Metadata, - OpenProviderStreamResponse, SignalId, Value, -}; -use databroker_proto::kuksa::val::v2::{Datapoint, OpenProviderStreamRequest}; -use databroker_proto::kuksa::val::v2::{SubscribeByIdRequest, SubscribeByIdResponse}; use http::Uri; -pub use kuksa_common::{Client, ClientError, ClientError::Status}; +pub use kuksa_common::{Client, ClientError, ClientTraitV2}; use prost_types::Timestamp; use std::collections::HashMap; use std::fmt::Debug; use std::time::SystemTime; use tokio_stream::wrappers::ReceiverStream; -use tonic::Streaming; +use tonic::async_trait; -#[derive(Debug)] -pub struct ServerInfo { - pub name: String, - pub commit_hash: String, - pub version: String, -} - -pub struct OpenProviderStream { - pub sender: tokio::sync::mpsc::Sender, - pub receiver_stream: Streaming, -} - -impl OpenProviderStream { - fn new( - sender: tokio::sync::mpsc::Sender, - receiver_stream: Streaming, - ) -> Self { - OpenProviderStream { - sender, - receiver_stream, - } - } -} +use kuksa_common::conversion::{ConvertToV1, ConvertToV2}; +use kuksa_common::types::{OpenProviderStream, ServerInfo}; #[derive(Debug)] pub struct KuksaClientV2 { @@ -72,6 +44,163 @@ impl KuksaClientV2 { Self::new(uri) } + /// Resolves the databroker ids for the specified list of paths and returns them in a HashMap + /// + /// Returns (GRPC error code): + /// NOT_FOUND if the specified root branch does not exist. + /// UNAUTHENTICATED if no credentials provided or credentials has expired + /// + pub async fn resolve_ids_for_paths( + &mut self, + vss_paths: Vec, + ) -> Result, ClientError> { + let mut hash_map = HashMap::new(); + + for path in vss_paths { + let vec = self.list_metadata((path, "*".to_string())).await?; + let metadata = vec.first().unwrap(); + + hash_map.insert(metadata.path.clone(), metadata.id); + } + + Ok(hash_map) + } + + fn convert_to_actuate_requests(values: HashMap) -> Vec { + let mut actuate_requests = Vec::with_capacity(values.len()); + for (signal_path, value) in values { + let actuate_request = ActuateRequest { + signal_id: Some(SignalId { + signal: Some(Path(signal_path)), + }), + value: Some(value), + }; + + actuate_requests.push(actuate_request) + } + actuate_requests + } +} + +#[async_trait] +impl kuksa_common::ClientTraitV1 for KuksaClientV2 { + type SensorUpdateType = kuksa_common::types::SensorUpdateTypeV1; + type UpdateActuationType = kuksa_common::types::UpdateActuationTypeV1; + type PathType = kuksa_common::types::PathTypeV1; + type SubscribeType = kuksa_common::types::SubscribeTypeV1; + type PublishResponseType = kuksa_common::types::PublishResponseTypeV1; + type GetResponseType = kuksa_common::types::GetResponseTypeV1; + type SubscribeResponseType = kuksa_common::types::SubscribeResponseTypeV1; + type ProvideResponseType = kuksa_common::types::ProvideResponseTypeV1; + type ActuateResponseType = kuksa_common::types::ActuateResponseTypeV1; + type MetadataResponseType = kuksa_common::types::MetadataResponseTypeV1; + + async fn set_current_values( + &mut self, + datapoints: Self::SensorUpdateType, + ) -> Result { + for (signal_path, datapoint) in datapoints { + self.publish_value(signal_path, datapoint.convert_to_v2()) + .await? + } + Ok(()) + } + + async fn get_current_values( + &mut self, + paths: Self::PathType, + ) -> Result { + Ok(self + .get_values(paths.convert_to_v2()) + .await + .unwrap() + .convert_to_v1()) + } + + async fn subscribe_target_values( + &mut self, + _paths: Self::PathType, + ) -> Result { + unimplemented!("The concept behind target and current value has changed! Target values will not get stored anymore.") + // here we could default to call a kuksa.val.v1 function as well but I would not recommend. + // This would suggerate that it still works which it won't + // Other option would be to open a provider stream here and return stuff but this would change the return type aka the dev has to adapt anyways. + } + + async fn get_target_values( + &mut self, + _paths: Self::PathType, + ) -> Result { + unimplemented!("The concept behind target and current value has changed! Target values will not get stored anymore.") + // here we could default to call a kuksa.val.v1 function as well but I would not recommend. + // This would suggerate that it still works which it won't + // Other option would be to open a provider stream here and return stuff but this would change the return type aka the dev has to adapt anyways. + } + + async fn subscribe_current_values( + &mut self, + paths: Self::SubscribeType, + ) -> Result { + Ok(ClientTraitV2::subscribe(self, paths.convert_to_v2(), None) + .await + .unwrap() + .convert_to_v1()) + } + + async fn subscribe( + &mut self, + paths: Self::SubscribeType, + ) -> Result { + Ok(ClientTraitV2::subscribe(self, paths.convert_to_v2(), None) + .await + .unwrap() + .convert_to_v1()) + } + + async fn set_target_values( + &mut self, + datapoints: Self::UpdateActuationType, + ) -> Result { + let result = self + .batch_actuate(datapoints.convert_to_v2()) + .await + .unwrap(); + let converted_result = result.convert_to_v1(); + Ok(converted_result) + } + + async fn get_metadata( + &mut self, + paths: Self::PathType, + ) -> Result { + let result = self.list_metadata(paths.convert_to_v2()).await.unwrap(); + let converted_result = result.convert_to_v1(); + Ok(converted_result) + } +} + +#[async_trait] +impl ClientTraitV2 for KuksaClientV2 { + type SensorUpdateType = kuksa_common::types::SensorUpdateTypeV2; + type UpdateActuationType = kuksa_common::types::UpdateActuationTypeV2; + type MultipleUpdateActuationType = kuksa_common::types::MultipleUpdateActuationTypeV2; + type PathType = kuksa_common::types::PathTypeV2; + type PathsType = kuksa_common::types::PathsTypeV2; + type IdsType = kuksa_common::types::IdsTypeV2; + type SubscribeType = kuksa_common::types::SubscribeTypeV2; + type SubscribeByIdType = kuksa_common::types::SubscribeByIdTypeV2; + type PublishResponseType = kuksa_common::types::PublishResponseTypeV2; + type GetResponseType = kuksa_common::types::GetResponseTypeV2; + type MultipleGetResponseType = kuksa_common::types::MultipleGetResponseTypeV2; + type SubscribeResponseType = kuksa_common::types::SubscribeResponseTypeV2; + type SubscribeByIdResponseType = kuksa_common::types::SubscribeByIdResponseTypeV2; + type ProvideResponseType = kuksa_common::types::ProvideResponseTypeV2; + type ActuateResponseType = kuksa_common::types::ActuateResponseTypeV2; + type OpenProviderStreamResponseType = kuksa_common::types::OpenProviderStreamResponseTypeV2; + type MetadataType = kuksa_common::types::MetadataTypeV2; + type MetadataResponseType = kuksa_common::types::MetadataResponseTypeV2; + type ServerInfoType = kuksa_common::types::ServerInfoTypeV2; + /// Get the latest value of a signal /// If the signal exist but does not have a valid value /// a DataPoint where value is None shall be returned. @@ -83,7 +212,10 @@ impl KuksaClientV2 { /// INVALID_ARGUMENT if the request is empty or provided path is too long /// - MAX_REQUEST_PATH_LENGTH: usize = 1000; /// - pub async fn get_value(&mut self, path: &str) -> Result, ClientError> { + async fn get_value( + &mut self, + path: Self::PathType, + ) -> Result { let mut client = ValClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), @@ -91,7 +223,7 @@ impl KuksaClientV2 { let get_value_request = GetValueRequest { signal_id: Some(SignalId { - signal: Some(Path(path.to_string())), + signal: Some(Path(path)), }), }; @@ -100,7 +232,7 @@ impl KuksaClientV2 { let message = response.into_inner(); Ok(message.data_point) } - Err(err) => Err(Status(err)), + Err(err) => Err(ClientError::Status(err)), } } @@ -115,10 +247,10 @@ impl KuksaClientV2 { /// INVALID_ARGUMENT if the request is empty or provided path is too long /// - MAX_REQUEST_PATH_LENGTH: usize = 1000; /// - pub async fn get_values( + async fn get_values( &mut self, - signal_paths: Vec<&str>, - ) -> Result, ClientError> { + signal_paths: Self::PathsType, + ) -> Result { let mut client = ValClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), @@ -138,7 +270,7 @@ impl KuksaClientV2 { let message = response.into_inner(); Ok(message.data_points) } - Err(err) => Err(Status(err)), + Err(err) => Err(ClientError::Status(err)), } } @@ -156,11 +288,11 @@ impl KuksaClientV2 { /// e.g. if sending an unsupported enum value /// - if the published value is out of the min/max range specified /// - pub async fn publish_value( + async fn publish_value( &mut self, - signal_path: &str, - value: Value, - ) -> Result<(), ClientError> { + signal_path: Self::PathType, + value: Self::SensorUpdateType, + ) -> Result { let mut client = ValClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), @@ -175,7 +307,7 @@ impl KuksaClientV2 { let publish_value_request = PublishValueRequest { signal_id: Some(SignalId { - signal: Some(Path(signal_path.to_string())), + signal: Some(Path(signal_path)), }), data_point: Some(Datapoint { timestamp: Some(Timestamp { seconds, nanos }), @@ -185,7 +317,7 @@ impl KuksaClientV2 { match client.publish_value(publish_value_request).await { Ok(_response) => Ok(()), - Err(err) => Err(Status(err)), + Err(err) => Err(ClientError::Status(err)), } } @@ -205,7 +337,11 @@ impl KuksaClientV2 { /// e.g. if sending an unsupported enum value /// - if the provided value is out of the min/max range specified /// - pub async fn actuate(&mut self, signal_path: &str, value: Value) -> Result<(), ClientError> { + async fn actuate( + &mut self, + signal_path: Self::PathType, + value: Self::UpdateActuationType, + ) -> Result { let mut client = ValClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), @@ -213,14 +349,14 @@ impl KuksaClientV2 { let actuate_request = ActuateRequest { signal_id: Some(SignalId { - signal: Some(Path(signal_path.to_string())), + signal: Some(Path(signal_path)), }), value: Some(value), }; match client.actuate(actuate_request).await { Ok(_response) => Ok(()), - Err(err) => Err(Status(err)), + Err(err) => Err(ClientError::Status(err)), } } @@ -242,10 +378,10 @@ impl KuksaClientV2 { /// e.g. if sending an unsupported enum value /// - if any of the provided actuators values are out of the min/max range specified /// - pub async fn batch_actuate( + async fn batch_actuate( &mut self, - values: HashMap, - ) -> Result<(), ClientError> { + values: Self::MultipleUpdateActuationType, + ) -> Result { let mut client = ValClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), @@ -257,7 +393,7 @@ impl KuksaClientV2 { match client.batch_actuate(batch_actuate_request).await { Ok(_response) => Ok(()), - Err(err) => Err(Status(err)), + Err(err) => Err(ClientError::Status(err)), } } @@ -279,27 +415,25 @@ impl KuksaClientV2 { /// If a subscriber is slow to consume signals, messages will be buffered up /// to the specified buffer_size before the oldest messages are dropped. /// - pub async fn subscribe( + async fn subscribe( &mut self, - signal_paths: Vec<&str>, + signal_paths: Self::SubscribeType, buffer_size: Option, - ) -> Result, ClientError> { + ) -> Result { let mut client = ValClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), ); - let paths: Vec = signal_paths.iter().map(|&s| s.to_string()).collect(); - let subscribe_request = SubscribeRequest { - signal_paths: paths, + signal_paths, buffer_size: buffer_size.unwrap_or(0), filter: None, }; match client.subscribe(subscribe_request).await { Ok(response) => Ok(response.into_inner()), - Err(err) => Err(Status(err)), + Err(err) => Err(ClientError::Status(err)), } } @@ -321,11 +455,11 @@ impl KuksaClientV2 { /// If a subscriber is slow to consume signals, messages will be buffered up /// to the specified buffer_size before the oldest messages are dropped. /// - pub async fn subscribe_by_id( + async fn subscribe_by_id( &mut self, - signal_ids: Vec, + signal_ids: Self::SubscribeByIdType, buffer_size: Option, - ) -> Result, ClientError> { + ) -> Result { let mut client = ValClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), @@ -339,7 +473,7 @@ impl KuksaClientV2 { match client.subscribe_by_id(subscribe_by_id_request).await { Ok(response) => Ok(response.into_inner()), - Err(err) => Err(Status(err)), + Err(err) => Err(ClientError::Status(err)), } } @@ -374,10 +508,10 @@ impl KuksaClientV2 { /// - Provider returns BatchActuateStreamResponse <- Databroker sends BatchActuateStreamRequest /// No error definition, a BatchActuateStreamResponse is expected from provider. /// - pub async fn open_provider_stream( + async fn open_provider_stream( &mut self, buffer_size: Option, - ) -> Result { + ) -> Result { let mut client = ValClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), @@ -391,7 +525,7 @@ impl KuksaClientV2 { let message = response.into_inner(); Ok(OpenProviderStream::new(sender, message)) } - Err(err) => Err(Status(err)), + Err(err) => Err(ClientError::Status(err)), } } @@ -402,19 +536,18 @@ impl KuksaClientV2 { /// UNAUTHENTICATED if no credentials provided or credentials has expired /// INVALID_ARGUMENT if the provided path or wildcard is wrong. /// - pub async fn list_metadata( + async fn list_metadata( &mut self, - root: &str, - filter: &str, - ) -> Result, ClientError> { + tuple: Self::MetadataType, + ) -> Result { let mut client = ValClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), ); let list_metadata_request = ListMetadataRequest { - root: root.to_string(), - filter: filter.to_string(), + root: tuple.0, + filter: tuple.1, }; match client.list_metadata(list_metadata_request).await { @@ -422,12 +555,12 @@ impl KuksaClientV2 { let metadata_response = response.into_inner(); Ok(metadata_response.metadata) } - Err(err) => Err(Status(err)), + Err(err) => Err(ClientError::Status(err)), } } /// Get server information - pub async fn get_server_info(&mut self) -> Result { + async fn get_server_info(&mut self) -> Result { let mut client = ValClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), @@ -445,45 +578,15 @@ impl KuksaClientV2 { }; Ok(server_info) } - Err(err) => Err(Status(err)), + Err(err) => Err(ClientError::Status(err)), } } - /// Resolves the databroker ids for the specified list of paths and returns them in a HashMap - /// - /// Returns (GRPC error code): - /// NOT_FOUND if the specified root branch does not exist. - /// UNAUTHENTICATED if no credentials provided or credentials has expired - /// - pub async fn resolve_ids_for_paths( + async fn provide_actuation( &mut self, - vss_paths: Vec<&str>, - ) -> Result, ClientError> { - let mut hash_map = HashMap::new(); - - for path in vss_paths { - let vec = self.list_metadata(path, "*").await?; - let metadata = vec.first().unwrap(); - - hash_map.insert(metadata.path.clone(), metadata.id); - } - - Ok(hash_map) - } - - fn convert_to_actuate_requests(values: HashMap) -> Vec { - let mut actuate_requests = Vec::with_capacity(values.len()); - for (signal_path, value) in values { - let actuate_request = ActuateRequest { - signal_id: Some(SignalId { - signal: Some(Path(signal_path)), - }), - value: Some(value), - }; - - actuate_requests.push(actuate_request) - } - actuate_requests + _path: Self::PathType, + ) -> Result { + todo!() } } @@ -526,7 +629,7 @@ mod tests { async fn test_get_value() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let response = client.get_value("Vehicle.Speed").await; + let response = client.get_value("Vehicle.Speed".to_string()).await; assert!(response.is_ok()); } @@ -535,7 +638,7 @@ mod tests { async fn test_get_value_with_empty_path_will_return_not_found() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let response = client.get_value("").await; + let response = client.get_value("".to_string()).await; let err = response.unwrap_err(); expect_status_code(err, NotFound); @@ -546,7 +649,9 @@ mod tests { async fn test_get_value_with_invalid_path_will_return_not_found() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let response = client.get_value("Vehicle.Some.Invalid.Path").await; + let response = client + .get_value("Vehicle.Some.Invalid.Path".to_string()) + .await; assert!(response.is_err()); let err = response.unwrap_err(); @@ -559,7 +664,7 @@ mod tests { let mut client = KuksaClientV2::new_test_client(Some(Read)); let long_path = "Vehicle.".repeat(200) + "Speed"; - let response = client.get_value(&long_path).await; + let response = client.get_value(long_path).await; assert!(response.is_err()); let err = response.unwrap_err(); @@ -571,7 +676,7 @@ mod tests { async fn test_get_value_without_auth_token_will_return_unauthenticated() { let mut client = KuksaClientV2::new_test_client(None); - let response = client.get_value("Vehicle.Speed").await; + let response = client.get_value("Vehicle.Speed".to_string()).await; assert!(response.is_err()); let err = response.unwrap_err(); @@ -583,7 +688,10 @@ mod tests { async fn test_get_values_will_return_ok() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let signal_paths = vec!["Vehicle.Speed", "Vehicle.AverageSpeed"]; + let signal_paths = vec![ + "Vehicle.Speed".to_string(), + "Vehicle.AverageSpeed".to_string(), + ]; let response = client.get_values(signal_paths).await; assert!(response.is_ok()); } @@ -593,7 +701,7 @@ mod tests { async fn test_get_values_with_empty_path_will_return_not_found() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let signal_paths = vec!["Vehicle.Speed", ""]; + let signal_paths = vec!["Vehicle.Speed".to_string(), "".to_string()]; let response = client.get_values(signal_paths).await; assert!(response.is_err()); @@ -606,7 +714,10 @@ mod tests { async fn test_get_values_with_invalid_path_will_return_not_found() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let signal_paths = vec!["Vehicle.Speed", "Vehicle.Some.Invalid.Path"]; + let signal_paths = vec![ + "Vehicle.Speed".to_string(), + "Vehicle.Some.Invalid.Path".to_string(), + ]; let response = client.get_values(signal_paths).await; assert!(response.is_err()); @@ -619,7 +730,10 @@ mod tests { async fn test_get_values_without_auth_token_will_return_unauthenticated() { let mut client = KuksaClientV2::new_test_client(None); - let signal_paths = vec!["Vehicle.Speed", "Vehicle.AverageSpeed"]; + let signal_paths = vec![ + "Vehicle.Speed".to_string(), + "Vehicle.AverageSpeed".to_string(), + ]; let response = client.get_values(signal_paths).await; assert!(response.is_err()); @@ -632,12 +746,14 @@ mod tests { async fn test_publish_value_will_return_ok() { let mut client = KuksaClientV2::new_test_client(Some(ReadWrite)); - let signal_path = "Vehicle.Speed"; + let signal_path = "Vehicle.Speed".to_string(); let value = Value { typed_value: Some(TypedValue::Float(120.0)), }; - let response = client.publish_value(signal_path, value.clone()).await; + let response = client + .publish_value(signal_path.clone(), value.clone()) + .await; assert!(response.is_ok()); let datapoint_option = client.get_value(signal_path).await.unwrap(); @@ -651,7 +767,7 @@ mod tests { async fn test_publish_value_with_invalid_data_type_will_return_invalid_argument() { let mut client = KuksaClientV2::new_test_client(Some(ReadWrite)); - let signal_path = "Vehicle.Speed"; + let signal_path = "Vehicle.Speed".to_string(); let value = Value { typed_value: Some(TypedValue::Int32(100)), }; @@ -668,7 +784,7 @@ mod tests { async fn test_publish_value_with_invalid_value_will_return_invalid_argument() { let mut client = KuksaClientV2::new_test_client(Some(ReadWrite)); - let signal_path = "Vehicle.Powertrain.Type"; + let signal_path = "Vehicle.Powertrain.Type".to_string(); let value = Value { typed_value: Some(TypedValue::String("Unknown".to_string())), }; @@ -685,7 +801,7 @@ mod tests { async fn test_publish_value_with_invalid_min_max_value_will_return_invalid_argument() { let mut client = KuksaClientV2::new_test_client(Some(ReadWrite)); - let signal_path = "Vehicle.ADAS.PowerOptimizeLevel"; + let signal_path = "Vehicle.ADAS.PowerOptimizeLevel".to_string(); let value = Value { typed_value: Some(TypedValue::Uint32(100)), }; @@ -702,7 +818,7 @@ mod tests { async fn test_publish_value_with_empty_path_will_return_not_found() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let signal_path = ""; + let signal_path = "".to_string(); let value = Value { typed_value: Some(TypedValue::Float(120.0)), }; @@ -719,7 +835,7 @@ mod tests { async fn test_publish_value_with_invalid_path_will_return_not_found() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let signal_path = "Vehicle.Some.Invalid.Path"; + let signal_path = "Vehicle.Some.Invalid.Path".to_string(); let value = Value { typed_value: Some(TypedValue::Float(120.0)), }; @@ -735,7 +851,7 @@ mod tests { async fn test_publish_value_to_an_actuator_will_return_ok() { let mut client = KuksaClientV2::new_test_client(Some(ReadWrite)); - let signal_path = "Vehicle.ADAS.ABS.IsEnabled"; // is an actuator + let signal_path = "Vehicle.ADAS.ABS.IsEnabled".to_string(); // is an actuator let value = Value { typed_value: Some(TypedValue::Bool(true)), }; @@ -749,7 +865,7 @@ mod tests { async fn test_publish_value_without_auth_token_will_return_unauthenticated() { let mut client = KuksaClientV2::new_test_client(None); - let signal_path = "Vehicle.Driver.HeartRate"; + let signal_path = "Vehicle.Driver.HeartRate".to_string(); let value = Value { typed_value: Some(TypedValue::Uint32(80)), }; @@ -766,7 +882,7 @@ mod tests { async fn test_publish_value_with_read_auth_token_will_return_permission_denied() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let signal_path = "Vehicle.Driver.HeartRate"; + let signal_path = "Vehicle.Driver.HeartRate".to_string(); let value = Value { typed_value: Some(TypedValue::Uint32(80)), }; @@ -783,17 +899,18 @@ mod tests { async fn test_actuate() { let mut client = KuksaClientV2::new_test_client(Some(ReadWrite)); - let signal_path = "Vehicle.ADAS.ABS.IsEnabled"; // is an actuator + let signal_path = "Vehicle.ADAS.ABS.IsEnabled".to_string(); // is an actuator let mut stream = client.open_provider_stream(None).await.unwrap(); - let provide_actuation_request = OpenProviderStreamRequest { - action: Some(Action::ProvideActuationRequest(ProvideActuationRequest { - actuator_identifiers: vec![SignalId { - signal: Some(Path(signal_path.to_string())), - }], - })), - }; + let provide_actuation_request = + databroker_proto::kuksa::val::v2::OpenProviderStreamRequest { + action: Some(Action::ProvideActuationRequest(ProvideActuationRequest { + actuator_identifiers: vec![SignalId { + signal: Some(Path(signal_path.to_string())), + }], + })), + }; stream.sender.send(provide_actuation_request).await.unwrap(); stream.receiver_stream.message().await.unwrap(); // wait until databroker has processed / answered provide_actuation_request @@ -811,7 +928,7 @@ mod tests { async fn test_actuate_with_no_actuation_provider_will_return_unavailable() { let mut client = KuksaClientV2::new_test_client(Some(ReadWrite)); - let signal_path = "Vehicle.ADAS.CruiseControl.IsActive"; // is an actuator + let signal_path = "Vehicle.ADAS.CruiseControl.IsActive".to_string(); // is an actuator let value = Value { typed_value: Some(TypedValue::Bool(true)), }; @@ -828,7 +945,7 @@ mod tests { async fn test_actuate_a_sensor_will_return_invalid_argument() { let mut client = KuksaClientV2::new_test_client(Some(ReadWrite)); - let signal_path = "Vehicle.Speed"; + let signal_path = "Vehicle.Speed".to_string(); let value = Value { typed_value: Some(TypedValue::Float(100.0)), }; @@ -845,7 +962,7 @@ mod tests { async fn test_actuate_with_invalid_signal_path_will_return_not_found() { let mut client = KuksaClientV2::new_test_client(Some(ReadWrite)); - let signal_path = "Vehicle.Some.Invalid.Path"; + let signal_path = "Vehicle.Some.Invalid.Path".to_string(); let value = Value { typed_value: Some(TypedValue::Bool(true)), }; @@ -862,7 +979,7 @@ mod tests { async fn test_actuate_without_auth_token_will_return_unauthenticated() { let mut client = KuksaClientV2::new_test_client(None); - let signal_path = "Vehicle.ADAS.ESC.IsEnabled"; // is an actuator + let signal_path = "Vehicle.ADAS.ESC.IsEnabled".to_string(); // is an actuator let value = Value { typed_value: Some(TypedValue::Bool(true)), @@ -880,7 +997,7 @@ mod tests { async fn test_actuate_with_read_auth_token_will_return_permission_denied() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let signal_path = "Vehicle.ADAS.ESC.IsEnabled"; // is an actuator + let signal_path = "Vehicle.ADAS.ESC.IsEnabled".to_string(); // is an actuator let value = Value { typed_value: Some(TypedValue::Bool(true)), @@ -898,8 +1015,8 @@ mod tests { async fn test_batch_actuate() { let mut client = KuksaClientV2::new_test_client(Some(ReadWrite)); - let eba_is_enabled = "Vehicle.ADAS.EBA.IsEnabled"; - let ebd_is_enabled = "Vehicle.ADAS.EBD.IsEnabled"; + let eba_is_enabled = "Vehicle.ADAS.EBA.IsEnabled".to_string(); + let ebd_is_enabled = "Vehicle.ADAS.EBD.IsEnabled".to_string(); let mut values = HashMap::new(); values.insert( @@ -917,18 +1034,19 @@ mod tests { let mut stream = client.open_provider_stream(None).await.unwrap(); - let provide_actuation_request = OpenProviderStreamRequest { - action: Some(Action::ProvideActuationRequest(ProvideActuationRequest { - actuator_identifiers: vec![ - SignalId { - signal: Some(Path(ebd_is_enabled.to_string())), - }, - SignalId { - signal: Some(Path(eba_is_enabled.to_string())), - }, - ], - })), - }; + let provide_actuation_request = + databroker_proto::kuksa::val::v2::OpenProviderStreamRequest { + action: Some(Action::ProvideActuationRequest(ProvideActuationRequest { + actuator_identifiers: vec![ + SignalId { + signal: Some(Path(ebd_is_enabled.to_string())), + }, + SignalId { + signal: Some(Path(eba_is_enabled.to_string())), + }, + ], + })), + }; stream.sender.send(provide_actuation_request).await.unwrap(); stream.receiver_stream.message().await.unwrap(); @@ -982,8 +1100,8 @@ mod tests { async fn test_batch_actuate_without_auth_token_will_return_unauthenticated() { let mut client = KuksaClientV2::new_test_client(None); - let eba_is_enabled = "Vehicle.ADAS.EBA.IsEnabled"; - let ebd_is_enabled = "Vehicle.ADAS.EBD.IsEnabled"; + let eba_is_enabled = "Vehicle.ADAS.EBA.IsEnabled".to_string(); + let ebd_is_enabled = "Vehicle.ADAS.EBD.IsEnabled".to_string(); let mut values = HashMap::new(); values.insert( @@ -1011,8 +1129,8 @@ mod tests { async fn test_batch_actuate_with_read_auth_token_will_return_permission_denied() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let eba_is_enabled = "Vehicle.ADAS.EBA.IsEnabled"; - let ebd_is_enabled = "Vehicle.ADAS.EBD.IsEnabled"; + let eba_is_enabled = "Vehicle.ADAS.EBA.IsEnabled".to_string(); + let ebd_is_enabled = "Vehicle.ADAS.EBD.IsEnabled".to_string(); let mut values = HashMap::new(); values.insert( @@ -1043,8 +1161,8 @@ mod tests { let mut stream = client .subscribe( vec![ - "Vehicle.AverageSpeed", - "Vehicle.Body.Raindetection.Intensity", + "Vehicle.AverageSpeed".to_string(), + "Vehicle.Body.Raindetection.Intensity".to_string(), ], None, ) @@ -1067,8 +1185,8 @@ mod tests { let mut stream = client .subscribe( vec![ - "Vehicle.AverageSpeed", - "Vehicle.Body.Raindetection.Intensity", + "Vehicle.AverageSpeed".to_string(), + "Vehicle.Body.Raindetection.Intensity".to_string(), ], None, ) @@ -1079,7 +1197,7 @@ mod tests { typed_value: Some(TypedValue::Float(100.0)), }; client - .publish_value("Vehicle.AverageSpeed", value) + .publish_value("Vehicle.AverageSpeed".to_string(), value) .await .expect("Could not publish Vehicle.AverageSpeed"); @@ -1109,7 +1227,7 @@ mod tests { async fn test_subscribe_to_empty_path_will_return_not_found() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let response = client.subscribe(vec![""], None).await; + let response = client.subscribe(vec!["".to_string()], None).await; assert!(response.is_err()); let err = response.unwrap_err(); @@ -1122,7 +1240,7 @@ mod tests { let mut client = KuksaClientV2::new_test_client(Some(Read)); let response = client - .subscribe(vec!["Vehicle.Some.Invalid.Path"], None) + .subscribe(vec!["Vehicle.Some.Invalid.Path".to_string()], None) .await; assert!(response.is_err()); @@ -1136,7 +1254,7 @@ mod tests { let mut client = KuksaClientV2::new_test_client(Some(Read)); let response = client - .subscribe(vec!["Vehicle.AverageSpeed"], Some(2048)) + .subscribe(vec!["Vehicle.AverageSpeed".to_string()], Some(2048)) .await; assert!(response.is_err()); @@ -1152,8 +1270,8 @@ mod tests { let response = client .subscribe( vec![ - "Vehicle.AverageSpeed", - "Vehicle.Body.Raindetection.Intensity", + "Vehicle.AverageSpeed".to_string(), + "Vehicle.Body.Raindetection.Intensity".to_string(), ], None, ) @@ -1169,7 +1287,10 @@ mod tests { async fn test_subscribe_by_id() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let vss_paths = vec!["Vehicle.Speed", "Vehicle.AverageSpeed"]; + let vss_paths = vec![ + "Vehicle.Speed".to_string(), + "Vehicle.AverageSpeed".to_string(), + ]; let path_id_map = client.resolve_ids_for_paths(vss_paths).await.unwrap(); let signal_ids: Vec = path_id_map.values().copied().collect(); @@ -1195,7 +1316,10 @@ mod tests { async fn test_subscribe_by_id_with_invalid_buffer_size_will_return_invalid_argument() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let vss_paths = vec!["Vehicle.Speed", "Vehicle.AverageSpeed"]; + let vss_paths = vec![ + "Vehicle.Speed".to_string(), + "Vehicle.AverageSpeed".to_string(), + ]; let path_id_map = client.resolve_ids_for_paths(vss_paths).await.unwrap(); let signal_ids: Vec = path_id_map.values().copied().collect(); @@ -1224,7 +1348,9 @@ mod tests { async fn test_list_metadata() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let response = client.list_metadata("Vehicle", "*").await; + let response = client + .list_metadata(("Vehicle".to_string(), "*".to_string())) + .await; assert!(response.is_ok()); let metadata_list = response.unwrap(); @@ -1236,7 +1362,9 @@ mod tests { async fn test_list_metadata_with_invalid_root_will_return_not_found() { let mut client = KuksaClientV2::new_test_client(Some(Read)); - let response = client.list_metadata("InvalidRoot", "*").await; + let response = client + .list_metadata(("InvalidRoot".to_string(), "*".to_string())) + .await; assert!(response.is_err()); let err = response.unwrap_err(); @@ -1248,7 +1376,9 @@ mod tests { async fn test_lists_metadata_without_auth_token_will_return_unauthenticated() { let mut client = KuksaClientV2::new_test_client(None); - let response = client.list_metadata("Vehicle", "*").await; + let response = client + .list_metadata(("Vehicle".to_string(), "*".to_string())) + .await; assert!(response.is_err()); let err = response.unwrap_err(); @@ -1283,7 +1413,7 @@ mod tests { fn expect_status_code(err: ClientError, code: tonic::Code) { match err { - Status(status) => { + ClientError::Status(status) => { assert_eq!(status.code(), code); } _ => panic!("unexpected error"), diff --git a/lib/sdv/src/lib.rs b/lib/sdv/src/lib.rs index d011b1cd..9fc1c5cd 100644 --- a/lib/sdv/src/lib.rs +++ b/lib/sdv/src/lib.rs @@ -1,5 +1,5 @@ /******************************************************************************** -* Copyright (c) 2023 Contributors to the Eclipse Foundation +* Copyright (c) 2025 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -15,7 +15,8 @@ use std::collections::HashMap; use databroker_proto::sdv::databroker as proto; use http::Uri; -use kuksa_common::{Client, ClientError}; +use kuksa_common::{Client, ClientError, SDVClientTraitV1}; +use tonic::async_trait; pub struct SDVClient { pub basic_client: Client, @@ -27,40 +28,58 @@ impl SDVClient { basic_client: Client::new(uri), } } +} + +#[async_trait] +impl SDVClientTraitV1 for SDVClient { + type SensorUpdateType = kuksa_common::types::SensorUpdateSDVTypeV1; + type UpdateActuationType = kuksa_common::types::UpdateActuationSDVTypeV1; + type PathType = kuksa_common::types::PathSDVTypeV1; + type SubscribeType = kuksa_common::types::SubscribeSDVTypeV1; + type PublishResponseType = kuksa_common::types::PublishResponseSDVTypeV1; + type GetResponseType = kuksa_common::types::GetResponseSDVTypeV1; + type SubscribeResponseType = kuksa_common::types::SubscribeResponseSDVTypeV1; + type ProvideResponseType = kuksa_common::types::ProvideResponseSDVTypeV1; + type ActuateResponseType = kuksa_common::types::ActuateResponseSDVTypeV1; + type MetadataResponseType = kuksa_common::types::MetadataResponseSDVTypeV1; - pub async fn get_metadata( + async fn update_datapoints( &mut self, - paths: Vec, - ) -> Result, ClientError> { - let mut client = proto::v1::broker_client::BrokerClient::with_interceptor( + datapoints: Self::SensorUpdateType, + ) -> Result { + let metadata = self + .get_metadata(datapoints.keys().cloned().collect()) + .await + .unwrap(); + let id_datapoints: HashMap = metadata + .into_iter() + .map(|meta| meta.id) + .zip(datapoints.into_values()) + .collect(); + + let mut client = proto::v1::collector_client::CollectorClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), ); - // Empty vec == all property metadata - let args = tonic::Request::new(proto::v1::GetMetadataRequest { names: paths }); - match client.get_metadata(args).await { - Ok(response) => { - let message = response.into_inner(); - Ok(message.list) - } + + let request = tonic::Request::new(proto::v1::UpdateDatapointsRequest { + datapoints: id_datapoints, + }); + match client.update_datapoints(request).await { + Ok(response) => Ok(response.into_inner()), Err(err) => Err(ClientError::Status(err)), } } - pub async fn get_datapoints( + async fn get_datapoints( &mut self, - paths: Vec>, - ) -> Result< - HashMap, - ClientError, - > { + paths: Self::PathType, + ) -> Result { let mut client = proto::v1::broker_client::BrokerClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), ); - let args = tonic::Request::new(proto::v1::GetDatapointsRequest { - datapoints: paths.iter().map(|path| path.as_ref().into()).collect(), - }); + let args = tonic::Request::new(proto::v1::GetDatapointsRequest { datapoints: paths }); match client.get_datapoints(args).await { Ok(response) => { let message = response.into_inner(); @@ -70,49 +89,52 @@ impl SDVClient { } } - pub async fn set_datapoints( + async fn subscribe( &mut self, - datapoints: HashMap, - ) -> Result { - let args = tonic::Request::new(proto::v1::SetDatapointsRequest { datapoints }); + paths: Self::SubscribeType, + ) -> Result { let mut client = proto::v1::broker_client::BrokerClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), ); - match client.set_datapoints(args).await { + let args = tonic::Request::new(proto::v1::SubscribeRequest { query: paths }); + + match client.subscribe(args).await { Ok(response) => Ok(response.into_inner()), Err(err) => Err(ClientError::Status(err)), } } - pub async fn subscribe( + async fn set_datapoints( &mut self, - query: String, - ) -> Result, ClientError> { + datapoints: Self::UpdateActuationType, + ) -> Result { + let args = tonic::Request::new(proto::v1::SetDatapointsRequest { datapoints }); let mut client = proto::v1::broker_client::BrokerClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), ); - let args = tonic::Request::new(proto::v1::SubscribeRequest { query }); - - match client.subscribe(args).await { + match client.set_datapoints(args).await { Ok(response) => Ok(response.into_inner()), Err(err) => Err(ClientError::Status(err)), } } - pub async fn update_datapoints( + async fn get_metadata( &mut self, - datapoints: HashMap, - ) -> Result { - let mut client = proto::v1::collector_client::CollectorClient::with_interceptor( + paths: Self::PathType, + ) -> Result { + let mut client = proto::v1::broker_client::BrokerClient::with_interceptor( self.basic_client.get_channel().await?.clone(), self.basic_client.get_auth_interceptor(), ); - - let request = tonic::Request::new(proto::v1::UpdateDatapointsRequest { datapoints }); - match client.update_datapoints(request).await { - Ok(response) => Ok(response.into_inner()), + // Empty vec == all property metadata + let args = tonic::Request::new(proto::v1::GetMetadataRequest { names: paths }); + match client.get_metadata(args).await { + Ok(response) => { + let message = response.into_inner(); + Ok(message.list) + } Err(err) => Err(ClientError::Status(err)), } }