From c3b6374c8a57fed1e9c45873e7b3d9b3e3d5356a Mon Sep 17 00:00:00 2001 From: Ash Beitz <8304894+ashbeitz@users.noreply.github.com> Date: Thu, 20 Jul 2023 15:34:15 -0700 Subject: [PATCH] Added seat massage demo (#35) * Added seat massage demo * Add seat massage demo * Add seat massage demo * Add seat massage demo * Add seat massage demo * Add seat massage demo * Render PlantUML files * Add seat massage demo * Add seat massage demo * Add seat massage demo * Add seat massage demo * Add seat massage demo * Add seat massage demo * Add seat massage demo * Add seat massage demo * Add seat massage demo * Add seat massage demo * Add seat massage demo * Render PlantUML files * Add seat massage demo * Add seat massage demo --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.toml | 1 + digital-twin-model/Cargo.toml | 2 + .../dtdl/v2/content/sdv/vehicle.json | 60 ----- .../v3/content/sdv/airbag_seat_massager.json | 20 ++ .../dtdl/v3/content/sdv/hmi.json | 23 ++ .../dtdl/v3/content/sdv/hvac.json | 24 ++ .../dtdl/v3/content/sdv/obd.json | 17 ++ digital-twin-model/src/lib.rs | 8 + digital-twin-model/src/sdv_v1.rs | 67 ++++-- docs/design/.accepted_words.txt | 13 +- docs/design/README.md | 207 ++++------------- docs/design/diagrams/findbyid_sequence.puml | 23 +- docs/design/diagrams/findbyid_sequence.svg | 2 +- docs/design/diagrams/get_sequence.puml | 51 +++++ docs/design/diagrams/get_sequence.svg | 1 + docs/design/diagrams/ibeji_component.puml | 12 +- docs/design/diagrams/ibeji_component.svg | 2 +- docs/design/diagrams/invoke_sequence.puml | 51 ++++- docs/design/diagrams/invoke_sequence.svg | 2 +- docs/design/diagrams/publish_sequence.puml | 8 - docs/design/diagrams/publish_sequence.svg | 1 - docs/design/diagrams/register_sequence.puml | 24 +- docs/design/diagrams/register_sequence.svg | 2 +- docs/design/diagrams/respond_sequence.puml | 8 - docs/design/diagrams/respond_sequence.svg | 1 - docs/design/diagrams/set_sequence.puml | 40 ++++ docs/design/diagrams/set_sequence.svg | 1 + docs/design/diagrams/subscribe_sequence.puml | 49 +++- docs/design/diagrams/subscribe_sequence.svg | 2 +- dtdl-parser/src/model_parser.rs | 48 +--- samples/command/Cargo.toml | 2 + samples/command/consumer/src/main.rs | 32 ++- samples/command/provider/src/main.rs | 15 +- samples/command/provider/src/provider_impl.rs | 73 +++--- samples/mixed/Cargo.toml | 2 + samples/mixed/consumer/src/main.rs | 69 ++++-- samples/mixed/provider/src/main.rs | 103 ++++++--- samples/mixed/provider/src/provider_impl.rs | 24 +- samples/property/Cargo.toml | 2 + samples/property/consumer/src/main.rs | 6 +- samples/property/provider/src/main.rs | 48 ++-- samples/seat_massager/Cargo.toml | 34 +++ .../consumer/src/consumer_impl.rs | 110 +++++++++ samples/seat_massager/consumer/src/main.rs | 191 ++++++++++++++++ samples/seat_massager/provider/src/main.rs | 99 ++++++++ .../provider/src/provider_impl.rs | 212 ++++++++++++++++++ 46 files changed, 1336 insertions(+), 456 deletions(-) delete mode 100644 digital-twin-model/dtdl/v2/content/sdv/vehicle.json create mode 100644 digital-twin-model/dtdl/v3/content/sdv/airbag_seat_massager.json create mode 100644 digital-twin-model/dtdl/v3/content/sdv/hmi.json create mode 100644 digital-twin-model/dtdl/v3/content/sdv/hvac.json create mode 100644 digital-twin-model/dtdl/v3/content/sdv/obd.json create mode 100644 docs/design/diagrams/get_sequence.puml create mode 100644 docs/design/diagrams/get_sequence.svg delete mode 100644 docs/design/diagrams/publish_sequence.puml delete mode 100644 docs/design/diagrams/publish_sequence.svg delete mode 100644 docs/design/diagrams/respond_sequence.puml delete mode 100644 docs/design/diagrams/respond_sequence.svg create mode 100644 docs/design/diagrams/set_sequence.puml create mode 100644 docs/design/diagrams/set_sequence.svg create mode 100644 samples/seat_massager/Cargo.toml create mode 100644 samples/seat_massager/consumer/src/consumer_impl.rs create mode 100644 samples/seat_massager/consumer/src/main.rs create mode 100644 samples/seat_massager/provider/src/main.rs create mode 100644 samples/seat_massager/provider/src/provider_impl.rs diff --git a/Cargo.toml b/Cargo.toml index b501875a..fcde9006 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ members = [ "samples/command", "samples/mixed", "samples/property", + "samples/seat_massager", ] [workspace.dependencies] diff --git a/digital-twin-model/Cargo.toml b/digital-twin-model/Cargo.toml index f0bfe957..51e4dd38 100644 --- a/digital-twin-model/Cargo.toml +++ b/digital-twin-model/Cargo.toml @@ -9,6 +9,8 @@ edition = "2021" license = "MIT" [dependencies] +serde = { workspace = true } +serde_derive = { workspace = true } [lib] path = "src/lib.rs" diff --git a/digital-twin-model/dtdl/v2/content/sdv/vehicle.json b/digital-twin-model/dtdl/v2/content/sdv/vehicle.json deleted file mode 100644 index f4e1fb9f..00000000 --- a/digital-twin-model/dtdl/v2/content/sdv/vehicle.json +++ /dev/null @@ -1,60 +0,0 @@ -[ - { - "@context": ["dtmi:dtdl:context;2"], - "@type": "Interface", - "@id": "dtmi:sdv:Vehicle:Cabin:HVAC;1", - "description": "Heat, Ventilation and Air Conditioning", - "contents": [ - { - "@type": ["Property", "Temperature"], - "@id": "dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1", - "name": "AmbientAirTemperature", - "description": "The immediate surroundings air temperature (in Fahrenheit).", - "schema": "integer", - "unit": "degreeFahrenheit" - }, - { - "@type": ["Property"], - "@id": "dtmi:sdv:Vehicle:Cabin:HVAC:IsAirConditioningActive;1", - "name": "IsAirConditioningActive", - "description": "Is air conditioning active?", - "schema": "boolean" - } - ] - }, - { - "@context": ["dtmi:dtdl:context;2"], - "@type": "Interface", - "@id": "dtmi:sdv:Vehicle:OBD;1", - "description": "On-board Diagnostics Interface", - "contents": [ - { - "@type": ["Property"], - "@id": "dtmi:sdv:Vehicle:OBD:HybridBatteryRemaining;1", - "name": "HybridBatteryRemaining", - "description": "The remaining hybrid battery life.", - "schema": "integer", - "unit": "percent" - } - ] - }, - { - "@context": ["dtmi:dtdl:context;2"], - "@type": "Interface", - "@id": "dtmi:sdv:Vehicle:Cabin:Infotainment:HMI;1", - "description": "The Human Machine Interface.", - "contents": [ - { - "@type": ["Command"], - "@id": "dtmi:sdv:Vehicle:Cabin:Infotainment:HMI:ShowNotification;1", - "name": "ShowNotification", - "request": { - "name": "ShowNotification", - "displayName": "Show Notification", - "description": "Show a notification on the HMI.", - "schema": "string" - } - } - ] - } -] diff --git a/digital-twin-model/dtdl/v3/content/sdv/airbag_seat_massager.json b/digital-twin-model/dtdl/v3/content/sdv/airbag_seat_massager.json new file mode 100644 index 00000000..be2478bb --- /dev/null +++ b/digital-twin-model/dtdl/v3/content/sdv/airbag_seat_massager.json @@ -0,0 +1,20 @@ +[ + { + "@context": ["dtmi:dtdl:context;3"], + "@type": "Interface", + "@id": "dtmi:sdv:AirbagSeatMassager;1", + "description": "Airbag Seat Massager", + "contents": [ + { + "@type": "Property", + "@id": "dtmi:sdv:AirbagSeatMassager:MassageAirbags;1", + "name": "MassageAirbags", + "description": "The inflation level (0..100) for each massage airbag.", + "schema": { + "@type": "Array", + "elementSchema": "integer" + } + } + ] + } +] diff --git a/digital-twin-model/dtdl/v3/content/sdv/hmi.json b/digital-twin-model/dtdl/v3/content/sdv/hmi.json new file mode 100644 index 00000000..8d025274 --- /dev/null +++ b/digital-twin-model/dtdl/v3/content/sdv/hmi.json @@ -0,0 +1,23 @@ +[ + { + "@context": ["dtmi:dtdl:context;3"], + "@type": "Interface", + "@id": "dtmi:sdv:HMI;1", + "description": "The Human Machine Interface.", + "contents": [ + { + "@type": "Command", + "@id": "dtmi:sdv:HMI:ShowNotification;1", + "name": "ShowNotification", + "description": "Show a notification on the HMI.", + "request": { + "@id": "dtmi:sdv:HMI:ShowNotification:request;1", + "name": "Notification", + "displayName": "Notification", + "description": "The notification to show on the HMI.", + "schema": "string" + } + } + ] + } +] diff --git a/digital-twin-model/dtdl/v3/content/sdv/hvac.json b/digital-twin-model/dtdl/v3/content/sdv/hvac.json new file mode 100644 index 00000000..2f06fc25 --- /dev/null +++ b/digital-twin-model/dtdl/v3/content/sdv/hvac.json @@ -0,0 +1,24 @@ +[ + { + "@context": ["dtmi:dtdl:context;3"], + "@type": "Interface", + "@id": "dtmi:sdv:HVAC;1", + "description": "Heat, Ventilation and Air Conditioning", + "contents": [ + { + "@type": "Property", + "@id": "dtmi:sdv:HVAC:AmbientAirTemperature;1", + "name": "AmbientAirTemperature", + "description": "The immediate surroundings air temperature (in Fahrenheit).", + "schema": "integer" + }, + { + "@type": "Property", + "@id": "dtmi:sdv:HVAC:IsAirConditioningActive;1", + "name": "IsAirConditioningActive", + "description": "Is air conditioning active?", + "schema": "boolean" + } + ] + } +] diff --git a/digital-twin-model/dtdl/v3/content/sdv/obd.json b/digital-twin-model/dtdl/v3/content/sdv/obd.json new file mode 100644 index 00000000..9453a419 --- /dev/null +++ b/digital-twin-model/dtdl/v3/content/sdv/obd.json @@ -0,0 +1,17 @@ +[ + { + "@context": ["dtmi:dtdl:context;3"], + "@type": "Interface", + "@id": "dtmi:sdv:OBD;1", + "description": "On-board Diagnostics Interface", + "contents": [ + { + "@type": "Property", + "@id": "dtmi:sdv:OBD:HybridBatteryRemaining;1", + "name": "HybridBatteryRemaining", + "description": "The remaining hybrid battery life.", + "schema": "integer" + } + ] + } +] diff --git a/digital-twin-model/src/lib.rs b/digital-twin-model/src/lib.rs index 82b8452a..b300bf20 100644 --- a/digital-twin-model/src/lib.rs +++ b/digital-twin-model/src/lib.rs @@ -3,3 +3,11 @@ // SPDX-License-Identifier: MIT pub mod sdv_v1; + +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Metadata { + #[serde(rename = "$model")] + pub model: String, +} diff --git a/digital-twin-model/src/sdv_v1.rs b/digital-twin-model/src/sdv_v1.rs index 4cd1879a..dd31b818 100644 --- a/digital-twin-model/src/sdv_v1.rs +++ b/digital-twin-model/src/sdv_v1.rs @@ -2,30 +2,55 @@ // Licensed under the MIT license. // SPDX-License-Identifier: MIT -// Note: In the future this code could be auto-generated from a DTDL spec. +// Note: In the future this code should be generated from a DTDL spec. -pub mod vehicle { - pub mod cabin { - pub mod hvac { - pub mod ambient_air_temperature { - pub const ID: &str = "dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1"; - } - pub mod is_air_conditioning_active { - pub const ID: &str = "dtmi:sdv:Vehicle:Cabin:HVAC:IsAirConditioningActive;1"; - } +pub mod airbag_seat_massager { + pub mod massage_airbags { + pub const ID: &str = "dtmi:sdv:AirbagSeatMassager:MassageAirbags;1"; + pub const NAME: &str = "MassageAirbags"; + pub const DESCRIPTION: &str = "The inflation level (0..100) for each massage airbag."; + pub type TYPE = Vec; + } +} + +pub mod hmi { + pub mod show_notification { + pub const ID: &str = "dtmi:sdv:HMI:ShowNotification;1"; + pub const NAME: &str = "Show Notification"; + pub const DESCRIPTION: &str = "Show a notification on the HMI."; + pub mod request { + pub const ID: &str = "dtmi:sdv:HMI:ShowNotification::request;1"; + pub const NAME: &str = "Notification"; + pub const DESCRIPTION: &str = "The notification to show on the HMI."; + pub type TYPE = String; } - pub mod infotainment { - pub mod hmi { - pub mod show_notification { - pub const ID: &str = - "dtmi:sdv:Vehicle:Cabin:Infotainment:HMI:ShowNotification;1"; - } - } + pub mod response { + pub const ID: &str = "dtmi:sdv:HMI:ShowNotification::response;1"; } } - pub mod obd { - pub mod hybrid_battery_remaining { - pub const ID: &str = "dtmi:sdv:Vehicle:OBD:HybridBatteryRemaining;1"; - } +} + +pub mod hvac { + pub mod ambient_air_temperature { + pub const ID: &str = "dtmi:sdv:HVAC:AmbientAirTemperature;1"; + pub const NAME: &str = "AmbientAirTemperature"; + pub const DESCRIPTION: &str = "The immediate surroundings air temperature (in Fahrenheit)."; + pub type TYPE = i32; + } + + pub mod is_air_conditioning_active { + pub const ID: &str = "dtmi:sdv:HVAC:IsAirConditioningActive;1"; + pub const NAME: &str = "IsAirConditioningActive"; + pub const DESCRIPTION: &str = "Is air conditioning active?"; + pub type TYPE = bool; + } +} + +pub mod obd { + pub mod hybrid_battery_remaining { + pub const ID: &str = "dtmi:sdv::OBD:HybridBatteryRemaining;1"; + pub const NAME: &str = "HybridBatteryRemaining"; + pub const DESCRIPTION: &str = "The remaining hybrid battery life."; + pub type TYPE = i32; } } diff --git a/docs/design/.accepted_words.txt b/docs/design/.accepted_words.txt index 5aa74aab..3d2b413d 100644 --- a/docs/design/.accepted_words.txt +++ b/docs/design/.accepted_words.txt @@ -1,26 +1,27 @@ AmbientAirTemperature +api +com degreeFahrenheit dt dtdl DTDL dtmi +github +https ibeji Ibeji +IsAirConditioningActive findbyid FindById gRPC http HVAC +json metadata +opendigitaltwins org RemotelyAccessible sdv svg -uml uri -api -com -github -https -opendigitaltwins diff --git a/docs/design/README.md b/docs/design/README.md index 2383cba8..51a24f64 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -4,12 +4,12 @@ - [Architecture](#architecture) - [DTDL](#dtdl) - [In-Vehicle Digital Twin Service](#in-vehicle-digital-twin-service) -- [Provider](#provider) -- [Consumer](#consumer) +- [Sample Digital Twin Interactions](#sample-digital-twin-interactions) +- [Appendix: Digital Twin Interface](#appendix-digital-twin-interface) ## Introduction -Project Eclipse Ibeji delivers an In-Vehicle software component that is a digital representation of vehicle hardware resources. The representation is usable by other software in the vehicle to monitor and control vehicle hardware resources in a standardized manner. +Project Eclipse Ibeji delivers an In-Vehicle software component that provides a digital representation of vehicle hardware resources. The representation is usable by other software in the vehicle to monitor and control vehicle hardware resources in a standardized manner. Please note that the initial Ibeji implementation is a proof-of-concept. We would like to see it evolve into an enterprise class solution. @@ -23,52 +23,49 @@ Ibeji has three main architectural concepts: The first Ibeji architectural concept that we will introduce is the Digital Twin Consumer. A Digital Twin Consumer is a software entity that utilizes Ibeji to interface with the digital representation of the In-Vehicle hardware components. -Another Ibeji architectural concept is the Digital Twin Provider. A Digital Twin Provider is the access point to some/all of the vehicle's hardware resources. A Digital Twin Provider registers itself with the In-Vehicle Digital Twin Service. Once registered, the In-Vehicle Digital Twin Service can make the resources available to Digital Twin Consumers. Each resource includes meta data so that Digital Twin Consumers know how to interact with it. The In-Vehicle Digital Twin Service supports multiple simultaneous Digital Twin Providers and internally resolves overlapping resources offered by multiple Digital Twin Providers. These overlaps offer multiple options for interacting with a resource and can improve the resource's availability (by supporting multiple access paths). +Another Ibeji architectural concept is the Digital Twin Provider. A Digital Twin Provider is the access point to some/all of the vehicle's hardware resources. A Digital Twin Provider registers itself with the In-Vehicle Digital Twin Service. Once registered, the In-Vehicle Digital Twin Service can make the resources available to Digital Twin Consumers. Each resource includes metadata so that Digital Twin Consumers know how to interact with it. The In-Vehicle Digital Twin Service supports multiple simultaneous Digital Twin Providers and accommodates overlapping resources offered by multiple Digital Twin Providers. These overlaps offer multiple options for interacting with a resource and can improve the resource's availability (by supporting multiple access paths). -In the middle is the In-Vehicle Digital Twin Service. It exports a query interface that enables Digital Twin Consumers to discover the vehicle's resources and provides the details necessary to use those resources. The In-Vehicle Digital Twin Service has an interface that allows Digital Twin Providers to dynamically register their resources. +The final architectural concept is the In-Vehicle Digital Twin Service. It has an interface that enables Digital Twin Consumers to discover the vehicle's resources and provides the details necessary to interact with those resources. The In-Vehicle Digital Twin Service has an interface that allows Digital Twin Providers to dynamically register their resources. -Below is the component diagram for Ibeji. +Below is the architectural diagram for Ibeji. ![Component Diagram](diagrams/ibeji_component.svg) ## DTDL -Fundamental to the Ibeji solution is its use of Digital Twin Definition Language [DTDL](https://github.com/Azure/opendigitaltwins-dtdl) to identify and specify each of the vehicle's resources. +Fundamental to the Ibeji solution is its use of Digital Twin Definition Language [DTDL](https://github.com/Azure/opendigitaltwins-dtdl) to identify and specify each of the vehicle's resources. We are using Version 3 of DTDL. -This initial contribution does not try to arrange the resources into a hierarchy or into a graph. It is intended that some future update will enable this capability. +This initial contribution does not try to arrange the resources into a hierarchy or into a graph. It is expected that some future version will facilitate this. -DTDL can identify and specify each of the resources. Below is an example for the AmbientAirTemperature property. +DTDL can identify and specify each of the resources. Below is an example of a HVAC resource. -```uml - { - "@context": ["dtmi:dtdl:context;2"], - "@type": "Interface", - "@id": "dtmi:sdv:Vehicle:Cabin:HVAC;1", - "contents": [ - { - "@type": ["Property", "Temperature"], - "@id": "dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1", - "name": "Cabin_AmbientAirTemperature", +```json +{ + "@context": ["dtmi:dtdl:context;3"], + "@type": "Interface", + "@id": "dtmi:sdv:HVAC;1", + "description": "Heat, Ventilation and Air Conditioning", + "contents": [ + { + "@type": "Property", + "@id": "dtmi:sdv:HVAC:AmbientAirTemperature;1", + "name": "AmbientAirTemperature", "description": "The immediate surroundings air temperature (in Fahrenheit).", - "schema": "integer", - "unit": "degreeFahrenheit" + "schema": "integer" + }, + { + "@type": "Property", + "@id": "dtmi:sdv:HVAC:IsAirConditioningActive;1", + "name": "IsAirConditioningActive", + "description": "Is air conditioning active?", + "schema": "boolean" } - ] - } + ] +} ``` -The DTDL must use the standard dtmi dtdl context. - ## In-Vehicle Digital Twin Service -### In-Vehicle Digital Twin Service Overview - -The initial In-Vehicle Digital Twin Service will provide the functionality needed by the proof-of-concept. On the Provider side, this initial contribution supports only a single Provider registering its DTDL. On the Consumer side, there is a simplified query api, and the ability to subscribe to a provided hardware resource data feed and to invoke a command on a provided hardware resource. - -### Interfaces - -The initial In-Vehicle Digital Twin Service supports both Providers and Consumers. - ### Activities #### Register @@ -83,124 +80,35 @@ Below is the sequence diagram for the Find-By-Id activity. ![Sequence Diagram](diagrams/findbyid_sequence.svg) -## Provider +## Sample Digital Twin Interactions -### Overview +### Activities -The initial Providers will implement basic resources - the AmbientAirTemperature property and the send_notification command. +#### Get -### Interfaces +The sequence diagram for a Digital Twin Consumer using the Get operation. -A Provider supports an interface for subscribing to resource's data feeds, requesting a resource's value, setting a resource's value and invoking a command. +![Sequence Diagram](diagrams/get_sequence.svg) -### Activities +#### Set + +The sequence diagram for a Digital Twin Consumer using the Set operation. + +![Sequence Diagram](diagrams/set_sequence.svg) #### Subscribe -Below is the sequence diagram for the Subscribe activity. +The sequence diagram for a Digital Twin Consumer using the Subscribe operation. ![Sequence Diagram](diagrams/subscribe_sequence.svg) #### Invoke -Below is the sequence diagram for the Invoke activity. +The sequence diagram for a Digital Twin Consumer using the Invoke operation. ![Sequence Diagram](diagrams/invoke_sequence.svg) -## Consumer - -### Overview - -The initial Consumers will provide the functionality needed by the proof-of-concept to subscribe to resources data feeds and invoke commands on resources. - -Interfaces - -A Consumer supports an interface that is the callback/notification endpoint for subscribed-to data feeds. - -Activities - -#### Publish - -Below is the sequence diagram for the Publish activity. - -![Sequence Diagram](diagrams/publish_sequence.svg) - -#### Respond - -Below is the sequence diagram for the Respond activity. - -![Sequence Diagram](diagrams/respond_sequence.svg) - -## Appendix A – Digital Twin Provider Interface - -### Subscribe - -Subscribe to a property's data feed. - -#### Request - -- entity_id - The property's id. -- consumer_uri - The uri for the consumer endpoint where the data feed will be delivered. - -#### Response - -- No response. - -### Unsubscribe - -Unsubscribe from a property's data feed. - -#### Request - -- entity_id - The property's id. -- consumer_uri - The uri for the consumer endpoint where the data feed should no longer be delivered. - -#### Response - -- No response. - -### Get - -Get the latest value for a property and publish it to a consumer endpoint. - -#### Request - -- entity_id - The property's id. -- consumer_uri - The uri for the consumer endpoint where the value should be delivered. - -#### Response - -- No response. - -### Set - -Set an entity's value to the one provided. This may not cause a change if the entity cannot be updated. - -#### Request - -- entity_id - The entity's id. -- value - The entity's new value. - -#### Response - -- No response. - -### Invoke - -Invoke a command. - -#### Request - -- entity_id - The command's id. -- consumer_uri - The uri for the endpoint where the command's response should be delivered. -- response_id - The id that the invoker of the command provided for the response. -- payload - The command's request payload. - -#### Response - -- No response. - -## Appendix B – Digital Twin Interface +## Appendix: Digital Twin Interface ### FindById @@ -225,32 +133,3 @@ Register one or more entities access information. #### Response - No response. - -## Appendix C – Digital Twin Consumer Interface - -### Publish - -Publish a value for a specific entity. - -#### Request - -- entity_id - The entity's id. -- value - The value to publish. - -#### Response - -- No response. - -### Respond - -Respond for the execution of a command. - -#### Request - -- entity_id - The command's id. -- response_id - The id that the invoker of the command provided for the response. -- payload - The command's response payload. - -#### Response - -- No response. diff --git a/docs/design/diagrams/findbyid_sequence.puml b/docs/design/diagrams/findbyid_sequence.puml index 64cbd179..9a24c2e1 100644 --- a/docs/design/diagrams/findbyid_sequence.puml +++ b/docs/design/diagrams/findbyid_sequence.puml @@ -1,9 +1,24 @@ @startuml -participant "In-Vehicle Digital Twin Service" -participant "Consumer" +autonumber -"Consumer" -> "In-Vehicle Digital Twin Service" : FindById - request (entity id) -"In-Vehicle Digital Twin Service" --> "Consumer" : FindById - response (entity access info) +"Digital Twin Consumer" -> "In-Vehicle Digital Twin Service" : FindById("dtmi:sdv:HVAC:AmbientAirTemperature;1") - request +"Digital Twin Consumer" <- "In-Vehicle Digital Twin Service" : FindById - response +note left +entityAccessInfo: +{ + name: "AmbientAirTemperature" + id: "dtmi:sdv:HVAC:AmbientAirTemperature;1" + description: "The immediate surroundings air temperature (in Fahrenheit)." + endpointInfoList: [ + { + protocol: "grpc" + operations: ["Get", "Subscribe"] + uri: "http://127.0.0.1:4000" + context: dtmi:sdv:HVAC:AmbientAirTemperature;1" + } + ] +} +end note @enduml diff --git a/docs/design/diagrams/findbyid_sequence.svg b/docs/design/diagrams/findbyid_sequence.svg index 6d1ece9b..ccc7cf45 100644 --- a/docs/design/diagrams/findbyid_sequence.svg +++ b/docs/design/diagrams/findbyid_sequence.svg @@ -1 +1 @@ -In-Vehicle Digital Twin ServiceIn-Vehicle Digital Twin ServiceConsumerConsumerFindById - request (entity id)FindById - response (entity access info) \ No newline at end of file +Digital Twin ConsumerDigital Twin ConsumerIn-Vehicle Digital Twin ServiceIn-Vehicle Digital Twin Service1FindById("dtmi:sdv:HVAC:AmbientAirTemperature;1") - request2FindById - responseentityAccessInfo:{name: "AmbientAirTemperature"id: "dtmi:sdv:HVAC:AmbientAirTemperature;1"description: "The immediate surroundings air temperature (in Fahrenheit)."endpointInfoList: [{protocol: "grpc"operations: ["Get", "Subscribe"]uri: "http://127.0.0.1:4000"context: dtmi:sdv:HVAC:AmbientAirTemperature;1"}]} \ No newline at end of file diff --git a/docs/design/diagrams/get_sequence.puml b/docs/design/diagrams/get_sequence.puml new file mode 100644 index 00000000..6fbe4db2 --- /dev/null +++ b/docs/design/diagrams/get_sequence.puml @@ -0,0 +1,51 @@ +@startuml + +autonumber + +"Digital Twin Consumer" -> "In-Vehicle Digital Twin Service" : FindById("dtmi:sdv:HVAC:AmbientAirTemperature;1") - request +"Digital Twin Consumer" <- "In-Vehicle Digital Twin Service" : FindById - response +note left +entityAccessInfo: +{ + name: "AmbientAirTemperature" + id: "dtmi:sdv:HVAC:AmbientAirTemperature;1" + description: "The immediate surroundings air temperature (in Fahrenheit)." + endpointInfoList: [ + { + protocol: "grpc" + operations: ["Get", "Invoke"] + uri: "http://127.0.0.1:4000" + context: "dtmi:sdv:HVAC:AmbientAirTemperature;1" + } + ] +} +end note + +"Digital Twin Consumer" -> "Digital Twin Provider" : Get - request +note right +{ + entity_id: "dtmi:sdv:HVAC:AmbientAirTemperature;1" + consumer_uri: "http://127.0.0.1:5000" +} +end note + +"Digital Twin Consumer" <- "Digital Twin Provider" : Get - response + +"Digital Twin Consumer" <- "Digital Twin Provider" : Publish - request +note left +{ + entity_id: "dtmi:sdv:HVAC:AmbientAirTemperature;1" + value: + { + "AmbientAirTemperature": 77, + "$metadata": { + "$model": "dtmi:sdv:HVAC:AmbientAirTemperature;1" + } + + } +} +end note + +"Digital Twin Consumer" -> "Digital Twin Provider" : Publish - response + +@enduml diff --git a/docs/design/diagrams/get_sequence.svg b/docs/design/diagrams/get_sequence.svg new file mode 100644 index 00000000..de96cc47 --- /dev/null +++ b/docs/design/diagrams/get_sequence.svg @@ -0,0 +1 @@ +Digital Twin ConsumerDigital Twin ConsumerIn-Vehicle Digital Twin ServiceIn-Vehicle Digital Twin ServiceDigital Twin ProviderDigital Twin Provider1FindById("dtmi:sdv:HVAC:AmbientAirTemperature;1") - request2FindById - responseentityAccessInfo:{name: "AmbientAirTemperature"id: "dtmi:sdv:HVAC:AmbientAirTemperature;1"description: "The immediate surroundings air temperature (in Fahrenheit)."endpointInfoList: [{protocol: "grpc"operations: ["Get", "Invoke"]uri: "http://127.0.0.1:4000"context: "dtmi:sdv:HVAC:AmbientAirTemperature;1"}]}3Get - request{entity_id: "dtmi:sdv:HVAC:AmbientAirTemperature;1"consumer_uri: "http://127.0.0.1:5000"}4Get - response5Publish - request{entity_id: "dtmi:sdv:HVAC:AmbientAirTemperature;1"value:{"AmbientAirTemperature": 77,"$metadata": {"$model": "dtmi:sdv:HVAC:AmbientAirTemperature;1"} }}6Publish - response \ No newline at end of file diff --git a/docs/design/diagrams/ibeji_component.puml b/docs/design/diagrams/ibeji_component.puml index fabd806c..f29300bc 100644 --- a/docs/design/diagrams/ibeji_component.puml +++ b/docs/design/diagrams/ibeji_component.puml @@ -1,15 +1,17 @@ @startuml -frame "Consumer" { - interface DigitalTwinConsumerInterface +frame "Digital Twin Consumer" { } frame "In-Vehicle Digital Twin Service" { - interface DigitalTwinInterface + interface "Digital Twin Interface" } -frame "Provider" { - interface DigitalTwinProviderInterface +frame "Digital Twin Provider" { } +"Digital Twin Provider" --> "Digital Twin Interface" : Register + +"Digital Twin Consumer" --> "Digital Twin Interface" : FindById + @enduml diff --git a/docs/design/diagrams/ibeji_component.svg b/docs/design/diagrams/ibeji_component.svg index 10bcf1e8..c98abd83 100644 --- a/docs/design/diagrams/ibeji_component.svg +++ b/docs/design/diagrams/ibeji_component.svg @@ -1 +1 @@ -ConsumerIn-Vehicle Digital Twin ServiceProviderDigitalTwinConsumerInterfaceDigitalTwinInterfaceDigitalTwinProviderInterface \ No newline at end of file +In-Vehicle Digital Twin ServiceDigital Twin ConsumerDigital Twin InterfaceDigital Twin ProviderRegisterFindById \ No newline at end of file diff --git a/docs/design/diagrams/invoke_sequence.puml b/docs/design/diagrams/invoke_sequence.puml index 9a113876..a5c0f094 100644 --- a/docs/design/diagrams/invoke_sequence.puml +++ b/docs/design/diagrams/invoke_sequence.puml @@ -1,8 +1,53 @@ @startuml -participant "Provider" -participant "Consumer" +autonumber -"Consumer" -> "Provider" : Invoke - request (entity id, consumer uri, response id, payload) +"Digital Twin Consumer" -> "In-Vehicle Digital Twin Service" : FindById("dtmi:sdv:HMI:ShowNotification;1") - request +"Digital Twin Consumer" <- "In-Vehicle Digital Twin Service" : FindById - response +note left +entityAccessInfo: +{ + name: "ShowNotification" + id: "dtmi:sdv:HMI:ShowNotifiation;1" + description: "..." + endpointInfoList: [ + { + protocol: "grpc" + operations: ["Invoke"] + uri: "http://127.0.0.1:4000" + context: "dtmi:sdv:HMI:ShowNotifiation;1" + } + ] +} +end note + +"Digital Twin Consumer" -> "Digital Twin Provider" : Invoke - request +note right +{ + entity_id: "dtmi:sdv:HMI:ShowNotification:request;1" + consumer_uri: "http://127.0.0.1:5000" + response_id: "123456789" + payload: + { + "Notification": "Show this sample notification", + "$metadata": { + "$model": "dtmi:sdv:HMI:ShowNotification:request;1" + } + } +} +end note + +"Digital Twin Consumer" <- "Digital Twin Provider" : Invoke - response + +"Digital Twin Consumer" <- "Digital Twin Provider" : Respond - request +note left +{ + entity_id: "dtmi:sdv:HMI:ShowNotification;1" + response_id: "123456789" + payload: {} +} +end note + +"Digital Twin Consumer" -> "Digital Twin Provider" : Respond - response @enduml diff --git a/docs/design/diagrams/invoke_sequence.svg b/docs/design/diagrams/invoke_sequence.svg index ba1346f9..20d5173b 100644 --- a/docs/design/diagrams/invoke_sequence.svg +++ b/docs/design/diagrams/invoke_sequence.svg @@ -1 +1 @@ -ProviderProviderConsumerConsumerInvoke - request (entity id, consumer uri, response id, payload) \ No newline at end of file +Digital Twin ConsumerDigital Twin ConsumerIn-Vehicle Digital Twin ServiceIn-Vehicle Digital Twin ServiceDigital Twin ProviderDigital Twin Provider1FindById("dtmi:sdv:HMI:ShowNotification;1") - request2FindById - responseentityAccessInfo:{name: "ShowNotification"id: "dtmi:sdv:HMI:ShowNotifiation;1"description: "..."endpointInfoList: [{protocol: "grpc"operations: ["Invoke"]uri: "http://127.0.0.1:4000"context: "dtmi:sdv:HMI:ShowNotifiation;1"}]}3Invoke - request{entity_id: "dtmi:sdv:HMI:ShowNotification:request;1"consumer_uri: "http://127.0.0.1:5000"response_id: "123456789"payload:{"Notification": "Show this sample notification","$metadata": {"$model": "dtmi:sdv:HMI:ShowNotification:request;1"}}}4Invoke - response5Respond - request{entity_id: "dtmi:sdv:HMI:ShowNotification;1"response_id: "123456789"payload: {}}6Respond - response \ No newline at end of file diff --git a/docs/design/diagrams/publish_sequence.puml b/docs/design/diagrams/publish_sequence.puml deleted file mode 100644 index 695a9b31..00000000 --- a/docs/design/diagrams/publish_sequence.puml +++ /dev/null @@ -1,8 +0,0 @@ -@startuml - -participant "Provider" -participant "Consumer" - -"Provider" -> "Consumer" : Publish - request (entity id, value) - -@enduml diff --git a/docs/design/diagrams/publish_sequence.svg b/docs/design/diagrams/publish_sequence.svg deleted file mode 100644 index d4a3e5ce..00000000 --- a/docs/design/diagrams/publish_sequence.svg +++ /dev/null @@ -1 +0,0 @@ -ProviderProviderConsumerConsumerPublish - request (entity id, value) \ No newline at end of file diff --git a/docs/design/diagrams/register_sequence.puml b/docs/design/diagrams/register_sequence.puml index 087586e5..fb0da6ae 100644 --- a/docs/design/diagrams/register_sequence.puml +++ b/docs/design/diagrams/register_sequence.puml @@ -1,8 +1,26 @@ @startuml -participant "Provider" -participant "In-Vehicle Digital Twin Service" +autonumber -"Provider" -> "In-Vehicle Digital Twin Service" : Register - request (entity access info list) +"In-Vehicle Digital Twin Service" <- "Digital Twin Provider" : Register - request +note left +entityAccessInfoList: [ + { + name: "AmbientAirTemperature" + id: "dtmi:sdv:HVAC:AmbientAirTemperature;1" + description: "The immediate surroundings air temperature (in Fahrenheit)." + endpointInfoList: [ + { + protocol: "grpc" + operations: ["Get", "Subscribe"] + uri: "http://127.0.0.1:4000" + context: "dtmi:sdv:HVAC:AmbientAirTemperature;1" + } + ] + } +] +end note + +"In-Vehicle Digital Twin Service" -> "Digital Twin Provider": Register - response @enduml diff --git a/docs/design/diagrams/register_sequence.svg b/docs/design/diagrams/register_sequence.svg index f4aef8b1..bacb944e 100644 --- a/docs/design/diagrams/register_sequence.svg +++ b/docs/design/diagrams/register_sequence.svg @@ -1 +1 @@ -ProviderProviderIn-Vehicle Digital Twin ServiceIn-Vehicle Digital Twin ServiceRegister - request (entity access info list) \ No newline at end of file +In-Vehicle Digital Twin ServiceIn-Vehicle Digital Twin ServiceDigital Twin ProviderDigital Twin Provider1Register - requestentityAccessInfoList: [{name: "AmbientAirTemperature"id: "dtmi:sdv:HVAC:AmbientAirTemperature;1"description: "The immediate surroundings air temperature (in Fahrenheit)."endpointInfoList: [{protocol: "grpc"operations: ["Get", "Subscribe"]uri: "http://127.0.0.1:4000"context: "dtmi:sdv:HVAC:AmbientAirTemperature;1"}]}]2Register - response \ No newline at end of file diff --git a/docs/design/diagrams/respond_sequence.puml b/docs/design/diagrams/respond_sequence.puml deleted file mode 100644 index 5eeae2be..00000000 --- a/docs/design/diagrams/respond_sequence.puml +++ /dev/null @@ -1,8 +0,0 @@ -@startuml - -participant "Provider" -participant "Consumer" - -"Provider" -> "Consumer" : Respond - request (entity id, response id, payload) - -@enduml diff --git a/docs/design/diagrams/respond_sequence.svg b/docs/design/diagrams/respond_sequence.svg deleted file mode 100644 index 1a2ff9fa..00000000 --- a/docs/design/diagrams/respond_sequence.svg +++ /dev/null @@ -1 +0,0 @@ -ProviderProviderConsumerConsumerRespond - request (entity id, response id, payload) \ No newline at end of file diff --git a/docs/design/diagrams/set_sequence.puml b/docs/design/diagrams/set_sequence.puml new file mode 100644 index 00000000..2b940c10 --- /dev/null +++ b/docs/design/diagrams/set_sequence.puml @@ -0,0 +1,40 @@ +@startuml + +autonumber + +"Digital Twin Consumer" -> "In-Vehicle Digital Twin Service" : FindById("dtmi:sdv:HVAC:IsAirConditioningActive;1") - request +"Digital Twin Consumer" <- "In-Vehicle Digital Twin Service" : FindById - response +note left +entityAccessInfo: +{ + name: "IsAirConditioningActive" + id: "dtmi:sdv:HVAC:IsAirConditioningActive;1" + description: "Is air conditioning active?" + endpointInfoList: [ + { + protocol: "grpc" + operations: ["Get", "Set", "Subscribe"] + uri: "http://127.0.0.1:4000" + context: "dtmi:sdv:HVAC:IsAirConditioningActive;1" + } + ] +} +end note + +"Digital Twin Consumer" -> "Digital Twin Provider" : Set - request +note right +{ + entity_id: "dtmi:sdv:HVAC:IsAirConditioningActive;1" + value: + { + "IsAirConditioningActive": true + "$metadata": { + "$model": "dtmi:sdv:HVAC:IsAirConditioningActive;1" + } + } +} +end note + +"Digital Twin Consumer" <- "Digital Twin Provider" : Set - response + +@enduml diff --git a/docs/design/diagrams/set_sequence.svg b/docs/design/diagrams/set_sequence.svg new file mode 100644 index 00000000..c10a3d63 --- /dev/null +++ b/docs/design/diagrams/set_sequence.svg @@ -0,0 +1 @@ +Digital Twin ConsumerDigital Twin ConsumerIn-Vehicle Digital Twin ServiceIn-Vehicle Digital Twin ServiceDigital Twin ProviderDigital Twin Provider1FindById("dtmi:sdv:HVAC:IsAirConditioningActive;1") - request2FindById - responseentityAccessInfo:{name: "IsAirConditioningActive"id: "dtmi:sdv:HVAC:IsAirConditioningActive;1"description: "Is air conditioning active?"endpointInfoList: [{protocol: "grpc"operations: ["Get", "Set", "Subscribe"]uri: "http://127.0.0.1:4000"context: "dtmi:sdv:HVAC:IsAirConditioningActive;1"}]}3Set - request{entity_id: "dtmi:sdv:HVAC:IsAirConditioningActive;1"value:{"IsAirConditioningActive": true"$metadata": {"$model": "dtmi:sdv:HVAC:IsAirConditioningActive;1"}}}4Set - response \ No newline at end of file diff --git a/docs/design/diagrams/subscribe_sequence.puml b/docs/design/diagrams/subscribe_sequence.puml index 135191c4..6703adc3 100644 --- a/docs/design/diagrams/subscribe_sequence.puml +++ b/docs/design/diagrams/subscribe_sequence.puml @@ -1,8 +1,51 @@ @startuml -participant "Provider" -participant "Consumer" +autonumber -"Consumer" -> "Provider" : Subscribe - response (entity id, consumer uri) +"Digital Twin Consumer" -> "In-Vehicle Digital Twin Service" : FindById("dtmi:sdv:HVAC:AmbientAirTemperature;1") - request +"Digital Twin Consumer" <- "In-Vehicle Digital Twin Service" : FindById - response +note left +entityAccessInfo: +{ + name: "AmbientAirTemperature" + id: "dtmi:sdv:HVAC:AmbientAirTemperature;1" + description: "..." + endpointInfoList: [ + { + protocol: "grpc" + operations: ["Get", "Subscribe"] + uri: "http://127.0.0.1:4000" + context: "dtmi:sdv:HVAC:AmbientAirTemperature;1" + } + ] +} +end note + +"Digital Twin Consumer" -> "Digital Twin Provider" : Subscribe - request +note right +{ + entity_id: "dtmi:sdv:HVAC:AmbientAirTemperature;1" + consumer_uri: "http://127.0.0.1:5000" +} +end note + +"Digital Twin Consumer" <- "Digital Twin Provider" : Subscribe - response + +"Digital Twin Consumer" <- "Digital Twin Provider" : Publish - request +note left +{ + entity_id: "dtmi:sdv:HVAC:AmbientAirTemperature;1" + value: + { + "AmbientAirTemperature": 77, + "$metadata": { + "$model": "dtmi:sdv:HVAC:AmbientAirTemperature;1" + } + + } +} +end note + +"Digital Twin Consumer" -> "Digital Twin Provider" : Publish - response @enduml diff --git a/docs/design/diagrams/subscribe_sequence.svg b/docs/design/diagrams/subscribe_sequence.svg index e8cf55a5..a8ed04cf 100644 --- a/docs/design/diagrams/subscribe_sequence.svg +++ b/docs/design/diagrams/subscribe_sequence.svg @@ -1 +1 @@ -ProviderProviderConsumerConsumerSubscribe - response (entity id, consumer uri) \ No newline at end of file +Digital Twin ConsumerDigital Twin ConsumerIn-Vehicle Digital Twin ServiceIn-Vehicle Digital Twin ServiceDigital Twin ProviderDigital Twin Provider1FindById("dtmi:sdv:HVAC:AmbientAirTemperature;1") - request2FindById - responseentityAccessInfo:{name: "AmbientAirTemperature"id: "dtmi:sdv:HVAC:AmbientAirTemperature;1"description: "..."endpointInfoList: [{protocol: "grpc"operations: ["Get", "Subscribe"]uri: "http://127.0.0.1:4000"context: "dtmi:sdv:HVAC:AmbientAirTemperature;1"}]}3Subscribe - request{entity_id: "dtmi:sdv:HVAC:AmbientAirTemperature;1"consumer_uri: "http://127.0.0.1:5000"}4Subscribe - response5Publish - request{entity_id: "dtmi:sdv:HVAC:AmbientAirTemperature;1"value:{"AmbientAirTemperature": 77,"$metadata": {"$model": "dtmi:sdv:HVAC:AmbientAirTemperature;1"} }}6Publish - response \ No newline at end of file diff --git a/dtdl-parser/src/model_parser.rs b/dtdl-parser/src/model_parser.rs index bb5da5d2..86f5185e 100644 --- a/dtdl-parser/src/model_parser.rs +++ b/dtdl-parser/src/model_parser.rs @@ -1163,57 +1163,13 @@ mod model_parser_tests { ); } - #[rustfmt::skip] - #[test] - fn sdv_vehicle_validation_test() { - set_dtdl_path(); - - let mut json_texts = Vec::::new(); - - let vehicle_path_result = ModelParser::find_full_path("v2/content/sdv/vehicle.json"); - assert!(vehicle_path_result.is_ok()); - let vehicle_contents_result = retrieve_dtdl(&vehicle_path_result.unwrap()); - assert!(vehicle_contents_result.is_ok()); - json_texts.push(vehicle_contents_result.unwrap()); - - let mut parser = ModelParser::new(); - let model_dict_result = parser.parse(&json_texts); - assert!( - model_dict_result.is_ok(), - "parse failed due to: {}", - model_dict_result.err().unwrap() - ); - let model_dict = model_dict_result.unwrap(); - - let ambient_air_temperature_id: Option = create_dtmi("dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1"); - assert!(ambient_air_temperature_id.is_some()); - let ambient_air_temperature_entity_result = - model_dict.get(&ambient_air_temperature_id.unwrap()); - assert!(ambient_air_temperature_entity_result.is_some()); - - let is_air_conditioning_active_id: Option = create_dtmi("dtmi:sdv:Vehicle:Cabin:HVAC:IsAirConditioningActive;1"); - assert!(is_air_conditioning_active_id.is_some()); - let is_air_conditioning_active_entity_result = model_dict.get(&is_air_conditioning_active_id.unwrap()); - assert!(is_air_conditioning_active_entity_result.is_some()); - - let hybrid_battery_remaining_id: Option = create_dtmi("dtmi:sdv:Vehicle:OBD:HybridBatteryRemaining;1"); - assert!(hybrid_battery_remaining_id.is_some()); - let hybrid_battery_remaining_entity_result = model_dict.get(&hybrid_battery_remaining_id.unwrap()); - assert!(hybrid_battery_remaining_entity_result.is_some()); - - let show_notification_id: Option = create_dtmi("dtmi:sdv:Vehicle:Cabin:Infotainment:HMI:ShowNotification;1"); - assert!(show_notification_id.is_some()); - let show_notification_entity_result = model_dict.get(&show_notification_id.unwrap()); - assert!(show_notification_entity_result.is_some()); - } - #[test] fn find_full_path_test() { set_dtdl_path(); - let find_full_path_result = ModelParser::find_full_path("v2/content/sdv/vehicle.json"); + let find_full_path_result = ModelParser::find_full_path("v3/content/sdv/hvac.json"); assert!(find_full_path_result.is_ok()); let full_path = find_full_path_result.unwrap(); - assert!(full_path.ends_with("/v2/content/sdv/vehicle.json")); + assert!(full_path.ends_with("/v3/content/sdv/hvac.json")); } } diff --git a/samples/command/Cargo.toml b/samples/command/Cargo.toml index f69c90f2..e6c0ce0e 100644 --- a/samples/command/Cargo.toml +++ b/samples/command/Cargo.toml @@ -17,6 +17,8 @@ parking_lot = { workspace = true } prost = { workspace = true } samples-common = { path = "../common" } samples-protobuf-data-access = { path = "../protobuf_data_access" } +serde = { workspace = true } +serde_derive = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tonic = { workspace = true } diff --git a/samples/command/consumer/src/main.rs b/samples/command/consumer/src/main.rs index a7bb9b43..fcede2df 100644 --- a/samples/command/consumer/src/main.rs +++ b/samples/command/consumer/src/main.rs @@ -4,7 +4,7 @@ mod consumer_impl; -use digital_twin_model::sdv_v1 as sdv; +use digital_twin_model::{sdv_v1 as sdv, Metadata}; use env_logger::{Builder, Target}; use log::{debug, info, warn, LevelFilter}; use samples_common::constants::{digital_twin_operation, digital_twin_protocol}; @@ -13,11 +13,20 @@ use samples_common::utils::{discover_digital_twin_provider_using_ibeji, retrieve use samples_protobuf_data_access::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_server::DigitalTwinConsumerServer; use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::digital_twin_provider_client::DigitalTwinProviderClient; use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::InvokeRequest; +use serde_derive::{Deserialize, Serialize}; use std::net::SocketAddr; use tokio::time::{sleep, Duration}; use tonic::transport::Server; use uuid::Uuid; +#[derive(Debug, Serialize, Deserialize)] +struct ShowNotificationRequestPayload { + #[serde(rename = "Notification")] + notification: sdv::hmi::show_notification::request::TYPE, + #[serde(rename = "$metadata")] + metadata: Metadata, +} + /// Start the show notification repeater. /// /// # Arguments @@ -25,12 +34,18 @@ use uuid::Uuid; /// `consumer_uri` - The consumer_uri. fn start_show_notification_repeater(provider_uri: String, consumer_uri: String) { debug!("Starting the Consumer's show notification repeater."); + + let request_payload: ShowNotificationRequestPayload = ShowNotificationRequestPayload { + notification: "The show-notification request.".to_string(), + metadata: Metadata { model: sdv::hmi::show_notification::request::ID.to_string() }, + }; + + let request_payload_json = serde_json::to_string(&request_payload).unwrap(); + tokio::spawn(async move { loop { - let payload: String = "The show-notification request.".to_string(); - - info!("Sending an invoke request on entity {} with payload '{payload} to provider URI {provider_uri}", - sdv::vehicle::cabin::infotainment::hmi::show_notification::ID); + info!("Sending an invoke request on entity {} with payload '{}' to provider URI {provider_uri}", + sdv::hmi::show_notification::ID, &request_payload_json); let client_result = DigitalTwinProviderClient::connect(provider_uri.clone()).await; if client_result.is_err() { @@ -43,11 +58,10 @@ fn start_show_notification_repeater(provider_uri: String, consumer_uri: String) let response_id = Uuid::new_v4().to_string(); let request = tonic::Request::new(InvokeRequest { - entity_id: sdv::vehicle::cabin::infotainment::hmi::show_notification::ID - .to_string(), + entity_id: sdv::hmi::show_notification::ID.to_string(), consumer_uri: consumer_uri.clone(), response_id, - payload, + payload: request_payload_json.to_string(), }); let response = client.invoke(request).await; @@ -89,7 +103,7 @@ async fn main() -> Result<(), Box> { let provider_endpoint_info = discover_digital_twin_provider_using_ibeji( &invehicle_digital_twin_url, - sdv::vehicle::cabin::infotainment::hmi::show_notification::ID, + sdv::hmi::show_notification::ID, digital_twin_protocol::GRPC, &[digital_twin_operation::INVOKE.to_string()], ) diff --git a/samples/command/provider/src/main.rs b/samples/command/provider/src/main.rs index 4e5c650f..7bfb4ef7 100644 --- a/samples/command/provider/src/main.rs +++ b/samples/command/provider/src/main.rs @@ -7,7 +7,6 @@ mod provider_impl; use digital_twin_model::sdv_v1 as sdv; use env_logger::{Builder, Target}; use log::{debug, info, LevelFilter}; -use parking_lot::Mutex; use samples_common::constants::{digital_twin_operation, digital_twin_protocol}; use samples_common::utils::{retrieve_invehicle_digital_twin_url, retry_async_based_on_status}; use samples_common::provider_config; @@ -15,11 +14,10 @@ use samples_protobuf_data_access::digital_twin::v1::digital_twin_client::Digital use samples_protobuf_data_access::digital_twin::v1::{EndpointInfo, EntityAccessInfo, RegisterRequest}; use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::digital_twin_provider_server::DigitalTwinProviderServer; use std::net::SocketAddr; -use std::sync::Arc; use tokio::time::Duration; use tonic::{Status, transport::Server}; -use crate::provider_impl::{ProviderImpl, SubscriptionMap}; +use crate::provider_impl::ProviderImpl; /// Register the show notification command's endpoint. /// @@ -34,13 +32,13 @@ async fn register_show_notification( protocol: digital_twin_protocol::GRPC.to_string(), operations: vec![digital_twin_operation::INVOKE.to_string()], uri: provider_uri.to_string(), - context: sdv::vehicle::cabin::infotainment::hmi::show_notification::ID.to_string(), + context: sdv::hmi::show_notification::ID.to_string(), }; let entity_access_info = EntityAccessInfo { - name: "ShowNotification".to_string(), - id: sdv::vehicle::cabin::infotainment::hmi::show_notification::ID.to_string(), - description: "Show a notification on the HMI.".to_string(), + name: sdv::hmi::show_notification::NAME.to_string(), + id: sdv::hmi::show_notification::ID.to_string(), + description: sdv::hmi::show_notification::DESCRIPTION.to_string(), endpoint_info_list: vec![endpoint_info], }; @@ -76,8 +74,7 @@ async fn main() -> Result<(), Box> { // Setup the HTTP server. let addr: SocketAddr = provider_authority.parse()?; - let subscription_map = Arc::new(Mutex::new(SubscriptionMap::new())); - let provider_impl = ProviderImpl { subscription_map: subscription_map.clone() }; + let provider_impl = ProviderImpl {}; let server_future = Server::builder().add_service(DigitalTwinProviderServer::new(provider_impl)).serve(addr); info!("The HTTP server is listening on address '{provider_authority}'"); diff --git a/samples/command/provider/src/provider_impl.rs b/samples/command/provider/src/provider_impl.rs index 57b5061f..0fe474aa 100644 --- a/samples/command/provider/src/provider_impl.rs +++ b/samples/command/provider/src/provider_impl.rs @@ -2,8 +2,8 @@ // Licensed under the MIT license. // SPDX-License-Identifier: MIT -use log::{info, warn}; -use parking_lot::Mutex; +use digital_twin_model::sdv_v1 as sdv; +use log::{debug, info, warn}; use samples_protobuf_data_access::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_client::DigitalTwinConsumerClient; use samples_protobuf_data_access::sample_grpc::v1::digital_twin_consumer::RespondRequest; use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::digital_twin_provider_server::DigitalTwinProvider; @@ -11,16 +11,15 @@ use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::{ GetRequest, GetResponse, InvokeRequest, InvokeResponse, SetRequest, SetResponse, SubscribeRequest, SubscribeResponse, UnsubscribeRequest, UnsubscribeResponse, }; -use std::collections::{HashMap, HashSet}; -use std::sync::Arc; +use serde_derive::{Deserialize, Serialize}; use tonic::{Request, Response, Status}; -pub type SubscriptionMap = HashMap>; +/// The reponse payload is empty. +#[derive(Debug, Serialize, Deserialize)] +struct ResponsePayload {} #[derive(Debug, Default)] -pub struct ProviderImpl { - pub subscription_map: Arc>, -} +pub struct ProviderImpl {} #[tonic::async_trait] impl DigitalTwinProvider for ProviderImpl { @@ -78,32 +77,39 @@ impl DigitalTwinProvider for ProviderImpl { &self, request: Request, ) -> Result, Status> { - let request_inner = request.into_inner(); - let entity_id: String = request_inner.entity_id.clone(); - let response_id: String = request_inner.response_id.clone(); - let consumer_uri: String = request_inner.consumer_uri; - let payload: String = request_inner.payload; - - info!( - "Received an invoke request from for entity id {entity_id} with payload '{payload}' from consumer URI {consumer_uri}" + let InvokeRequest { entity_id, response_id, consumer_uri, payload } = request.into_inner(); + + let request_payload_json: serde_json::Value = serde_json::from_str(&payload) + .map_err(|error| Status::invalid_argument(error.to_string()))?; + let notification_json = + request_payload_json.get(sdv::hmi::show_notification::request::NAME).unwrap(); + + let notification: sdv::hmi::show_notification::request::TYPE = + serde_json::from_value(notification_json.clone()).unwrap(); + + debug!( + "Received an invoke request from for entity id {entity_id} with payload'{payload}' from consumer URI {consumer_uri}" ); - info!("Notification: '{payload}'"); + info!("Notification: '{notification}'"); tokio::spawn(async move { let mut client = DigitalTwinConsumerClient::connect(consumer_uri.clone()) .await .map_err(|error| Status::internal(error.to_string()))?; + let response_payload = ResponsePayload {}; + let response_payload_json = serde_json::to_string(&response_payload).unwrap(); + let respond_request = tonic::Request::new(RespondRequest { - entity_id: entity_id.clone(), + entity_id: sdv::hmi::show_notification::response::ID.to_string(), response_id, - payload, + payload: response_payload_json, }); let response_future = client.respond(respond_request).await; - info!( + debug!( "Sent an invoke response to consumer URI {} for entity id {}", &consumer_uri, &entity_id ); @@ -120,21 +126,38 @@ impl DigitalTwinProvider for ProviderImpl { #[cfg(test)] mod provider_impl_tests { use super::*; + use digital_twin_model::Metadata; use uuid::Uuid; + #[derive(Debug, Serialize, Deserialize)] + struct ShowNotificationRequestPayload { + #[serde(rename = "Notification")] + notification: sdv::hmi::show_notification::request::TYPE, + #[serde(rename = "$metadata")] + metadata: Metadata, + } + #[tokio::test] async fn invoke_test() { - let subscription_map = Arc::new(Mutex::new(HashMap::new())); - let provider_impl = ProviderImpl { subscription_map }; + let provider_impl = ProviderImpl {}; let entity_id = String::from("one-id"); let consumer_uri = String::from("bogus uri"); let response_id = Uuid::new_v4().to_string(); - let payload = String::from("some-payload"); - let request = - tonic::Request::new(InvokeRequest { entity_id, consumer_uri, response_id, payload }); + let request_payload: ShowNotificationRequestPayload = ShowNotificationRequestPayload { + notification: "The show-notification request.".to_string(), + metadata: Metadata { model: sdv::hmi::show_notification::request::ID.to_string() }, + }; + let request_payload_json = serde_json::to_string(&request_payload).unwrap(); + + let request = tonic::Request::new(InvokeRequest { + entity_id, + consumer_uri, + response_id, + payload: request_payload_json, + }); let result = provider_impl.invoke(request).await; assert!(result.is_ok()); diff --git a/samples/mixed/Cargo.toml b/samples/mixed/Cargo.toml index 006f68da..6d37a752 100644 --- a/samples/mixed/Cargo.toml +++ b/samples/mixed/Cargo.toml @@ -17,6 +17,8 @@ parking_lot = { workspace = true } prost = { workspace = true } samples-common = { path = "../common" } samples-protobuf-data-access = { path = "../protobuf_data_access" } +serde = { workspace = true } +serde_derive = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tonic = { workspace = true } diff --git a/samples/mixed/consumer/src/main.rs b/samples/mixed/consumer/src/main.rs index e4c182b5..82a560aa 100644 --- a/samples/mixed/consumer/src/main.rs +++ b/samples/mixed/consumer/src/main.rs @@ -4,7 +4,7 @@ mod consumer_impl; -use digital_twin_model::sdv_v1 as sdv; +use digital_twin_model::{sdv_v1 as sdv, Metadata}; use env_logger::{Builder, Target}; use log::{debug, info, warn, LevelFilter}; use samples_common::constants::{digital_twin_operation, digital_twin_protocol}; @@ -15,11 +15,28 @@ use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::digita use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::{ InvokeRequest, SetRequest, SubscribeRequest, }; +use serde_derive::{Deserialize, Serialize}; use std::net::SocketAddr; use tokio::time::{sleep, Duration}; use tonic::{Status, transport::Server}; use uuid::Uuid; +#[derive(Debug, Serialize, Deserialize)] +struct IsAirConditioningActiveProperty { + #[serde(rename = "IsAirConditioningActive")] + is_air_conditioning_active: sdv::hvac::is_air_conditioning_active::TYPE, + #[serde(rename = "$metadata")] + metadata: Metadata, +} + +#[derive(Debug, Serialize, Deserialize)] +struct ShowNotificationRequestPayload { + #[serde(rename = "Notification")] + notification: sdv::hmi::show_notification::request::TYPE, + #[serde(rename = "$metadata")] + metadata: Metadata, +} + /// Start the show-notification repeater. /// /// # Arguments @@ -27,12 +44,20 @@ use uuid::Uuid; /// `consumer_uri` - The consumer_uri. fn start_show_notification_repeater(provider_uri: String, consumer_uri: String) { debug!("Starting the Consumer's show-notification repeater."); + tokio::spawn(async move { - loop { - let payload: String = "show-notification request".to_string(); + let metadata = Metadata { model: sdv::hmi::show_notification::request::ID.to_string() }; + + let request_payload: ShowNotificationRequestPayload = ShowNotificationRequestPayload { + notification: "The show-notification request.".to_string(), + metadata, + }; - info!("Sending an invoke request on entity {} with payload '{payload} to provider URI {provider_uri}", - sdv::vehicle::cabin::infotainment::hmi::show_notification::ID); + let request_payload_json = serde_json::to_string(&request_payload).unwrap(); + + loop { + info!("Sending an invoke request on entity {} with payload '{} to provider URI {provider_uri}", + sdv::hmi::show_notification::ID, &request_payload_json); let client_result = DigitalTwinProviderClient::connect(provider_uri.clone()).await; if client_result.is_err() { @@ -45,11 +70,10 @@ fn start_show_notification_repeater(provider_uri: String, consumer_uri: String) let response_id = Uuid::new_v4().to_string(); let request = tonic::Request::new(InvokeRequest { - entity_id: sdv::vehicle::cabin::infotainment::hmi::show_notification::ID - .to_string(), + entity_id: sdv::hmi::show_notification::ID.to_string(), consumer_uri: consumer_uri.clone(), response_id, - payload, + payload: request_payload_json.to_string(), }); let response = client.invoke(request).await; @@ -71,11 +95,20 @@ fn start_show_notification_repeater(provider_uri: String, consumer_uri: String) /// `provider_uri` - The provider_uri. fn start_activate_air_conditioning_repeater(provider_uri: String) { debug!("Starting the Consumer's activate-air-conditioning repeater."); + tokio::spawn(async move { let mut is_active = true; + loop { info!("Sending a set request for entity id {} to the value '{is_active}' to provider URI {provider_uri}", - sdv::vehicle::cabin::hvac::is_air_conditioning_active::ID); + sdv::hvac::is_air_conditioning_active::ID); + + let metadata: Metadata = + Metadata { model: sdv::hvac::is_air_conditioning_active::ID.to_string() }; + let property: IsAirConditioningActiveProperty = + IsAirConditioningActiveProperty { is_air_conditioning_active: is_active, metadata }; + + let value = serde_json::to_string_pretty(&property).unwrap(); let client_result = DigitalTwinProviderClient::connect(provider_uri.clone()).await; if client_result.is_err() { @@ -85,10 +118,8 @@ fn start_activate_air_conditioning_repeater(provider_uri: String) { } let mut client = client_result.unwrap(); - let value: String = format!("{is_active}"); - let request = tonic::Request::new(SetRequest { - entity_id: sdv::vehicle::cabin::hvac::is_air_conditioning_active::ID.to_string(), + entity_id: sdv::hvac::is_air_conditioning_active::ID.to_string(), value, }); @@ -158,7 +189,7 @@ async fn main() -> Result<(), Box> { let show_notification_command_provider_endpoint_info = discover_digital_twin_provider_using_ibeji( &invehicle_digital_twin_url, - sdv::vehicle::cabin::infotainment::hmi::show_notification::ID, + sdv::hmi::show_notification::ID, digital_twin_protocol::GRPC, &[digital_twin_operation::INVOKE.to_string()], ) @@ -170,7 +201,7 @@ async fn main() -> Result<(), Box> { let ambient_air_temperature_property_provider_endpoint_info = discover_digital_twin_provider_using_ibeji( &invehicle_digital_twin_url, - sdv::vehicle::cabin::hvac::ambient_air_temperature::ID, + sdv::hvac::ambient_air_temperature::ID, digital_twin_protocol::GRPC, &[digital_twin_operation::SUBSCRIBE.to_string()], ) @@ -182,7 +213,7 @@ async fn main() -> Result<(), Box> { let is_air_conditioning_active_property_provider_endpoint_info = discover_digital_twin_provider_using_ibeji( &invehicle_digital_twin_url, - sdv::vehicle::cabin::hvac::is_air_conditioning_active::ID, + sdv::hvac::is_air_conditioning_active::ID, digital_twin_protocol::GRPC, &[ digital_twin_operation::SUBSCRIBE.to_string(), @@ -197,7 +228,7 @@ async fn main() -> Result<(), Box> { let hybrid_battery_remaining_property_provider_endpoint_info = discover_digital_twin_provider_using_ibeji( &invehicle_digital_twin_url, - sdv::vehicle::obd::hybrid_battery_remaining::ID, + sdv::obd::hybrid_battery_remaining::ID, digital_twin_protocol::GRPC, &[digital_twin_operation::SUBSCRIBE.to_string()], ) @@ -209,7 +240,7 @@ async fn main() -> Result<(), Box> { retry_async_based_on_status(30, Duration::from_secs(1), || { send_subscribe_request( &ambient_air_temperature_property_provider_uri, - sdv::vehicle::cabin::hvac::ambient_air_temperature::ID, + sdv::hvac::ambient_air_temperature::ID, &consumer_uri, ) }) @@ -218,7 +249,7 @@ async fn main() -> Result<(), Box> { retry_async_based_on_status(30, Duration::from_secs(1), || { send_subscribe_request( &is_air_conditioning_active_property_provider_uri, - sdv::vehicle::cabin::hvac::is_air_conditioning_active::ID, + sdv::hvac::is_air_conditioning_active::ID, &consumer_uri, ) }) @@ -227,7 +258,7 @@ async fn main() -> Result<(), Box> { retry_async_based_on_status(30, Duration::from_secs(1), || { send_subscribe_request( &hybrid_battery_remaining_property_provider_uri, - sdv::vehicle::obd::hybrid_battery_remaining::ID, + sdv::obd::hybrid_battery_remaining::ID, &consumer_uri, ) }) diff --git a/samples/mixed/provider/src/main.rs b/samples/mixed/provider/src/main.rs index b56ab512..50f918a3 100644 --- a/samples/mixed/provider/src/main.rs +++ b/samples/mixed/provider/src/main.rs @@ -5,7 +5,7 @@ mod provider_impl; mod vehicle; -use digital_twin_model::sdv_v1 as sdv; +use digital_twin_model::{sdv_v1 as sdv, Metadata}; use env_logger::{Builder, Target}; use log::{debug, info, warn, LevelFilter}; use parking_lot::{Mutex, MutexGuard}; @@ -17,6 +17,7 @@ use samples_protobuf_data_access::digital_twin::v1::{EndpointInfo, EntityAccessI use samples_protobuf_data_access::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_client::DigitalTwinConsumerClient; use samples_protobuf_data_access::sample_grpc::v1::digital_twin_consumer::PublishRequest; use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::digital_twin_provider_server::DigitalTwinProviderServer; +use serde_derive::{Deserialize, Serialize}; use std::collections::HashSet; use std::net::SocketAddr; use std::sync::Arc; @@ -26,6 +27,30 @@ use tonic::{Status, transport::Server}; use crate::provider_impl::{ProviderImpl, SubscriptionMap}; use crate::vehicle::Vehicle; +#[derive(Debug, Serialize, Deserialize)] +struct AmbientAirTemperatureProperty { + #[serde(rename = "AmbientAirTemperature")] + ambient_air_temperature: sdv::hvac::ambient_air_temperature::TYPE, + #[serde(rename = "$metadata")] + metadata: Metadata, +} + +#[derive(Debug, Serialize, Deserialize)] +struct HybridBatteryRemainingProperty { + #[serde(rename = "HybridBatteryRemainaing")] + hybrid_battery_remaining: sdv::obd::hybrid_battery_remaining::TYPE, + #[serde(rename = "$metadata")] + metadata: Metadata, +} + +#[derive(Debug, Serialize, Deserialize)] +struct IsAirConditioingActiveProperty { + #[serde(rename = "IsAirConditioingActive")] + is_air_conditioning_active: sdv::hvac::is_air_conditioning_active::TYPE, + #[serde(rename = "$metadata")] + metadata: Metadata, +} + /// Register the entities endpoints. /// /// # Arguments @@ -38,17 +63,14 @@ async fn register_entities( // AmbientAirTemperature let ambient_air_temperature_endpoint_info = EndpointInfo { protocol: digital_twin_protocol::GRPC.to_string(), - operations: vec![ - digital_twin_operation::SUBSCRIBE.to_string(), - digital_twin_operation::UNSUBSCRIBE.to_string(), - ], + operations: vec![digital_twin_operation::SUBSCRIBE.to_string()], uri: provider_uri.to_string(), - context: sdv::vehicle::cabin::hvac::ambient_air_temperature::ID.to_string(), + context: sdv::hvac::ambient_air_temperature::ID.to_string(), }; let ambient_air_temperature_access_info = EntityAccessInfo { - name: "AmbientAirTemperature".to_string(), - id: sdv::vehicle::cabin::hvac::ambient_air_temperature::ID.to_string(), - description: "The immediate surroundings air temperature (in Fahrenheit).".to_string(), + name: sdv::hvac::ambient_air_temperature::NAME.to_string(), + id: sdv::hvac::ambient_air_temperature::ID.to_string(), + description: sdv::hvac::ambient_air_temperature::DESCRIPTION.to_string(), endpoint_info_list: vec![ambient_air_temperature_endpoint_info], }; @@ -57,33 +79,29 @@ async fn register_entities( protocol: digital_twin_protocol::GRPC.to_string(), operations: vec![ digital_twin_operation::SUBSCRIBE.to_string(), - digital_twin_operation::UNSUBSCRIBE.to_string(), digital_twin_operation::SET.to_string(), ], uri: provider_uri.to_string(), - context: sdv::vehicle::cabin::hvac::ambient_air_temperature::ID.to_string(), + context: sdv::hvac::is_air_conditioning_active::ID.to_string(), }; let is_air_conditioning_active_access_info = EntityAccessInfo { - name: "IsAirConditioningActive".to_string(), - id: sdv::vehicle::cabin::hvac::is_air_conditioning_active::ID.to_string(), - description: "Is air conditioning active?".to_string(), + name: sdv::hvac::is_air_conditioning_active::NAME.to_string(), + id: sdv::hvac::is_air_conditioning_active::ID.to_string(), + description: sdv::hvac::is_air_conditioning_active::DESCRIPTION.to_string(), endpoint_info_list: vec![is_air_conditioning_active_endpoint_info], }; // HybridBatteryRemaining let hybrid_battery_remaining_endpoint_info = EndpointInfo { protocol: digital_twin_protocol::GRPC.to_string(), - operations: vec![ - digital_twin_operation::SUBSCRIBE.to_string(), - digital_twin_operation::UNSUBSCRIBE.to_string(), - ], + operations: vec![digital_twin_operation::SUBSCRIBE.to_string()], uri: provider_uri.to_string(), - context: sdv::vehicle::obd::hybrid_battery_remaining::ID.to_string(), + context: sdv::obd::hybrid_battery_remaining::ID.to_string(), }; let hybrid_battery_remaining_access_info = EntityAccessInfo { - name: "HybridBatteryRemaining".to_string(), - id: sdv::vehicle::obd::hybrid_battery_remaining::ID.to_string(), - description: "The remaining hybrid battery life.".to_string(), + name: sdv::obd::hybrid_battery_remaining::NAME.to_string(), + id: sdv::obd::hybrid_battery_remaining::ID.to_string(), + description: sdv::obd::hybrid_battery_remaining::DESCRIPTION.to_string(), endpoint_info_list: vec![hybrid_battery_remaining_endpoint_info], }; @@ -92,12 +110,12 @@ async fn register_entities( protocol: digital_twin_protocol::GRPC.to_string(), operations: vec![digital_twin_operation::INVOKE.to_string()], uri: provider_uri.to_string(), - context: sdv::vehicle::cabin::infotainment::hmi::show_notification::ID.to_string(), + context: sdv::hmi::show_notification::ID.to_string(), }; let show_notification_access_info = EntityAccessInfo { - name: "ShowNotification".to_string(), - id: sdv::vehicle::cabin::infotainment::hmi::show_notification::ID.to_string(), - description: "Show a notification on the HMI.".to_string(), + name: sdv::hmi::show_notification::NAME.to_string(), + id: sdv::hmi::show_notification::ID.to_string(), + description: sdv::hmi::show_notification::DESCRIPTION.to_string(), endpoint_info_list: vec![show_notification_endpoint_info], }; @@ -182,22 +200,43 @@ async fn start_vehicle_simulator( } info!("Publishing the values: Ambient air temperature is {ambient_air_temperature}; Is air conditioning active is {is_air_conditioning_active}; Hybrid battery remaining is {hybrid_battery_remaining}"); + let ambient_air_temperature_property: AmbientAirTemperatureProperty = + AmbientAirTemperatureProperty { + ambient_air_temperature, + metadata: Metadata { + model: sdv::hvac::ambient_air_temperature::ID.to_string(), + }, + }; publish( subscription_map.clone(), - sdv::vehicle::cabin::hvac::ambient_air_temperature::ID, - &ambient_air_temperature.to_string(), + sdv::hvac::ambient_air_temperature::ID, + &serde_json::to_string(&ambient_air_temperature_property).unwrap(), ) .await; + let is_air_conditioning_active_property: IsAirConditioingActiveProperty = + IsAirConditioingActiveProperty { + is_air_conditioning_active, + metadata: Metadata { + model: sdv::hvac::is_air_conditioning_active::ID.to_string(), + }, + }; publish( subscription_map.clone(), - sdv::vehicle::cabin::hvac::is_air_conditioning_active::ID, - &is_air_conditioning_active.to_string(), + sdv::hvac::is_air_conditioning_active::ID, + &serde_json::to_string(&is_air_conditioning_active_property).unwrap(), ) .await; + let hybrid_battery_remaining_property: HybridBatteryRemainingProperty = + HybridBatteryRemainingProperty { + hybrid_battery_remaining, + metadata: Metadata { + model: sdv::obd::hybrid_battery_remaining::ID.to_string(), + }, + }; publish( subscription_map.clone(), - sdv::vehicle::obd::hybrid_battery_remaining::ID, - &hybrid_battery_remaining.to_string(), + sdv::obd::hybrid_battery_remaining::ID, + &serde_json::to_string(&hybrid_battery_remaining_property).unwrap(), ) .await; diff --git a/samples/mixed/provider/src/provider_impl.rs b/samples/mixed/provider/src/provider_impl.rs index 2df58737..e6bd251c 100644 --- a/samples/mixed/provider/src/provider_impl.rs +++ b/samples/mixed/provider/src/provider_impl.rs @@ -13,7 +13,6 @@ use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::{ SubscribeRequest, SubscribeResponse, UnsubscribeRequest, UnsubscribeResponse, }; use std::collections::{HashMap, HashSet}; -use std::str::FromStr; use std::sync::Arc; use tonic::{Request, Response, Status}; @@ -30,14 +29,13 @@ pub struct ProviderImpl { impl ProviderImpl { fn set_is_air_conditioning_active( vehicle: Arc>, - value: &str, + value: bool, ) -> Result<(), String> { // This block controls the lifetime of the lock. { let mut lock: MutexGuard = vehicle.lock(); - lock.is_air_conditioning_active = - FromStr::from_str(value).map_err(|error| format!("{error:?}"))?; + lock.is_air_conditioning_active = value; } Ok(()) @@ -117,13 +115,25 @@ impl DigitalTwinProvider for ProviderImpl { let entity_id: String = request_inner.entity_id.clone(); let value: String = request_inner.value; + let is_air_conditioing_active_property_json: serde_json::Value = + serde_json::from_str(&value) + .map_err(|error| Status::invalid_argument(error.to_string()))?; + let is_air_conditioning_active_json = is_air_conditioing_active_property_json + .get(sdv::hvac::is_air_conditioning_active::NAME) + .unwrap(); + let is_air_conditioning_active: sdv::hvac::is_air_conditioning_active::TYPE = + serde_json::from_value(is_air_conditioning_active_json.clone()).unwrap(); + info!("Received a set request for entity id {entity_id} with value '{value}'"); let vehicle: Arc> = self.vehicle.clone(); tokio::spawn(async move { - if entity_id == sdv::vehicle::cabin::hvac::is_air_conditioning_active::ID { - let result = ProviderImpl::set_is_air_conditioning_active(vehicle.clone(), &value); + if entity_id == sdv::hvac::is_air_conditioning_active::ID { + let result = ProviderImpl::set_is_air_conditioning_active( + vehicle.clone(), + is_air_conditioning_active, + ); if result.is_err() { warn!("Failed to set {} due to: {}", entity_id, result.unwrap_err()); } @@ -162,7 +172,7 @@ impl DigitalTwinProvider for ProviderImpl { tokio::spawn(async move { let mut response_payload: String = format!("Successfully invoked {entity_id}"); - if entity_id == sdv::vehicle::cabin::infotainment::hmi::show_notification::ID { + if entity_id == sdv::hmi::show_notification::ID { ProviderImpl::show_notification(&payload); } else { response_payload = format!("Error: The entity id {entity_id} is not recognized."); diff --git a/samples/property/Cargo.toml b/samples/property/Cargo.toml index c512a56d..3fee8b06 100644 --- a/samples/property/Cargo.toml +++ b/samples/property/Cargo.toml @@ -17,6 +17,8 @@ parking_lot = { workspace = true } prost = { workspace = true } samples-common = { path = "../common" } samples-protobuf-data-access = { path = "../protobuf_data_access" } +serde = { workspace = true } +serde_derive = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tonic = { workspace = true } diff --git a/samples/property/consumer/src/main.rs b/samples/property/consumer/src/main.rs index 96bc94b5..c2dfa481 100644 --- a/samples/property/consumer/src/main.rs +++ b/samples/property/consumer/src/main.rs @@ -30,7 +30,7 @@ async fn subscribe_to_ambient_air_temperature( .await .map_err(|e| Status::internal(e.to_string()))?; let request = tonic::Request::new(SubscribeRequest { - entity_id: sdv::vehicle::cabin::hvac::ambient_air_temperature::ID.to_string(), + entity_id: sdv::hvac::ambient_air_temperature::ID.to_string(), consumer_uri: consumer_uri.to_string(), }); let _response = client.subscribe(request).await?; @@ -65,7 +65,7 @@ async fn main() -> Result<(), Box> { // Retrieve the provider URI. let provider_endpoint_info = discover_digital_twin_provider_using_ibeji( &invehicle_digital_twin_url, - sdv::vehicle::cabin::hvac::ambient_air_temperature::ID, + sdv::hvac::ambient_air_temperature::ID, digital_twin_protocol::GRPC, &[digital_twin_operation::SUBSCRIBE.to_string()], ) @@ -79,7 +79,7 @@ async fn main() -> Result<(), Box> { info!( "Sending a subscribe request for entity id {} to provider URI {provider_uri}", - sdv::vehicle::cabin::hvac::ambient_air_temperature::ID + sdv::hvac::ambient_air_temperature::ID ); retry_async_based_on_status(30, Duration::from_secs(1), || { subscribe_to_ambient_air_temperature(&provider_uri, &consumer_uri) diff --git a/samples/property/provider/src/main.rs b/samples/property/provider/src/main.rs index f02c74cc..6fb4e994 100644 --- a/samples/property/provider/src/main.rs +++ b/samples/property/provider/src/main.rs @@ -4,7 +4,7 @@ mod provider_impl; -use digital_twin_model::sdv_v1 as sdv; +use digital_twin_model::{sdv_v1 as sdv, Metadata}; use env_logger::{Builder, Target}; use log::{debug, info, warn, LevelFilter}; use parking_lot::{Mutex, MutexGuard}; @@ -16,6 +16,7 @@ use samples_protobuf_data_access::digital_twin::v1::{EndpointInfo, EntityAccessI use samples_protobuf_data_access::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_client::DigitalTwinConsumerClient; use samples_protobuf_data_access::sample_grpc::v1::digital_twin_consumer::PublishRequest; use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::digital_twin_provider_server::DigitalTwinProviderServer; +use serde_derive::{Deserialize, Serialize}; use std::collections::HashSet; use std::net::SocketAddr; use std::sync::Arc; @@ -24,6 +25,14 @@ use tonic::{Status, transport::Server}; use crate::provider_impl::{ProviderImpl, SubscriptionMap}; +#[derive(Debug, Serialize, Deserialize)] +struct Property { + #[serde(rename = "AmbientAirTemperature")] + ambient_air_temperature: sdv::hvac::ambient_air_temperature::TYPE, + #[serde(rename = "$metadata")] + metadata: Metadata, +} + /// Register the ambient air temperature property's endpoint. /// /// # Arguments @@ -35,18 +44,15 @@ async fn register_ambient_air_temperature( ) -> Result<(), Status> { let endpoint_info = EndpointInfo { protocol: digital_twin_protocol::GRPC.to_string(), - operations: vec![ - digital_twin_operation::SUBSCRIBE.to_string(), - digital_twin_operation::UNSUBSCRIBE.to_string(), - ], + operations: vec![digital_twin_operation::SUBSCRIBE.to_string()], uri: provider_uri.to_string(), - context: sdv::vehicle::cabin::hvac::ambient_air_temperature::ID.to_string(), + context: sdv::hvac::ambient_air_temperature::ID.to_string(), }; let entity_access_info = EntityAccessInfo { - name: "AmbientAirTemperature".to_string(), - id: sdv::vehicle::cabin::hvac::ambient_air_temperature::ID.to_string(), - description: "The immediate surroundings air temperature (in Fahrenheit).".to_string(), + name: sdv::hvac::ambient_air_temperature::NAME.to_string(), + id: sdv::hvac::ambient_air_temperature::ID.to_string(), + description: sdv::hvac::ambient_air_temperature::DESCRIPTION.to_string(), endpoint_info_list: vec![endpoint_info], }; @@ -60,6 +66,18 @@ async fn register_ambient_air_temperature( Ok(()) } +/// Create the JSON for the ambient air temperature property. +/// +/// # Arguments +/// * `ambient_air_temperature` - The ambient air temperature value. +fn create_property(ambient_air_temperature: i32) -> String { + let metadata = Metadata { model: sdv::hvac::ambient_air_temperature::ID.to_string() }; + + let property: Property = Property { ambient_air_temperature, metadata }; + + serde_json::to_string(&property).unwrap() +} + /// Start the ambient air temperature data stream. /// /// # Arguments @@ -67,7 +85,7 @@ async fn register_ambient_air_temperature( fn start_ambient_air_temperature_data_stream(subscription_map: Arc>) { debug!("Starting the Provider's ambient air temperature data stream."); tokio::spawn(async move { - let mut temperature: u32 = 75; + let mut temperature: i32 = 75; let mut is_temperature_increasing: bool = true; loop { let urls; @@ -75,16 +93,18 @@ fn start_ambient_air_temperature_data_stream(subscription_map: Arc = subscription_map.lock(); - let get_result = lock.get(sdv::vehicle::cabin::hvac::ambient_air_temperature::ID); + let get_result = lock.get(sdv::hvac::ambient_air_temperature::ID); urls = match get_result { Some(val) => val.clone(), None => HashSet::new(), }; } + let property = create_property(temperature); + for url in urls { info!("Sending a publish request for {} with value {temperature} to consumer URI {url}", - sdv::vehicle::cabin::hvac::ambient_air_temperature::ID); + sdv::hvac::ambient_air_temperature::ID); let client_result = DigitalTwinConsumerClient::connect(url).await; if client_result.is_err() { @@ -95,8 +115,8 @@ fn start_ambient_air_temperature_data_stream(subscription_map: Arc, + ) -> Result, Status> { + let PublishRequest { entity_id, value } = request.into_inner(); + let value_json: serde_json::Value = serde_json::from_str(&value) + .map_err(|error| Status::invalid_argument(error.to_string()))?; + let massage_airbags_json = + value_json.get(sdv::airbag_seat_massager::massage_airbags::NAME).unwrap(); + + let massage_airbags: sdv::airbag_seat_massager::massage_airbags::TYPE = + serde_json::from_value(massage_airbags_json.clone()).unwrap(); + + info!("Received a publish for entity id {entity_id} with the value: {massage_airbags:?}"); + + let response = PublishResponse {}; + + Ok(Response::new(response)) + } + + /// Respond implementation. + /// + /// # Arguments + /// * `request` - Respond request. + async fn respond( + &self, + request: Request, + ) -> Result, Status> { + warn!("Got a respond request: {request:?}"); + + Err(Status::unimplemented("respond has not been implemented")) + } +} + +#[cfg(test)] +mod consumer_impl_tests { + use super::*; + use digital_twin_model::Metadata; + use serde_derive::{Deserialize, Serialize}; + + #[derive(Debug, Serialize, Deserialize)] + struct Property { + #[serde(rename = "MassageAirbags")] + massage_airbags: sdv::airbag_seat_massager::massage_airbags::TYPE, + #[serde(rename = "$metadata")] + metadata: Metadata, + } + + #[derive(Debug, Serialize, Deserialize)] + struct ResponsePayload {} + + #[tokio::test] + async fn publish_test() { + let consumer_impl = ConsumerImpl {}; + + let entity_id = String::from("some-entity-id"); + + let property: Property = Property { + massage_airbags: Vec::new(), + metadata: Metadata { + model: sdv::airbag_seat_massager::massage_airbags::ID.to_string(), + }, + }; + let property_json = serde_json::to_string(&property).unwrap(); + + let request = tonic::Request::new(PublishRequest { entity_id, value: property_json }); + let result = consumer_impl.publish(request).await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn respond_test() { + let consumer_impl = ConsumerImpl {}; + + let entity_id = String::from("some-entity-id"); + let response_id = String::from("some-response-id"); + + let response_payload: ResponsePayload = ResponsePayload {}; + let response_payload_json = serde_json::to_string(&response_payload).unwrap(); + + let request = tonic::Request::new(RespondRequest { + entity_id, + response_id, + payload: response_payload_json, + }); + let result = consumer_impl.respond(request).await; + assert!(result.is_err()); + } +} diff --git a/samples/seat_massager/consumer/src/main.rs b/samples/seat_massager/consumer/src/main.rs new file mode 100644 index 00000000..63c2c256 --- /dev/null +++ b/samples/seat_massager/consumer/src/main.rs @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +mod consumer_impl; + +use digital_twin_model::{sdv_v1 as sdv, Metadata}; +use env_logger::{Builder, Target}; +use log::{debug, info, LevelFilter, warn}; +use samples_common::constants::{digital_twin_operation, digital_twin_protocol}; +use samples_common::utils::{discover_digital_twin_provider_using_ibeji, retrieve_invehicle_digital_twin_url}; +use samples_common::consumer_config; +use samples_protobuf_data_access::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_server::DigitalTwinConsumerServer; +use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::digital_twin_provider_client::DigitalTwinProviderClient; +use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::{GetRequest, SetRequest}; +use serde_derive::{Deserialize, Serialize}; +use std::cmp::max; +use std::net::SocketAddr; +use tokio::time::{sleep, Duration}; +use tonic::transport::Server; + +#[derive(Debug, Serialize, Deserialize)] +struct Property { + #[serde(rename = "MassageAirbags")] + massage_airbags: sdv::airbag_seat_massager::massage_airbags::TYPE, + #[serde(rename = "$metadata")] + metadata: Metadata, +} + +/// Start the seat massage sequence. +/// +/// # Arguments +/// `provider_uri` - The provider uri. +fn start_seat_massage_sequence(provider_uri: String) { + debug!("Starting the consumer's seat massage sequence."); + + let mut crest_row: i8 = 0; + let mut is_wave_moving_forwards = true; + const MAX_ROW: i8 = 5; + + let metadata: Metadata = + Metadata { model: sdv::airbag_seat_massager::massage_airbags::ID.to_string() }; + let mut property: Property = Property { massage_airbags: Vec::new(), metadata }; + + tokio::spawn(async move { + loop { + let client_result = DigitalTwinProviderClient::connect(provider_uri.clone()).await; + if client_result.is_err() { + warn!("Unable to connect. We will retry in a moment."); + sleep(Duration::from_secs(1)).await; + continue; + } + let mut client = client_result.unwrap(); + + // We assume that the seat has 6 rows with 3 airbags in each row. + // The sequence will mimic a wave motion. With the crest of the wave + // having the maximum inflation level and either side of the crest + // having gradually decreasing inflation levels. + // crest_row represents the row where the crest of the wave is located. + property.massage_airbags = Vec::new(); + for airbag in 0..18 { + let row = airbag / 3; + let rows_from_crest = i8::abs(crest_row - row); + let inflation_level = max(100 - (rows_from_crest * 20), 0); + property.massage_airbags.push(inflation_level as i32); + } + + let value = serde_json::to_string_pretty(&property).unwrap(); + + info!( + "Sending a set request for entity id {} to provider URI {provider_uri}", + sdv::airbag_seat_massager::massage_airbags::ID + ); + + let request = tonic::Request::new(SetRequest { + entity_id: sdv::airbag_seat_massager::massage_airbags::ID.to_string(), + value, + }); + + let response = client.set(request).await; + if let Err(status) = response { + warn!("{status:?}"); + } + + // Set crest_row for the next loop iteration. + if crest_row == 0 { + is_wave_moving_forwards = true; + } else if crest_row == MAX_ROW { + is_wave_moving_forwards = false; + } + if is_wave_moving_forwards { + crest_row += 1; + } else { + crest_row -= 1; + } + + debug!("Completed the set request."); + + sleep(Duration::from_secs(1)).await; + } + }); +} + +/// Start the seat massage get repeater. +/// +/// # Arguments +/// `provider_uri` - The provider uri. +/// `consumer_uri` - The consumer uri. +fn start_seat_massage_get_repeater(provider_uri: String, consumer_uri: String) { + debug!("Starting the consumer's seat massage get repeater."); + + tokio::spawn(async move { + loop { + info!( + "Sending a get request for entity id {} to provider URI {provider_uri}", + sdv::airbag_seat_massager::massage_airbags::ID + ); + + let client_result = DigitalTwinProviderClient::connect(provider_uri.clone()).await; + if client_result.is_err() { + warn!("Unable to connect. We will retry in a moment."); + sleep(Duration::from_secs(1)).await; + continue; + } + let mut client = client_result.unwrap(); + + let request = tonic::Request::new(GetRequest { + entity_id: sdv::airbag_seat_massager::massage_airbags::ID.to_string(), + consumer_uri: consumer_uri.clone(), + }); + + let response = client.get(request).await; + if let Err(status) = response { + warn!("{status:?}"); + } + + debug!("Completed the get request."); + + sleep(Duration::from_secs(1)).await; + } + }); +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup logging. + Builder::new().filter(None, LevelFilter::Info).target(Target::Stdout).init(); + + info!("The Consumer has started."); + + let settings = consumer_config::load_settings(); + + let invehicle_digital_twin_url = retrieve_invehicle_digital_twin_url( + settings.invehicle_digital_twin_url, + settings.chariott_url, + ) + .await?; + + let consumer_authority = settings.consumer_authority; + + // Setup the HTTP server. + let addr: SocketAddr = consumer_authority.parse().unwrap(); + let consumer_impl = consumer_impl::ConsumerImpl::default(); + let server_future = + Server::builder().add_service(DigitalTwinConsumerServer::new(consumer_impl)).serve(addr); + info!("The HTTP server is listening on address '{consumer_authority}'"); + + // Retrieve the provider URI. + let provider_endpoint_info = discover_digital_twin_provider_using_ibeji( + &invehicle_digital_twin_url, + sdv::airbag_seat_massager::massage_airbags::ID, + digital_twin_protocol::GRPC, + &[digital_twin_operation::SET.to_string()], + ) + .await + .unwrap(); + let provider_uri = provider_endpoint_info.uri; + info!("The URI for the massage airbags property's provider is {provider_uri}"); + + let consumer_uri = format!("http://{consumer_authority}"); // Devskim: ignore DS137138 + + start_seat_massage_sequence(provider_uri.clone()); + + start_seat_massage_get_repeater(provider_uri, consumer_uri); + + server_future.await?; + + debug!("The Consumer has completed."); + + Ok(()) +} diff --git a/samples/seat_massager/provider/src/main.rs b/samples/seat_massager/provider/src/main.rs new file mode 100644 index 00000000..67598c40 --- /dev/null +++ b/samples/seat_massager/provider/src/main.rs @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +mod provider_impl; + +use digital_twin_model::sdv_v1 as sdv; +use env_logger::{Builder, Target}; +use log::{debug, info, LevelFilter}; +use parking_lot::Mutex; +use samples_common::constants::{digital_twin_operation, digital_twin_protocol}; +use samples_common::utils::{retrieve_invehicle_digital_twin_url, retry_async_based_on_status}; +use samples_common::provider_config; +use samples_protobuf_data_access::digital_twin::v1::digital_twin_client::DigitalTwinClient; +use samples_protobuf_data_access::digital_twin::v1::{EndpointInfo, EntityAccessInfo, RegisterRequest}; +use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::digital_twin_provider_server::DigitalTwinProviderServer; +use std::net::SocketAddr; +use std::sync::Arc; +use tokio::time::Duration; +use tonic::{Status, transport::Server}; + +use crate::provider_impl::{ProviderImpl, ProviderProperties}; + +/// Register the airbag seat massager's massage airbags property. +/// +/// # Arguments +/// * `invehicle_digital_twin_url` - The In-Vehicle Digital Twin URL. +/// * `provider_uri` - The provider's URI. +async fn register_massage_airbags( + invehicle_digital_twin_url: &str, + provider_uri: &str, +) -> Result<(), Status> { + let endpoint_info = EndpointInfo { + protocol: digital_twin_protocol::GRPC.to_string(), + operations: vec![ + digital_twin_operation::GET.to_string(), + digital_twin_operation::SET.to_string(), + ], + uri: provider_uri.to_string(), + context: sdv::airbag_seat_massager::massage_airbags::ID.to_string(), + }; + + let entity_access_info = EntityAccessInfo { + name: sdv::airbag_seat_massager::massage_airbags::NAME.to_string(), + id: sdv::airbag_seat_massager::massage_airbags::ID.to_string(), + description: sdv::airbag_seat_massager::massage_airbags::DESCRIPTION.to_string(), + endpoint_info_list: vec![endpoint_info], + }; + + let mut client = DigitalTwinClient::connect(invehicle_digital_twin_url.to_string()) + .await + .map_err(|e| Status::internal(e.to_string()))?; + let request = + tonic::Request::new(RegisterRequest { entity_access_info_list: vec![entity_access_info] }); + let _response = client.register(request).await?; + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup logging. + Builder::new().filter(None, LevelFilter::Info).target(Target::Stdout).init(); + + info!("The Provider has started."); + + let settings = provider_config::load_settings(); + + let provider_authority = settings.provider_authority; + + let invehicle_digital_twin_url = retrieve_invehicle_digital_twin_url( + settings.invehicle_digital_twin_url, + settings.chariott_url, + ) + .await?; + + // Construct the provider URI from the provider authority. + let provider_uri = format!("http://{provider_authority}"); // Devskim: ignore DS137138 + + // Setup the HTTP server. + let addr: SocketAddr = provider_authority.parse()?; + let properties = Arc::new(Mutex::new(ProviderProperties { massage_airbags: Vec::new() })); + let provider_impl = ProviderImpl { properties: properties.clone() }; + let server_future = + Server::builder().add_service(DigitalTwinProviderServer::new(provider_impl)).serve(addr); + info!("The HTTP server is listening on address '{provider_authority}'"); + + info!("Sending a register request to the In-Vehicle Digital Twin Service URI {invehicle_digital_twin_url}"); + retry_async_based_on_status(30, Duration::from_secs(1), || { + register_massage_airbags(&invehicle_digital_twin_url, &provider_uri) + }) + .await?; + + server_future.await?; + + debug!("The Provider has completed."); + + Ok(()) +} diff --git a/samples/seat_massager/provider/src/provider_impl.rs b/samples/seat_massager/provider/src/provider_impl.rs new file mode 100644 index 00000000..21a8474f --- /dev/null +++ b/samples/seat_massager/provider/src/provider_impl.rs @@ -0,0 +1,212 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +use digital_twin_model::{sdv_v1 as sdv, Metadata}; +use log::{debug, info, warn}; +use parking_lot::{Mutex, MutexGuard}; +use samples_protobuf_data_access::sample_grpc::v1::digital_twin_provider::{ + digital_twin_provider_server::DigitalTwinProvider, GetRequest, GetResponse, InvokeRequest, + InvokeResponse, SetRequest, SetResponse, SubscribeRequest, SubscribeResponse, + UnsubscribeRequest, UnsubscribeResponse, +}; +use samples_protobuf_data_access::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_client::DigitalTwinConsumerClient; +use samples_protobuf_data_access::sample_grpc::v1::digital_twin_consumer::PublishRequest; +use serde_derive::{Deserialize, Serialize}; +use std::sync::Arc; +use std::vec::Vec; +use tonic::{Request, Response, Status}; + +#[derive(Debug, Serialize, Deserialize)] +struct Property { + #[serde(rename = "MassageAirbags")] + massage_airbags: sdv::airbag_seat_massager::massage_airbags::TYPE, + #[serde(rename = "$metadata")] + metadata: Metadata, +} + +#[derive(Debug, Default)] +pub struct ProviderProperties { + pub massage_airbags: sdv::airbag_seat_massager::massage_airbags::TYPE, +} + +#[derive(Debug, Default)] +pub struct ProviderImpl { + pub properties: Arc>, +} + +impl ProviderImpl { + /// Set the massage airbags property from its JSON form. + /// + /// # Arguments + /// * `properties` - The providers properties. + /// * `value` - The massiage airbags property in its JSON form. + fn set_massage_airbags( + properties: Arc>, + value: &str, + ) -> Result<(), String> { + let message_airbags_property_json: serde_json::Value = serde_json::from_str(value).unwrap(); + let message_airbags_json = message_airbags_property_json + .get(sdv::airbag_seat_massager::massage_airbags::NAME) + .unwrap(); + + let massage_airbags: sdv::airbag_seat_massager::massage_airbags::TYPE = + serde_json::from_value(message_airbags_json.clone()).unwrap(); + + info!("Setting message airbags to: {:?}", massage_airbags); + + // This block controls the lifetime of the lock. + { + let mut lock: MutexGuard = properties.lock(); + + lock.massage_airbags = massage_airbags; + } + + Ok(()) + } + + /// Get the massage airbags property in its JSON form. + /// + /// # Arguments + /// * `properties` - The providers properties. + fn get_massage_airbags(properties: Arc>) -> Result { + let mut property: Property = Property { + massage_airbags: Vec::new(), + metadata: Metadata { + model: sdv::airbag_seat_massager::massage_airbags::ID.to_string(), + }, + }; + + // This block controls the lifetime of the lock. + { + let lock: MutexGuard = properties.lock(); + + property.massage_airbags = lock.massage_airbags.clone(); + } + + let property_json = serde_json::to_string(&property).unwrap(); + + Ok(property_json) + } +} + +#[tonic::async_trait] +impl DigitalTwinProvider for ProviderImpl { + /// Subscribe implementation. + /// + /// # Arguments + /// * `request` - Subscribe request. + async fn subscribe( + &self, + request: Request, + ) -> Result, Status> { + warn!("Got subscribe request: {request:?}"); + + Err(Status::unimplemented("subscribe has not been implemented")) + } + + /// Unsubscribe implementation. + /// + /// # Arguments + /// * `request` - Unsubscribe request. + async fn unsubscribe( + &self, + request: Request, + ) -> Result, Status> { + warn!("Got an unsubscribe request: {request:?}"); + + Err(Status::unimplemented("unsubscribe has not been implemented")) + } + + /// Get implementation. + /// + /// # Arguments + /// * `request` - Get request. + async fn get(&self, request: Request) -> Result, Status> { + let request_inner = request.into_inner(); + let entity_id: String = request_inner.entity_id.clone(); + let consumer_uri: String = request_inner.consumer_uri; + + info!("Received a get request for entity id {entity_id}"); + + let properties: Arc> = self.properties.clone(); + + tokio::spawn(async move { + if entity_id == sdv::airbag_seat_massager::massage_airbags::ID { + let get_massage_airbags_result = + ProviderImpl::get_massage_airbags(properties.clone()); + if let Err(error_message) = get_massage_airbags_result { + warn!("Failed to get {entity_id} due to: {error_message}"); + return; + } + let client_result = DigitalTwinConsumerClient::connect(consumer_uri).await; + if let Err(error_message) = client_result { + warn!("Unable to connect due to {error_message}"); + return; + } + let mut client = client_result.unwrap(); + + let publish_request = tonic::Request::new(PublishRequest { + entity_id, + value: get_massage_airbags_result.unwrap(), + }); + let response = client.publish(publish_request).await; + if let Err(status) = response { + warn!("Publish failed: {status:?}"); + } + } else { + warn!("The entity id {entity_id} is not recognized."); + } + }); + + let response = GetResponse {}; + + debug!("Completed the get request."); + + Ok(Response::new(response)) + } + + /// Set implementation. + /// + /// # Arguments + /// * `request` - Set request. + async fn set(&self, request: Request) -> Result, Status> { + let request_inner = request.into_inner(); + let entity_id: String = request_inner.entity_id.clone(); + let value: String = request_inner.value; + + info!("Received a set request for entity id {entity_id}"); + + let properties: Arc> = self.properties.clone(); + + tokio::spawn(async move { + if entity_id == sdv::airbag_seat_massager::massage_airbags::ID { + let result = ProviderImpl::set_massage_airbags(properties.clone(), &value); + if result.is_err() { + warn!("Failed to set {} due to: {}", entity_id, result.unwrap_err()); + } + } else { + warn!("Error: The entity id {entity_id} is not recognized."); + } + }); + + let response = SetResponse {}; + + debug!("Completed the set request."); + + Ok(Response::new(response)) + } + + /// Invoke implementation. + /// + /// # Arguments + /// * `request` - Invoke request. + async fn invoke( + &self, + request: Request, + ) -> Result, Status> { + warn!("Got an invoke request: {request:?}"); + + Err(Status::unimplemented("invoke has not been implemented")) + } +}