diff --git a/Cargo.toml b/Cargo.toml index 0359525e..5c8d8fc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,10 +7,11 @@ members = [ "common", "common-test", - "dt-model/dt-model-identifiers", - "proto", + "digital-twin-model", "dtdl-parser", "in-vehicle-digital-twin", + "samples/common", + "samples/proto", "samples/command/consumer", "samples/command/provider", "samples/mixed/consumer", @@ -29,10 +30,11 @@ lazy_static = "1.4.0" log = "^0.4" parking_lot = "0.12.1" prost = "0.11" -regex = " 1.7.1" +regex = " 1.8.1" tokio = "1.0" -tonic = "0.8.2" -tonic-build = "0.8.2" +tonic = "0.9.2" +tonic-build = "0.9.2" +serde = "1.0.160" serde_json = "^1.0" strum = "0.24" strum_macros = "0.24" diff --git a/README.md b/README.md index ede08cf7..1f10c664 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,6 @@ Steps: 1. The best way to run the demo is by using three windows: one running the In-Vehicle Digital Twin, one running the Provider and one running a Consumer. Orientate the three windows so that they are lined up in a column. The top window can be used for the In-Vehicle Digital Twin. The middle window can be used for the Provider. The bottom window can be used for a Consumer.
-1. In each window run the following command to set the DTDL_PATH environment variable. -Make sure that you replace "{repo-root-dir}" with the repository root directory on the machine where you are running the demo.

-`export DTDL_PATH="{repo-root-dir}/opendigitaltwins-dtdl/DTDL;{repo-root-dir}/dtdl;{repo-root-dir}/samples/property/dtdl"`
1. In each window change directory to the directory containing the build artifacts. Make sure that you replace "{repo-root-dir}" with the repository root directory on the machine where you are running the demo.

`cd {repo-root-dir}/target/debug`
@@ -133,9 +130,6 @@ Steps: 1. The best way to run the demo is by using three windows: one running the In-Vehicle Digital Twin, one running the Provider and one running a Consumer. Orientate the three windows so that they are lined up in a column. The top window can be used for the In-Vehicle Digital Twin. The middle window can be used for the Provider. The bottom window can be used for a Consumer.
-1. In each window run the following command to set the DTDL_PATH environment variable. -Make sure that you replace "{repo-root-dir}" with the repository root directory on the machine where you are running the demo.

-`export DTDL_PATH="{repo-root-dir}/opendigitaltwins-dtdl/DTDL;{repo-root-dir}/dtdl;{repo-root-dir}/samples/command/dtdl"`
1. In each window change directory to the directory containing the build artifacts. Make sure that you replace "{repo-root-dir}" with the repository root directory on the machine where you are running the demo.

`cd {repo-root-dir}/target/debug`
@@ -162,9 +156,6 @@ Steps: 1. The best way to run the demo is by using three windows: one running the In-Vehicle Digital Twin, one running the Provider and one running a Consumer. Orientate the three windows so that they are lined up in a column. The top window can be used for the In-Vehicle Digital Twin. The middle window can be used for the Provider. The bottom window can be used for a Consumer.
-1. In each window run the following command to set the DTDL_PATH environment variable. -Make sure that you replace "{repo-root-dir}" with the repository root directory on the machine where you are running the demo.

-`export DTDL_PATH="{repo-root-dir}/opendigitaltwins-dtdl/DTDL;{repo-root-dir}/dtdl;{repo-root-dir}/samples/mixed/dtdl"`
1. In each window change directory to the directory containing the build artifacts. Make sure that you replace "{repo-root-dir}" with the repository root directory on the machine where you are running the demo.

`cd {repo-root-dir}/target/debug`
diff --git a/common-test/src/lib.rs b/common-test/src/lib.rs index 68ff6247..2d6521b7 100644 --- a/common-test/src/lib.rs +++ b/common-test/src/lib.rs @@ -34,7 +34,7 @@ pub fn set_dtdl_path() { let repo_dir_result = get_repo_dir(); if let Some(repo_dir) = repo_dir_result { let value = format!( - "{repo_dir}/opendigitaltwins-dtdl/DTDL;{repo_dir}/iot-plugandplay-models;{repo_dir}/dtdl" + "{repo_dir}/opendigitaltwins-dtdl/DTDL;{repo_dir}/iot-plugandplay-models;{repo_dir}/dtdl;{repo_dir}/digital-twin-model/dtdl" ); env::set_var(DTDL_PATH, &value); trace!("{DTDL_PATH}={value}"); @@ -56,7 +56,7 @@ mod ibeji_common_test_tests { assert!(get_dtdl_path_result.is_ok()); let dtdl_path = get_dtdl_path_result.unwrap(); assert!(!dtdl_path.is_empty()); - assert!(dtdl_path.contains("/opendigitaltwins-dtdl/DTDL;")); + assert!(dtdl_path.contains("/opendigitaltwins-dtdl/DTDL")); assert!(dtdl_path.contains("/dtdl")); } } diff --git a/common/src/lib.rs b/common/src/lib.rs index 046a5212..1e55194f 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -56,9 +56,9 @@ mod ibeji_common_tests { fn find_full_path_test() { set_dtdl_path(); - let find_full_path_result = find_full_path("samples/remotely_accessible_resource.json"); + let find_full_path_result = find_full_path("v2/content/sdv/vehicle.json"); assert!(find_full_path_result.is_ok()); let full_path = find_full_path_result.unwrap(); - assert!(full_path.ends_with("/samples/remotely_accessible_resource.json")); + assert!(full_path.ends_with("/v2/content/sdv/vehicle.json")); } } diff --git a/dt-model/dt-model-identifiers/Cargo.toml b/digital-twin-model/Cargo.toml similarity index 88% rename from dt-model/dt-model-identifiers/Cargo.toml rename to digital-twin-model/Cargo.toml index 1b13d2a1..f0bfe957 100644 --- a/dt-model/dt-model-identifiers/Cargo.toml +++ b/digital-twin-model/Cargo.toml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT [package] -name = "dt-model-identifiers" +name = "digital-twin-model" version = "0.1.0" edition = "2021" license = "MIT" diff --git a/samples/mixed/dtdl/content/mixed.json b/digital-twin-model/dtdl/v2/content/sdv/vehicle.json similarity index 52% rename from samples/mixed/dtdl/content/mixed.json rename to digital-twin-model/dtdl/v2/content/sdv/vehicle.json index 25e832a5..adbe2685 100644 --- a/samples/mixed/dtdl/content/mixed.json +++ b/digital-twin-model/dtdl/v2/content/sdv/vehicle.json @@ -1,72 +1,51 @@ [ { - "@context": ["dtmi:dtdl:context;2", "dtmi:sdv:context;2"], + "@context": ["dtmi:dtdl:context;2"], "@type": "Interface", "@id": "dtmi:sdv:Vehicle:Cabin:HVAC;1", "description": "Heat, Ventilation and Air Conditioning", "contents": [ { - "@type": ["Property", "Temperature", "RemotelyAccessible"], + "@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", - "remote_access": [ - { - "@type": "Endpoint", - "uri": "http://[::1]:40010", - "operations": [ "Subscribe", "Unsubscribe" ] - } - ] + "unit": "degreeFahrenheit" }, { - "@type": ["Property", "RemotelyAccessible"], + "@type": ["Property"], "@id": "dtmi:sdv:Vehicle:Cabin:HVAC:IsAirConditioningActive;1", "name": "IsAirConditioningActive", "description": "Is air conditioning active?", - "schema": "boolean", - "remote_access": [ - { - "@type": "Endpoint", - "uri": "http://[::1]:40010", - "operations": [ "Set", "Subscribe", "Unsubscribe" ] - } - ] + "schema": "boolean" } ] }, { - "@context": ["dtmi:dtdl:context;2", "dtmi:sdv:context;2"], + "@context": ["dtmi:dtdl:context;2"], "@type": "Interface", "@id": "dtmi:sdv:Vehicle:OBD;1", "description": "On-board Diagnostics Interface", "contents": [ { - "@type": ["Property", "RemotelyAccessible"], + "@type": ["Property"], "@id": "dtmi:sdv:Vehicle:OBD:HybridBatteryRemaining;1", "name": "HybridBatteryRemaining", "description": "The remaining hybrid battery life.", "schema": "integer", - "unit": "percent", - "remote_access": [ - { - "@type": "Endpoint", - "uri": "http://[::1]:40010", - "operations": [ "Subscribe", "Unsubscribe" ] - } - ] + "unit": "percent" } ] }, { - "@context": ["dtmi:dtdl:context;2", "dtmi:sdv:context;2"], + "@context": ["dtmi:dtdl:context;2"], "@type": "Interface", "@id": "dtmi:sdv:Vehicle:Cabin:Infotainment:HMI;1", "description": "The Human Machine Interface.", "contents": [ { - "@type": ["Command", "RemotelyAccessible"], + "@type": ["Command"], "@id": "dtmi:sdv:Vehicle:Cabin:Infotainment:HMI:ShowNotification;1", "name": "ShowNotification", "request": { @@ -74,14 +53,7 @@ "displayName": "Show Notification", "descriptiption": "Show a notification on the HMI.", "schema": "string" - }, - "remote_access": [ - { - "@type": "Endpoint", - "uri": "http://[::1]:40010", - "operations": [ "Invoke" ] - } - ] + } } ] } diff --git a/dt-model/dt-model-identifiers/src/lib.rs b/digital-twin-model/src/lib.rs similarity index 100% rename from dt-model/dt-model-identifiers/src/lib.rs rename to digital-twin-model/src/lib.rs diff --git a/dt-model/dt-model-identifiers/src/sdv_v1.rs b/digital-twin-model/src/sdv_v1.rs similarity index 90% rename from dt-model/dt-model-identifiers/src/sdv_v1.rs rename to digital-twin-model/src/sdv_v1.rs index 83b42d55..4cd1879a 100644 --- a/dt-model/dt-model-identifiers/src/sdv_v1.rs +++ b/digital-twin-model/src/sdv_v1.rs @@ -29,9 +29,3 @@ pub mod vehicle { } } } - -pub mod property { - pub mod uri { - pub const ID: &str = "dtmi:sdv:property:uri;1"; - } -} diff --git a/docs/design/.accepted_words.txt b/docs/design/.accepted_words.txt index d4c6ffb6..5aa74aab 100644 --- a/docs/design/.accepted_words.txt +++ b/docs/design/.accepted_words.txt @@ -10,6 +10,7 @@ findbyid FindById gRPC http +HVAC metadata org RemotelyAccessible diff --git a/docs/design/README.md b/docs/design/README.md index 578cf60c..2383cba8 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -17,15 +17,15 @@ Please note that the initial Ibeji implementation is a proof-of-concept. We woul Ibeji has three main architectural concepts: -- Consumer -- Provider +- Digital Twin Consumer +- Digital Twin Provider - In-Vehicle Digital Twin Service -The first Ibeji architectural concept that we will introduce is the Consumer. A Consumer is a software entity that utilizes Ibeji to interface with the digital representation of the In-Vehicle hardware components. +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 Provider. A Provider is the access point to some/all of the vehicle's hardware resources. A Provider registers itself with the In-Vehicle Digital Twin Service. Once registered, the In-Vehicle Digital Twin Service can make the resources available to Consumers. Each resource includes meta data that allow Consumers to understand the semantics of the resource and know how to interact with it. The In-Vehicle Digital Twin Service supports multiple simultaneous Providers and internally resolves overlapping resources offered by multiple Providers. These overlaps offer multiple options for interacting with a resource and can improve the resource's availability (by supporting multiple access paths). A Provider must support a Provider interface that enables access to resource data feeds. +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). -In the middle is the In-Vehicle Digital Twin Service. It exports a query interface that enables 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 Providers to dynamically register and unregister resources. +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. Below is the component diagram for Ibeji. @@ -33,48 +33,41 @@ Below is the component diagram for Ibeji. ## 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, and to provide the metadata needed to interact the resource. +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. 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. -DTDL can identify and specify each of the resources. DTDL allows additional metadata to be associated with each of the resources, specifically the endpoint that can be used to interact with that resource. Below is an example for the AmbientAirTemperature property. You can see that the resource has the "RemotelyAccessible" type, which allows it to specify remote access metadata. The remote_access element utilizes an "Endpoint" type to specify the resource's endpoint and the supported operations. +DTDL can identify and specify each of the resources. Below is an example for the AmbientAirTemperature property. ```uml { - "@context": ["dtmi:dtdl:context;2", "dtmi:sdv:context;3"], + "@context": ["dtmi:dtdl:context;2"], "@type": "Interface", - "@id": "dtmi:org:eclipse:sdv:interface:cabin:AmbientAirTemperature;1", + "@id": "dtmi:sdv:Vehicle:Cabin:HVAC;1", "contents": [ { - "@type": ["Property", "Temperature", "RemotelyAccessible"], - "@id": "dtmi:org:eclipse:sdv:property:cabin:AmbientAirTemperature;1", + "@type": ["Property", "Temperature"], + "@id": "dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1", "name": "Cabin_AmbientAirTemperature", "description": "The immediate surroundings air temperature (in Fahrenheit).", - "schema": "double", - "unit": "degreeFahrenheit", - "remote_access": [ - { - "@type": "Endpoint", - "uri": "http://[::1]:40010", - "operations": [ "Get", "Set", "Subscribe", "Unsubscribe" ] - } - ] + "schema": "integer", + "unit": "degreeFahrenheit" } ] } ``` -The DTDL must use the standard dtmi dtdl context. It must also use the dtmi sdv context, which provides the definitions for the RemotelyAccessible type and the remote_access element. +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 commands on provided hardware resources. +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 with a gRPC interface. +The initial In-Vehicle Digital Twin Service supports both Providers and Consumers. ### Activities @@ -98,19 +91,19 @@ The initial Providers will implement basic resources - the AmbientAirTemperature ### Interfaces -A Provider supports a gRPC interface for subscribing to resource's data feeds, unsubscribing from a resource's data feed, requesting a resource's value, setting a resource's value and invoking a command. +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. ### Activities #### Subscribe -Below is the sequence diagram for the Subscribe activity. The Provider's endpoint details are exported by the Provider as DTDL to the Digital Twin Service. +Below is the sequence diagram for the Subscribe activity. ![Sequence Diagram](diagrams/subscribe_sequence.svg) #### Invoke -Below is the sequence diagram for the Invoke activity. The Provider's endpoint details are exported by the Provider as DTDL to the Digital Twin Service. +Below is the sequence diagram for the Invoke activity. ![Sequence Diagram](diagrams/invoke_sequence.svg) @@ -122,7 +115,7 @@ The initial Consumers will provide the functionality needed by the proof-of-conc Interfaces -A Consumer supports a gRPC interface that is the callback/notification endpoint for subscribed-to data feeds. +A Consumer supports an interface that is the callback/notification endpoint for subscribed-to data feeds. Activities @@ -138,7 +131,7 @@ Below is the sequence diagram for the Respond activity. ![Sequence Diagram](diagrams/respond_sequence.svg) -## Appendix A – Provider gRPC Interface +## Appendix A – Digital Twin Provider Interface ### Subscribe @@ -199,18 +192,19 @@ Invoke a command. #### Request - entity_id - The command's id. -- uri - The uri for the endpoint where the command's response should be delivered. +- 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 gRPC Interface +## Appendix B – Digital Twin Interface ### FindById -Find an entity's DTDL. +Find an entity's access information. #### Request @@ -218,33 +212,21 @@ Find an entity's DTDL. #### Response -- dtdl - The resource's DTDL. +- entity_access_info - The entity's access information. ### Register -Register one or more entities. - -#### Request - -- dtdl - The DTDL that represents the entities. - -#### Response - -- No response. - -### Unregister - -Unregister an entity. +Register one or more entities access information. #### Request -- id - The resource's id. +- entity_access_info_list - A list of entity access information. #### Response - No response. -## Appendix C – Consumer gRPC Interface +## Appendix C – Digital Twin Consumer Interface ### Publish diff --git a/docs/design/diagrams/findbyid_sequence.puml b/docs/design/diagrams/findbyid_sequence.puml index 27ee87af..64cbd179 100644 --- a/docs/design/diagrams/findbyid_sequence.puml +++ b/docs/design/diagrams/findbyid_sequence.puml @@ -3,7 +3,7 @@ participant "In-Vehicle Digital Twin Service" participant "Consumer" -"Consumer" -> "In-Vehicle Digital Twin Service" : FindById - request (id) -"In-Vehicle Digital Twin Service" --> "Consumer" : FindById - response (dtdl) +"Consumer" -> "In-Vehicle Digital Twin Service" : FindById - request (entity id) +"In-Vehicle Digital Twin Service" --> "Consumer" : FindById - response (entity access info) @enduml diff --git a/docs/design/diagrams/findbyid_sequence.svg b/docs/design/diagrams/findbyid_sequence.svg index 8a57da19..6d1ece9b 100644 --- a/docs/design/diagrams/findbyid_sequence.svg +++ b/docs/design/diagrams/findbyid_sequence.svg @@ -1,17 +1 @@ -In-Vehicle Digital Twin ServiceIn-Vehicle Digital Twin ServiceConsumerConsumerFindById - request (id)FindById - response (dtdl) \ No newline at end of file +In-Vehicle Digital Twin ServiceIn-Vehicle Digital Twin ServiceConsumerConsumerFindById - request (entity id)FindById - response (entity access info) \ No newline at end of file diff --git a/docs/design/diagrams/ibeji_component.puml b/docs/design/diagrams/ibeji_component.puml index ac2343af..fabd806c 100644 --- a/docs/design/diagrams/ibeji_component.puml +++ b/docs/design/diagrams/ibeji_component.puml @@ -1,16 +1,15 @@ @startuml frame "Consumer" { - interface "Consumer" as ConsumerInterface + interface DigitalTwinConsumerInterface } frame "In-Vehicle Digital Twin Service" { - interface "Digital Twin" as DigitalTwinInterface - interface "Provider" as DTProviderInterface + interface DigitalTwinInterface } frame "Provider" { - interface "Provider" as ProviderInterface + interface DigitalTwinProviderInterface } @enduml diff --git a/docs/design/diagrams/ibeji_component.svg b/docs/design/diagrams/ibeji_component.svg index b21ce4ff..10bcf1e8 100644 --- a/docs/design/diagrams/ibeji_component.svg +++ b/docs/design/diagrams/ibeji_component.svg @@ -1,31 +1 @@ -ConsumerIn-Vehicle Digital Twin ServiceProviderConsumerDigital TwinProviderProvider \ No newline at end of file +ConsumerIn-Vehicle Digital Twin ServiceProviderDigitalTwinConsumerInterfaceDigitalTwinInterfaceDigitalTwinProviderInterface \ No newline at end of file diff --git a/docs/design/diagrams/invoke_sequence.puml b/docs/design/diagrams/invoke_sequence.puml index 83cfafbb..9a113876 100644 --- a/docs/design/diagrams/invoke_sequence.puml +++ b/docs/design/diagrams/invoke_sequence.puml @@ -3,6 +3,6 @@ participant "Provider" participant "Consumer" -"Consumer" -> "Provider" : Invoke - request (id, url, payload) +"Consumer" -> "Provider" : Invoke - request (entity id, consumer uri, response id, payload) @enduml diff --git a/docs/design/diagrams/invoke_sequence.svg b/docs/design/diagrams/invoke_sequence.svg index 646d8e39..ba1346f9 100644 --- a/docs/design/diagrams/invoke_sequence.svg +++ b/docs/design/diagrams/invoke_sequence.svg @@ -1 +1 @@ -ProviderProviderConsumerConsumerInvoke - request (id, url, payload) \ No newline at end of file +ProviderProviderConsumerConsumerInvoke - request (entity id, consumer uri, response id, payload) \ No newline at end of file diff --git a/docs/design/diagrams/publish_sequence.puml b/docs/design/diagrams/publish_sequence.puml index 510ca335..695a9b31 100644 --- a/docs/design/diagrams/publish_sequence.puml +++ b/docs/design/diagrams/publish_sequence.puml @@ -3,6 +3,6 @@ participant "Provider" participant "Consumer" -"Provider" -> "Consumer" : Publish - request (id, value) +"Provider" -> "Consumer" : Publish - request (entity id, value) @enduml diff --git a/docs/design/diagrams/publish_sequence.svg b/docs/design/diagrams/publish_sequence.svg index 3335d7bf..d4a3e5ce 100644 --- a/docs/design/diagrams/publish_sequence.svg +++ b/docs/design/diagrams/publish_sequence.svg @@ -1,16 +1 @@ -ProviderProviderConsumerConsumerPublish - request (id, value) \ No newline at end of file +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 1e5ebc8e..087586e5 100644 --- a/docs/design/diagrams/register_sequence.puml +++ b/docs/design/diagrams/register_sequence.puml @@ -3,6 +3,6 @@ participant "Provider" participant "In-Vehicle Digital Twin Service" -"Provider" -> "In-Vehicle Digital Twin Service" : Register - request (dtdl) +"Provider" -> "In-Vehicle Digital Twin Service" : Register - request (entity access info list) @enduml diff --git a/docs/design/diagrams/register_sequence.svg b/docs/design/diagrams/register_sequence.svg index 92c9992f..f4aef8b1 100644 --- a/docs/design/diagrams/register_sequence.svg +++ b/docs/design/diagrams/register_sequence.svg @@ -1,16 +1 @@ -ProviderProviderIn-Vehicle Digital Twin ServiceIn-Vehicle Digital Twin ServiceRegister - request (dtdl) \ No newline at end of file +ProviderProviderIn-Vehicle Digital Twin ServiceIn-Vehicle Digital Twin ServiceRegister - request (entity access info list) \ No newline at end of file diff --git a/docs/design/diagrams/respond_sequence.puml b/docs/design/diagrams/respond_sequence.puml index 9517e4f0..5eeae2be 100644 --- a/docs/design/diagrams/respond_sequence.puml +++ b/docs/design/diagrams/respond_sequence.puml @@ -3,6 +3,6 @@ participant "Provider" participant "Consumer" -"Provider" -> "Consumer" : Respond - request (id, payload) +"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 index b1e306b0..1a2ff9fa 100644 --- a/docs/design/diagrams/respond_sequence.svg +++ b/docs/design/diagrams/respond_sequence.svg @@ -1 +1 @@ -ProviderProviderConsumerConsumerRespond - request (id, payload) \ No newline at end of file +ProviderProviderConsumerConsumerRespond - request (entity id, response id, payload) \ No newline at end of file diff --git a/docs/design/diagrams/subscribe_sequence.puml b/docs/design/diagrams/subscribe_sequence.puml index a95f7a91..135191c4 100644 --- a/docs/design/diagrams/subscribe_sequence.puml +++ b/docs/design/diagrams/subscribe_sequence.puml @@ -3,6 +3,6 @@ participant "Provider" participant "Consumer" -"Consumer" -> "Provider" : Subscribe - response (id, uri) +"Consumer" -> "Provider" : Subscribe - response (entity id, consumer uri) @enduml diff --git a/docs/design/diagrams/subscribe_sequence.svg b/docs/design/diagrams/subscribe_sequence.svg index 6479b472..e8cf55a5 100644 --- a/docs/design/diagrams/subscribe_sequence.svg +++ b/docs/design/diagrams/subscribe_sequence.svg @@ -1,16 +1 @@ -ProviderProviderConsumerConsumerSubscribe - response (id, uri) \ No newline at end of file +ProviderProviderConsumerConsumerSubscribe - response (entity id, consumer uri) \ No newline at end of file diff --git a/dtdl-parser/src/command_info_impl.rs b/dtdl-parser/src/command_info_impl.rs index d1e9a985..5afc6093 100644 --- a/dtdl-parser/src/command_info_impl.rs +++ b/dtdl-parser/src/command_info_impl.rs @@ -225,21 +225,21 @@ mod command_info_impl_tests { command_info.add_undefined_property(String::from("first"), first_propery_value.clone()); command_info.add_undefined_property(String::from("second"), second_propery_value.clone()); - assert!(command_info.dtdl_version() == 2); - assert!(command_info.id() == &id); + assert_eq!(command_info.dtdl_version(), 2); + assert_eq!(command_info.id(), &id); assert!(command_info.child_of().is_some()); - assert!(command_info.child_of().clone().unwrap() == child_of); + assert_eq!(command_info.child_of().clone().unwrap(), child_of); assert!(command_info.defined_in().is_some()); - assert!(command_info.defined_in().clone().unwrap() == defined_in); - assert!(command_info.entity_kind() == EntityKind::Command); - assert!(command_info.undefined_properties().len() == 2); - assert!( - command_info.undefined_properties().get("first").unwrap().clone() - == first_propery_value + assert_eq!(command_info.defined_in().clone().unwrap(), defined_in); + assert_eq!(command_info.entity_kind(), EntityKind::Command); + assert_eq!(command_info.undefined_properties().len(), 2); + assert_eq!( + command_info.undefined_properties().get("first").unwrap().clone(), + first_propery_value ); - assert!( - command_info.undefined_properties().get("second").unwrap().clone() - == second_propery_value + assert_eq!( + command_info.undefined_properties().get("second").unwrap().clone(), + second_propery_value ); match command_info.name() { diff --git a/dtdl-parser/src/command_payload_info_impl.rs b/dtdl-parser/src/command_payload_info_impl.rs index d806ec82..bef167ec 100644 --- a/dtdl-parser/src/command_payload_info_impl.rs +++ b/dtdl-parser/src/command_payload_info_impl.rs @@ -183,27 +183,27 @@ mod command_payload_info_impl_tests { command_payload_info .add_undefined_property(String::from("second"), second_propery_value.clone()); - assert!(command_payload_info.dtdl_version() == DTDL_VERSION); - assert!(command_payload_info.id() == &id); + assert_eq!(command_payload_info.dtdl_version(), DTDL_VERSION); + assert_eq!(command_payload_info.id(), &id); assert!(command_payload_info.child_of().is_some()); - assert!(command_payload_info.child_of().clone().unwrap() == child_of); + assert_eq!(command_payload_info.child_of().clone().unwrap(), child_of); assert!(command_payload_info.defined_in().is_some()); - assert!(command_payload_info.defined_in().clone().unwrap() == defined_in); - assert!(command_payload_info.entity_kind() == EntityKind::CommandPayload); + assert_eq!(command_payload_info.defined_in().clone().unwrap(), defined_in); + assert_eq!(command_payload_info.entity_kind(), EntityKind::CommandPayload); assert!(command_payload_info.schema().is_some()); match command_payload_info.schema() { Some(schema) => assert_eq!(schema.entity_kind(), EntityKind::String), None => return Err(String::from("schema has not been set")), } - assert!(command_payload_info.undefined_properties().len() == 2); - assert!( - command_payload_info.undefined_properties().get("first").unwrap().clone() - == first_propery_value + assert_eq!(command_payload_info.undefined_properties().len(), 2); + assert_eq!( + command_payload_info.undefined_properties().get("first").unwrap().clone(), + first_propery_value ); - assert!( - command_payload_info.undefined_properties().get("second").unwrap().clone() - == second_propery_value + assert_eq!( + command_payload_info.undefined_properties().get("second").unwrap().clone(), + second_propery_value ); match command_payload_info.name() { diff --git a/dtdl-parser/src/component_info_impl.rs b/dtdl-parser/src/component_info_impl.rs index 3e84b227..cb3fb200 100644 --- a/dtdl-parser/src/component_info_impl.rs +++ b/dtdl-parser/src/component_info_impl.rs @@ -176,21 +176,21 @@ mod component_info_impl_tests { component_info.add_undefined_property(String::from("first"), first_propery_value.clone()); component_info.add_undefined_property(String::from("second"), second_propery_value.clone()); - assert!(component_info.dtdl_version() == 2); - assert!(component_info.id() == &id); + assert_eq!(component_info.dtdl_version(), 2); + assert_eq!(component_info.id(), &id); assert!(component_info.child_of().is_some()); - assert!(component_info.child_of().clone().unwrap() == child_of); + assert_eq!(component_info.child_of().clone().unwrap(), child_of); assert!(component_info.defined_in().is_some()); - assert!(component_info.defined_in().clone().unwrap() == defined_in); - assert!(component_info.entity_kind() == EntityKind::Component); - assert!(component_info.undefined_properties().len() == 2); - assert!( - component_info.undefined_properties().get("first").unwrap().clone() - == first_propery_value + assert_eq!(component_info.defined_in().clone().unwrap(), defined_in); + assert_eq!(component_info.entity_kind(), EntityKind::Component); + assert_eq!(component_info.undefined_properties().len(), 2); + assert_eq!( + component_info.undefined_properties().get("first").unwrap().clone(), + first_propery_value ); - assert!( - component_info.undefined_properties().get("second").unwrap().clone() - == second_propery_value + assert_eq!( + component_info.undefined_properties().get("second").unwrap().clone(), + second_propery_value ); match component_info.name() { diff --git a/dtdl-parser/src/dtmi.rs b/dtdl-parser/src/dtmi.rs index c616cf8c..9bc6f23d 100644 --- a/dtdl-parser/src/dtmi.rs +++ b/dtdl-parser/src/dtmi.rs @@ -176,24 +176,24 @@ mod dmti_tests { assert!(new_dtmi_result.is_ok()); let mut dtmi: Dtmi = new_dtmi_result.unwrap(); assert!(dtmi.major_version().is_some()); - assert!(dtmi.major_version().unwrap() == 1); + assert_eq!(dtmi.major_version().unwrap(), 1); assert!(dtmi.minor_version().is_some()); - assert!(dtmi.minor_version().unwrap() == 234); - assert!(dtmi.complete_version() == 1.000234); - assert!(dtmi.versionless() == "dtmi:com:example:Thermostat"); - assert!(dtmi.labels().len() == 3); - assert!(dtmi.labels()[0] == "com"); - assert!(dtmi.labels()[1] == "example"); - assert!(dtmi.labels()[2] == "Thermostat"); - assert!(dtmi.absolute_path == "com:example:Thermostat"); - assert!(dtmi.fragment() == "some-fragment"); - assert!(format!("{dtmi}") == "dtmi:com:example:Thermostat;1.234#some-fragment"); + assert_eq!(dtmi.minor_version().unwrap(), 234); + assert_eq!(dtmi.complete_version(), 1.000234); + assert_eq!(dtmi.versionless(), "dtmi:com:example:Thermostat"); + assert_eq!(dtmi.labels().len(), 3); + assert_eq!(dtmi.labels()[0], "com"); + assert_eq!(dtmi.labels()[1], "example"); + assert_eq!(dtmi.labels()[2], "Thermostat"); + assert_eq!(dtmi.absolute_path, "com:example:Thermostat"); + assert_eq!(dtmi.fragment(), "some-fragment"); + assert_eq!(format!("{dtmi}"), "dtmi:com:example:Thermostat;1.234#some-fragment"); new_dtmi_result = Dtmi::new("dtmi:com:example:Thermostat;1.234#"); assert!(new_dtmi_result.is_ok()); dtmi = new_dtmi_result.unwrap(); - assert!(dtmi.fragment() == ""); - assert!(format!("{dtmi}") == "dtmi:com:example:Thermostat;1.234#"); + assert_eq!(dtmi.fragment(), ""); + assert_eq!(format!("{dtmi}"), "dtmi:com:example:Thermostat;1.234#"); } #[test] @@ -205,13 +205,13 @@ mod dmti_tests { assert!(dtmi.major_version().unwrap() == 1); assert!(dtmi.minor_version().is_some()); assert!(dtmi.minor_version().unwrap() == 234567); - assert!(dtmi.complete_version() == 1.234567); - assert!(dtmi.versionless() == "dtmi:com:example:Thermostat"); - assert!(dtmi.labels().len() == 3); - assert!(dtmi.labels()[0] == "com"); - assert!(dtmi.labels()[1] == "example"); - assert!(dtmi.labels()[2] == "Thermostat"); - assert!(dtmi.absolute_path == "com:example:Thermostat"); + assert_eq!(dtmi.complete_version(), 1.234567); + assert_eq!(dtmi.versionless(), "dtmi:com:example:Thermostat"); + assert_eq!(dtmi.labels().len(), 3); + assert_eq!(dtmi.labels()[0], "com"); + assert_eq!(dtmi.labels()[1], "example"); + assert_eq!(dtmi.labels()[2], "Thermostat"); + assert_eq!(dtmi.absolute_path, "com:example:Thermostat"); } #[test] @@ -244,7 +244,7 @@ mod dmti_tests { assert!(third_create_dtmi_result.is_some()); let third_dtmi = third_create_dtmi_result.unwrap(); - assert!(first_dtmi == second_dtmi); + assert_eq!(first_dtmi, second_dtmi); assert!(first_dtmi != third_dtmi); } } diff --git a/dtdl-parser/src/field_info_impl.rs b/dtdl-parser/src/field_info_impl.rs index a5303606..c9b4bb75 100644 --- a/dtdl-parser/src/field_info_impl.rs +++ b/dtdl-parser/src/field_info_impl.rs @@ -189,20 +189,21 @@ mod field_info_impl_tests { field_info.add_undefined_property(String::from("first"), first_propery_value.clone()); field_info.add_undefined_property(String::from("second"), second_propery_value.clone()); - assert!(field_info.dtdl_version() == 2); - assert!(field_info.id() == &id); + assert_eq!(field_info.dtdl_version(), 2); + assert_eq!(field_info.id(), &id); assert!(field_info.child_of().is_some()); - assert!(field_info.child_of().clone().unwrap() == child_of); + assert_eq!(field_info.child_of().clone().unwrap(), child_of); assert!(field_info.defined_in().is_some()); - assert!(field_info.defined_in().clone().unwrap() == defined_in); - assert!(field_info.entity_kind() == EntityKind::Telemetry); - assert!(field_info.undefined_properties().len() == 2); - assert!( - field_info.undefined_properties().get("first").unwrap().clone() == first_propery_value + assert_eq!(field_info.defined_in().clone().unwrap(), defined_in); + assert_eq!(field_info.entity_kind(), EntityKind::Telemetry); + assert_eq!(field_info.undefined_properties().len(), 2); + assert_eq!( + field_info.undefined_properties().get("first").unwrap().clone(), + first_propery_value ); - assert!( - field_info.undefined_properties().get("second").unwrap().clone() - == second_propery_value + assert_eq!( + field_info.undefined_properties().get("second").unwrap().clone(), + second_propery_value ); match field_info.name() { diff --git a/dtdl-parser/src/interface_info_impl.rs b/dtdl-parser/src/interface_info_impl.rs index 9acdaf81..ec64adca 100644 --- a/dtdl-parser/src/interface_info_impl.rs +++ b/dtdl-parser/src/interface_info_impl.rs @@ -139,21 +139,21 @@ mod interface_info_impl_tests { interface_info.add_undefined_property(String::from("first"), first_propery_value.clone()); interface_info.add_undefined_property(String::from("second"), second_propery_value.clone()); - assert!(interface_info.dtdl_version() == 2); - assert!(interface_info.id() == &id); + assert_eq!(interface_info.dtdl_version(), 2); + assert_eq!(interface_info.id(), &id); assert!(interface_info.child_of().is_some()); - assert!(interface_info.child_of().clone().unwrap() == child_of); + assert_eq!(interface_info.child_of().clone().unwrap(), child_of); assert!(interface_info.defined_in().is_some()); - assert!(interface_info.defined_in().clone().unwrap() == defined_in); - assert!(interface_info.entity_kind() == EntityKind::Interface); - assert!(interface_info.undefined_properties().len() == 2); - assert!( - interface_info.undefined_properties().get("first").unwrap().clone() - == first_propery_value + assert_eq!(interface_info.defined_in().clone().unwrap(), defined_in); + assert_eq!(interface_info.entity_kind(), EntityKind::Interface); + assert_eq!(interface_info.undefined_properties().len(), 2); + assert_eq!( + interface_info.undefined_properties().get("first").unwrap().clone(), + first_propery_value ); - assert!( - interface_info.undefined_properties().get("second").unwrap().clone() - == second_propery_value + assert_eq!( + interface_info.undefined_properties().get("second").unwrap().clone(), + second_propery_value ); } } diff --git a/dtdl-parser/src/model_parser.rs b/dtdl-parser/src/model_parser.rs index e0311a3b..d0541a48 100644 --- a/dtdl-parser/src/model_parser.rs +++ b/dtdl-parser/src/model_parser.rs @@ -256,12 +256,6 @@ impl ModelParser { let dtdl_2_context_value = self.retrieve_context(dtdl_2_context_path)?; self.replace_context_inline_in_doc(doc, "dtmi:dtdl:context;2", &dtdl_2_context_value)?; - let sdv_2_context_path_string = find_full_path("v2/context/SDV.v2.context.json")?; - let sdv_2_context_path_string_unwrapped = sdv_2_context_path_string; - let sdv_2_context_path = Path::new(&sdv_2_context_path_string_unwrapped); - let sdv_2_context_value = self.retrieve_context(sdv_2_context_path)?; - self.replace_context_inline_in_doc(doc, "dtmi:sdv:context;2", &sdv_2_context_value)?; - Ok(()) } @@ -1092,8 +1086,9 @@ mod model_parser_tests { model_dict_result.err().unwrap() ); let model_dict = model_dict_result.unwrap(); - assert!( - model_dict.len() == 31, + assert_eq!( + model_dict.len(), + 31, "expected length was 31, actual length is {}", model_dict.len() ); @@ -1101,16 +1096,16 @@ mod model_parser_tests { #[rustfmt::skip] #[test] - fn demo_validation_test() { + fn sdv_vehicle_validation_test() { set_dtdl_path(); let mut json_texts = Vec::::new(); - let demo_path_result = find_full_path("samples/demo_resources.json"); - assert!(demo_path_result.is_ok()); - let demo_contents_result = retrieve_dtdl(&demo_path_result.unwrap()); - assert!(demo_contents_result.is_ok()); - json_texts.push(demo_contents_result.unwrap()); + let vehicle_path_result = 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); @@ -1120,39 +1115,26 @@ mod model_parser_tests { model_dict_result.err().unwrap() ); let model_dict = model_dict_result.unwrap(); - assert!( - model_dict.len() == 13, - "expected length was 13, actual length is {}", - model_dict.len() - ); 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 ambient_air_temperature_uri_property_result = ambient_air_temperature_entity_result - .unwrap() - .undefined_properties() - .get("dtmi:sdv:property:uri;1"); - assert!(ambient_air_temperature_uri_property_result.is_some()); - let ambient_air_temperature_uri_property_value_result = - ambient_air_temperature_uri_property_result.unwrap().get("@value"); - assert!(ambient_air_temperature_uri_property_value_result.is_some()); - assert!(ambient_air_temperature_uri_property_value_result.unwrap() == "http://[::1]:40010"); // Devskim: ignore DS137138 - - let send_notification_id: Option = create_dtmi("dtmi:sdv:Vehicle:Cabin:HVAC:SendNotification;1"); - assert!(send_notification_id.is_some()); - let send_notification_entity_result = model_dict.get(&send_notification_id.unwrap()); - assert!(send_notification_entity_result.is_some()); - let send_notification_uri_property_result = send_notification_entity_result - .unwrap() - .undefined_properties() - .get("dtmi:sdv:property:uri;1"); - assert!(send_notification_uri_property_result.is_some()); - let send_notification_uri_property_value_result = - send_notification_uri_property_result.unwrap().get("@value"); - assert!(send_notification_uri_property_value_result.is_some()); - assert!(send_notification_uri_property_value_result.unwrap() == "http://[::1]:40010"); // Devskim: ignore DS137138 + + 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()); } } diff --git a/dtdl-parser/src/object_info_impl.rs b/dtdl-parser/src/object_info_impl.rs index f84ee1e1..f2fa6ec5 100644 --- a/dtdl-parser/src/object_info_impl.rs +++ b/dtdl-parser/src/object_info_impl.rs @@ -159,20 +159,21 @@ mod object_info_impl_tests { object_info.add_undefined_property(String::from("first"), first_propery_value.clone()); object_info.add_undefined_property(String::from("second"), second_propery_value.clone()); - assert!(object_info.dtdl_version() == 2); - assert!(object_info.id() == &id); + assert_eq!(object_info.dtdl_version(), 2); + assert_eq!(object_info.id(), &id); assert!(object_info.child_of().is_some()); - assert!(object_info.child_of().clone().unwrap() == child_of); + assert_eq!(object_info.child_of().clone().unwrap(), child_of); assert!(object_info.defined_in().is_some()); - assert!(object_info.defined_in().clone().unwrap() == defined_in); - assert!(object_info.entity_kind() == EntityKind::Object); - assert!(object_info.undefined_properties().len() == 2); - assert!( - object_info.undefined_properties().get("first").unwrap().clone() == first_propery_value + assert_eq!(object_info.defined_in().clone().unwrap(), defined_in); + assert_eq!(object_info.entity_kind(), EntityKind::Object); + assert_eq!(object_info.undefined_properties().len(), 2); + assert_eq!( + object_info.undefined_properties().get("first").unwrap().clone(), + first_propery_value ); - assert!( - object_info.undefined_properties().get("second").unwrap().clone() - == second_propery_value + assert_eq!( + object_info.undefined_properties().get("second").unwrap().clone(), + second_propery_value ); match object_info.fields() { diff --git a/dtdl-parser/src/primitive_schema_info_impl.rs b/dtdl-parser/src/primitive_schema_info_impl.rs index 3e3b4ca7..6fae4032 100644 --- a/dtdl-parser/src/primitive_schema_info_impl.rs +++ b/dtdl-parser/src/primitive_schema_info_impl.rs @@ -136,19 +136,19 @@ mod primitive_schema_info_impl_tests { primitive_schema_info .add_undefined_property(String::from("second"), second_propery_value.clone()); - assert!(primitive_schema_info.dtdl_version() == 2); - assert!(primitive_schema_info.id() == &id); + assert_eq!(primitive_schema_info.dtdl_version(), 2); + assert_eq!(primitive_schema_info.id(), &id); assert!(primitive_schema_info.child_of().is_none()); assert!(primitive_schema_info.defined_in().is_none()); assert!(primitive_schema_info.entity_kind() == EntityKind::String); - assert!(primitive_schema_info.undefined_properties().len() == 2); - assert!( - primitive_schema_info.undefined_properties().get("first").unwrap().clone() - == first_propery_value + assert_eq!(primitive_schema_info.undefined_properties().len(), 2); + assert_eq!( + primitive_schema_info.undefined_properties().get("first").unwrap().clone(), + first_propery_value ); - assert!( - primitive_schema_info.undefined_properties().get("second").unwrap().clone() - == second_propery_value + assert_eq!( + primitive_schema_info.undefined_properties().get("second").unwrap().clone(), + second_propery_value ); } } diff --git a/dtdl-parser/src/property_info_impl.rs b/dtdl-parser/src/property_info_impl.rs index 6e5f3800..26140866 100644 --- a/dtdl-parser/src/property_info_impl.rs +++ b/dtdl-parser/src/property_info_impl.rs @@ -190,21 +190,21 @@ mod property_info_impl_tests { property_info.add_undefined_property(String::from("first"), first_propery_value.clone()); property_info.add_undefined_property(String::from("second"), second_propery_value.clone()); - assert!(property_info.dtdl_version() == 2); - assert!(property_info.id() == &id); + assert_eq!(property_info.dtdl_version(), 2); + assert_eq!(property_info.id(), &id); assert!(property_info.child_of().is_some()); - assert!(property_info.child_of().clone().unwrap() == child_of); + assert_eq!(property_info.child_of().clone().unwrap(), child_of); assert!(property_info.defined_in().is_some()); - assert!(property_info.defined_in().clone().unwrap() == defined_in); - assert!(property_info.entity_kind() == EntityKind::Property); - assert!(property_info.undefined_properties().len() == 2); - assert!( - property_info.undefined_properties().get("first").unwrap().clone() - == first_propery_value + assert_eq!(property_info.defined_in().clone().unwrap(), defined_in); + assert_eq!(property_info.entity_kind(), EntityKind::Property); + assert_eq!(property_info.undefined_properties().len(), 2); + assert_eq!( + property_info.undefined_properties().get("first").unwrap().clone(), + first_propery_value ); - assert!( - property_info.undefined_properties().get("second").unwrap().clone() - == second_propery_value + assert_eq!( + property_info.undefined_properties().get("second").unwrap().clone(), + second_propery_value ); match property_info.name() { diff --git a/dtdl-parser/src/relationship_info_impl.rs b/dtdl-parser/src/relationship_info_impl.rs index 838ff36c..caf4f4bc 100644 --- a/dtdl-parser/src/relationship_info_impl.rs +++ b/dtdl-parser/src/relationship_info_impl.rs @@ -193,21 +193,21 @@ mod relationship_info_impl_tests { relationship_info .add_undefined_property(String::from("second"), second_propery_value.clone()); - assert!(relationship_info.dtdl_version() == 2); - assert!(relationship_info.id() == &id); + assert_eq!(relationship_info.dtdl_version(), 2); + assert_eq!(relationship_info.id(), &id); assert!(relationship_info.child_of().is_some()); - assert!(relationship_info.child_of().clone().unwrap() == child_of); + assert_eq!(relationship_info.child_of().clone().unwrap(), child_of); assert!(relationship_info.defined_in().is_some()); - assert!(relationship_info.defined_in().clone().unwrap() == defined_in); - assert!(relationship_info.entity_kind() == EntityKind::Property); - assert!(relationship_info.undefined_properties().len() == 2); - assert!( - relationship_info.undefined_properties().get("first").unwrap().clone() - == first_propery_value + assert_eq!(relationship_info.defined_in().clone().unwrap(), defined_in); + assert_eq!(relationship_info.entity_kind(), EntityKind::Property); + assert_eq!(relationship_info.undefined_properties().len(), 2); + assert_eq!( + relationship_info.undefined_properties().get("first").unwrap().clone(), + first_propery_value ); - assert!( - relationship_info.undefined_properties().get("second").unwrap().clone() - == second_propery_value + assert_eq!( + relationship_info.undefined_properties().get("second").unwrap().clone(), + second_propery_value ); match relationship_info.name() { diff --git a/dtdl-parser/src/telemetry_info_impl.rs b/dtdl-parser/src/telemetry_info_impl.rs index b329f8f4..5f4c7f49 100644 --- a/dtdl-parser/src/telemetry_info_impl.rs +++ b/dtdl-parser/src/telemetry_info_impl.rs @@ -181,21 +181,21 @@ mod telemetry_info_impl_tests { telemetry_info.add_undefined_property(String::from("first"), first_propery_value.clone()); telemetry_info.add_undefined_property(String::from("second"), second_propery_value.clone()); - assert!(telemetry_info.dtdl_version() == 2); - assert!(telemetry_info.id() == &id); + assert_eq!(telemetry_info.dtdl_version(), 2); + assert_eq!(telemetry_info.id(), &id); assert!(telemetry_info.child_of().is_some()); - assert!(telemetry_info.child_of().clone().unwrap() == child_of); + assert_eq!(telemetry_info.child_of().clone().unwrap(), child_of); assert!(telemetry_info.defined_in().is_some()); - assert!(telemetry_info.defined_in().clone().unwrap() == defined_in); - assert!(telemetry_info.entity_kind() == EntityKind::Telemetry); - assert!(telemetry_info.undefined_properties().len() == 2); - assert!( - telemetry_info.undefined_properties().get("first").unwrap().clone() - == first_propery_value + assert_eq!(telemetry_info.defined_in().clone().unwrap(), defined_in); + assert_eq!(telemetry_info.entity_kind(), EntityKind::Telemetry); + assert_eq!(telemetry_info.undefined_properties().len(), 2); + assert_eq!( + telemetry_info.undefined_properties().get("first").unwrap().clone(), + first_propery_value ); - assert!( - telemetry_info.undefined_properties().get("second").unwrap().clone() - == second_propery_value + assert_eq!( + telemetry_info.undefined_properties().get("second").unwrap().clone(), + second_propery_value ); match telemetry_info.name() { diff --git a/dtdl/samples/remotely_accessible_resource.json b/dtdl/samples/remotely_accessible_resource.json deleted file mode 100644 index e7563183..00000000 --- a/dtdl/samples/remotely_accessible_resource.json +++ /dev/null @@ -1,22 +0,0 @@ - { - "@context": ["dtmi:dtdl:context;2", "dtmi:sdv:context;2"], - "@type": "Interface", - "@id": "dtmi:sdv:Vehicle:Cabin:HAVC;1", - "contents": [ - { - "@type": ["Property", "Temperature", "RemotelyAccessible"], - "name": "Cabin_AmbientAirTemperature", - "@id": "dtmi:sdv:Vehicle:Cabin:HAVC:AmbientAirTemperature;1", - "description": "The immediate surroundings air temperature (in Fahrenheit).", - "schema": "double", - "unit": "degreeFahrenheit", - "remote_access": [ - { - "@type": "Endpoint", - "uri": "http://[::1]:40010", - "operations": [ "Get", "Set", "Subscribe", "Unsubscribe" ] - } - ] - } - ] - } diff --git a/dtdl/v2/context/SDV.v2.context.json b/dtdl/v2/context/SDV.v2.context.json deleted file mode 100644 index 22a06907..00000000 --- a/dtdl/v2/context/SDV.v2.context.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "remote_access": { - "@id": "dtmi:sdv:property:remote_access;1", - "@type": "@vocab" - }, - "uri": { - "@id": "dtmi:sdv:property:uri;1" - }, - "operations": { - "@id": "dtmi:sdv:property:operations;1", - "@type": "@vocab" - } -} diff --git a/in-vehicle-digital-twin/src/digitaltwin_impl.rs b/in-vehicle-digital-twin/src/digitaltwin_impl.rs index bb325254..9b20f5fc 100644 --- a/in-vehicle-digital-twin/src/digitaltwin_impl.rs +++ b/in-vehicle-digital-twin/src/digitaltwin_impl.rs @@ -4,23 +4,19 @@ extern crate iref; -use dtdl_parser::model_parser::ModelParser; -use log::Level::Debug; -use log::{debug, info, log_enabled, warn}; +use log::{debug, info}; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -use proto::digitaltwin::digital_twin_server::DigitalTwin; -use proto::digitaltwin::{ - FindByIdRequest, FindByIdResponse, RegisterRequest, RegisterResponse, UnregisterRequest, - UnregisterResponse, +use proto::digital_twin::digital_twin_server::DigitalTwin; +use proto::digital_twin::{ + EntityAccessInfo, FindByIdRequest, FindByIdResponse, RegisterRequest, RegisterResponse, }; -use serde_json::Value; use std::collections::HashMap; use std::sync::Arc; use tonic::{Request, Response, Status}; #[derive(Debug, Default)] pub struct DigitalTwinImpl { - pub entity_map: Arc>>, + pub entity_access_info_map: Arc>>, } #[tonic::async_trait] @@ -33,37 +29,26 @@ impl DigitalTwin for DigitalTwinImpl { &self, request: Request, ) -> Result, Status> { - let request_inner = request.into_inner(); - let entity_id = request_inner.entity_id; + let entity_id = request.into_inner().id; info!("Received a find_by_id request for entity id {entity_id}"); - let dtdl; + let entity_access_info; // This block controls the lifetime of the lock. { - let lock: RwLockReadGuard> = self.entity_map.read(); - let val_option = lock.get(&entity_id); - let val = match val_option { - Some(v) => v, - None => { - return Err(Status::not_found(format!( - "Unable to find the DTDL for entity id {entity_id}" - ))) - } - }; + let lock: RwLockReadGuard> = + self.entity_access_info_map.read(); + entity_access_info = lock.get(&entity_id).cloned(); + } - dtdl = match serde_json::to_string_pretty(&val) { - Ok(content) => content, - Err(error) => { - return Err(Status::internal(format!( - "Unexpected error with the DTDL for entity id {entity_id}: {error:?}" - ))) - } - }; + info!("{entity_access_info:?}"); + + if entity_access_info.is_none() { + return Err(Status::not_found("Unable to find the entity with id {entity_id}")); } - let response = FindByIdResponse { dtdl }; + let response = FindByIdResponse { entity_access_info }; debug!("Responded to the find_by_id request."); @@ -79,13 +64,11 @@ impl DigitalTwin for DigitalTwinImpl { request: Request, ) -> Result, Status> { let request_inner = request.into_inner(); - let dtdl = request_inner.dtdl; - info!("Received a register request for the DTDL:\n{}", &dtdl); + for entity_access_info in &request_inner.entity_access_info_list { + info!("Received a register request for the the entity:\n{}", entity_access_info.id); - let register_each_one_result = self.register_each_one(&dtdl); - if let Err(error) = register_each_one_result { - return Err(Status::internal(error)); + self.register_entity(entity_access_info.clone())?; } let response = RegisterResponse {}; @@ -94,79 +77,29 @@ impl DigitalTwin for DigitalTwinImpl { Ok(Response::new(response)) } - - /// Unregister implementation. - /// - /// # Arguments - /// * `request` - Unregister request. - async fn unregister( - &self, - request: Request, - ) -> Result, Status> { - warn!("Got an unregister request: {request:?}"); - - Err(Status::unimplemented("unregister has not been implemented")) - } } impl DigitalTwinImpl { - /// This function assumes that an array of resources has been provided and that each resource in the array needs to be registered. - /// - /// # Arguments - /// * `dtdl` - The DTDL for the array. - #[allow(unused_variables)] - fn register_each_one(&self, dtdl: &str) -> Result<(), String> { - let doc: Value = match serde_json::from_str(dtdl) { - Ok(json) => json, - Err(error) => return Err(format!("Failed to parse the DTDL due to: {error:?}")), - }; - - match doc { - Value::Array(array) => { - for v in array.iter() { - self.register_one(v)? - } - } - _ => return Err(String::from("An unexpected item was encountered in the DTDL.")), - }; - - Ok(()) - } - - /// Register the resource specified in the the JSON doc. + /// Register the entity. /// /// # Arguments - /// * `doc` - The JSON doc that specifies the entity. - fn register_one(&self, doc: &Value) -> Result<(), String> { - let dtdl = match serde_json::to_string_pretty(&doc) { - Ok(content) => content, - Err(error) => { - return Err(format!("Failed to make the DTDL pretty due to: : {error:?}")) - } - }; - - let mut parser = ModelParser::new(); - let json_texts = vec![dtdl]; - - let model_dict_result = parser.parse(&json_texts); - if let Err(error) = model_dict_result { - return Err(format!("Failed to parse the DTDL due to: {error:?}")); - } - let model_dict = model_dict_result.unwrap(); - + /// * `entity` - The entity. + fn register_entity(&self, entity_access_info: EntityAccessInfo) -> Result<(), Status> { // This block controls the lifetime of the lock. { - let mut lock: RwLockWriteGuard> = self.entity_map.write(); - for id in model_dict.keys() { - lock.insert(id.to_string(), doc.clone()); - } + let mut lock: RwLockWriteGuard> = + self.entity_access_info_map.write(); + match lock.get(&entity_access_info.id) { + Some(_) => { + return Err(Status::unimplemented("The in-vehicle digital twin service does not yet support multiple registrations of the same entity.")); + } + None => { + lock.insert(entity_access_info.id.clone(), entity_access_info.clone()); + } + }; } - if log_enabled!(Debug) { - for id in model_dict.keys() { - debug!("Registered DTDL for id {id}"); - } - } + debug!("Registered entity {}", &entity_access_info.id); Ok(()) } @@ -175,79 +108,99 @@ impl DigitalTwinImpl { #[cfg(test)] mod digitaltwin_impl_tests { use super::*; - use ibeji_common::find_full_path; use ibeji_common_test::set_dtdl_path; - use std::fs; - use std::path::Path; - - fn retrieve_dtdl(file_path: &str) -> Result { - let path = Path::new(file_path); - let read_result = fs::read_to_string(path); - match read_result { - Ok(contents) => Ok(contents), - Err(error) => Err(format!("Unable to retrieve the DTDL due to: {error:?}")), - } - } + use proto::digital_twin::EndpointInfo; #[tokio::test] async fn find_by_id_test() { set_dtdl_path(); - // Note: We can use any valid JSON. We'll use samples/remotely_accessible_resource.json. - let dtdl_path_result = find_full_path("samples/remotely_accessible_resource.json"); - assert!(dtdl_path_result.is_ok()); - let dtdl_path = dtdl_path_result.unwrap(); - let dtdl_result = retrieve_dtdl(&dtdl_path); - assert!(dtdl_result.is_ok()); - let dtdl = dtdl_result.unwrap(); + let operations = vec![String::from("Subscribe"), String::from("Unsubscribe")]; - let dtdl_json_result = serde_json::from_str(&dtdl); - assert!(dtdl_json_result.is_ok()); - let dtdl_json = dtdl_json_result.unwrap(); + let endpoint_info = EndpointInfo { + protocol: String::from("grpc"), + uri: String::from("http://[::1]:40010"), // Devskim: ignore DS137138 + context: String::from("dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1"), + operations, + }; - let entity_id = String::from("dtmi::some_id"); + let entity_access_info = EntityAccessInfo { + name: String::from("AmbientAirTemperature"), + id: String::from("dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1"), + description: String::from("Ambient air temperature"), + endpoint_info_list: vec![endpoint_info], + }; - let entity_map = Arc::new(RwLock::new(HashMap::new())); + let entity_access_info_map = Arc::new(RwLock::new(HashMap::new())); - let digital_twin_impl = DigitalTwinImpl { entity_map: entity_map.clone() }; + let digital_twin_impl = + DigitalTwinImpl { entity_access_info_map: entity_access_info_map.clone() }; // This block controls the lifetime of the lock. { - let mut lock: RwLockWriteGuard> = entity_map.write(); - lock.insert(entity_id.clone(), dtdl_json); + let mut lock: RwLockWriteGuard> = + entity_access_info_map.write(); + lock.insert(entity_access_info.id.clone(), entity_access_info.clone()); } - let request = tonic::Request::new(FindByIdRequest { entity_id }); + let request = tonic::Request::new(FindByIdRequest { + id: String::from("dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1"), + }); let result = digital_twin_impl.find_by_id(request).await; assert!(result.is_ok()); let response = result.unwrap(); - let dtdl = response.into_inner().dtdl; - assert!(!dtdl.is_empty()); + let response_inner = response.into_inner(); + + assert!(response_inner.entity_access_info.is_some()); + + let response_entity_access_info = response_inner.entity_access_info.unwrap(); + + assert_eq!( + response_entity_access_info.id, + "dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1" + ); + assert_eq!(response_entity_access_info.endpoint_info_list.len(), 1); + assert_eq!( + response_entity_access_info.endpoint_info_list[0].uri, + "http://[::1]:40010" // Devskim: ignore DS137138 + ); } #[tokio::test] async fn register_test() { set_dtdl_path(); - let entity_map = Arc::new(RwLock::new(HashMap::new())); - let digital_twin_impl = DigitalTwinImpl { entity_map: entity_map.clone() }; + let endpoint_info = EndpointInfo { + protocol: String::from("grpc"), + uri: String::from("http://[::1]:40010"), // Devskim: ignore DS137138 + context: String::from("dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1"), + operations: vec![String::from("Subscribe"), String::from("Unsubscribe")], + }; + + let entity_access_info = EntityAccessInfo { + name: String::from("AmbientAirTemperature"), + id: String::from("dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1"), + description: String::from("Ambient air temperature"), + endpoint_info_list: vec![endpoint_info], + }; - let dtdl_path_result = find_full_path("samples/demo_resources.json"); - assert!(dtdl_path_result.is_ok()); - let dtdl_path = dtdl_path_result.unwrap(); - let dtdl_result = retrieve_dtdl(&dtdl_path); - assert!(dtdl_result.is_ok()); - let dtdl = dtdl_result.unwrap(); + let entity_access_info_map = Arc::new(RwLock::new(HashMap::new())); - let request = tonic::Request::new(RegisterRequest { dtdl }); + let digital_twin_impl = + DigitalTwinImpl { entity_access_info_map: entity_access_info_map.clone() }; + + let request = tonic::Request::new(RegisterRequest { + entity_access_info_list: vec![entity_access_info], + }); let result = digital_twin_impl.register(request).await; - assert!(result.is_ok()); + assert!(result.is_ok(), "register result is not okay: {result:?}"); // This block controls the lifetime of the lock. { - let lock: RwLockReadGuard> = entity_map.read(); + let lock: RwLockReadGuard> = + entity_access_info_map.read(); // Make sure that we populated the entity map from the contents of the DTDL. - assert!(lock.len() == 13, "expected length was 13, actual length is {}", lock.len()); + assert_eq!(lock.len(), 1, "expected length was 1, actual length is {}", lock.len()); } } } diff --git a/in-vehicle-digital-twin/src/main.rs b/in-vehicle-digital-twin/src/main.rs index 30f37b66..3b6a6be8 100644 --- a/in-vehicle-digital-twin/src/main.rs +++ b/in-vehicle-digital-twin/src/main.rs @@ -5,15 +5,13 @@ use env_logger::{Builder, Target}; use log::{debug, info, LevelFilter}; use parking_lot::RwLock; -use proto::digitaltwin::digital_twin_server::DigitalTwinServer; -use proto::provider::provider_server::ProviderServer; +use proto::digital_twin::digital_twin_server::DigitalTwinServer; use std::collections::HashMap; use std::net::SocketAddr; use std::sync::Arc; use tonic::transport::Server; mod digitaltwin_impl; -mod provider_impl; const IN_VEHICLE_DIGITAL_TWIN_ADDR: &str = "[::1]:50010"; @@ -26,13 +24,11 @@ async fn main() -> Result<(), Box> { // Setup the HTTP server. let addr: SocketAddr = IN_VEHICLE_DIGITAL_TWIN_ADDR.parse()?; - let provider_impl = provider_impl::ProviderImpl::default(); - let digitaltwin_impl = - digitaltwin_impl::DigitalTwinImpl { entity_map: Arc::new(RwLock::new(HashMap::new())) }; - let server_future = Server::builder() - .add_service(ProviderServer::new(provider_impl)) - .add_service(DigitalTwinServer::new(digitaltwin_impl)) - .serve(addr); + let digitaltwin_impl = digitaltwin_impl::DigitalTwinImpl { + entity_access_info_map: Arc::new(RwLock::new(HashMap::new())), + }; + let server_future = + Server::builder().add_service(DigitalTwinServer::new(digitaltwin_impl)).serve(addr); info!("The HTTP server is listening on address '{IN_VEHICLE_DIGITAL_TWIN_ADDR}'"); server_future.await?; diff --git a/in-vehicle-digital-twin/src/provider_impl.rs b/in-vehicle-digital-twin/src/provider_impl.rs deleted file mode 100644 index b1d0b380..00000000 --- a/in-vehicle-digital-twin/src/provider_impl.rs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -use log::warn; -use proto::provider::provider_server::Provider; -use proto::provider::{ - GetRequest, GetResponse, InvokeRequest, InvokeResponse, SetRequest, SetResponse, - SubscribeRequest, SubscribeResponse, UnsubscribeRequest, UnsubscribeResponse, -}; -use tonic::{Request, Response, Status}; - -#[derive(Debug, Default)] -pub struct ProviderImpl {} - -#[tonic::async_trait] -impl Provider for ProviderImpl { - /// Subscribe implementation. - /// - /// # Arguments - /// * `request` - Subscribe request. - async fn subscribe( - &self, - request: Request, - ) -> Result, Status> { - warn!("Got a 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> { - warn!("Got a get request: {request:?}"); - - Err(Status::unimplemented("get has not been implemented")) - } - - /// Set implementation. - /// - /// # Arguments - /// * `request` - Set request. - async fn set(&self, request: Request) -> Result, Status> { - warn!("Got a set request: {request:?}"); - - Err(Status::unimplemented("set has not been implemented")) - } - - /// 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")) - } -} diff --git a/proto/Cargo.toml b/proto/Cargo.toml index 743a3cb5..418ea7a2 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -9,9 +9,10 @@ edition = "2021" license = "MIT" [dependencies] -tonic = { workspace = true } prost = { workspace = true } +serde = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +tonic = { workspace = true } [build-dependencies] tonic-build = { workspace = true } diff --git a/proto/build.rs b/proto/build.rs index 22b69677..8f95dd99 100644 --- a/proto/build.rs +++ b/proto/build.rs @@ -3,8 +3,10 @@ // SPDX-License-Identifier: MIT fn main() -> Result<(), Box> { - tonic_build::compile_protos("consumer.proto")?; - tonic_build::compile_protos("provider.proto")?; - tonic_build::compile_protos("digitaltwin.proto")?; + tonic_build::configure() + .message_attribute("EndpointInfo", "#[derive(serde::Deserialize, serde::Serialize)]") + .message_attribute("EntityAccessInfo", "#[derive(serde::Deserialize, serde::Serialize)]") + .compile(&["digital_twin/v1/digital_twin.proto"], &["digital_twin/v1/"])?; + Ok(()) } diff --git a/proto/digital_twin/v1/digital_twin.proto b/proto/digital_twin/v1/digital_twin.proto new file mode 100644 index 00000000..274c5933 --- /dev/null +++ b/proto/digital_twin/v1/digital_twin.proto @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +syntax = "proto3"; + +package digital_twin; + +service DigitalTwin { + rpc FindById (FindByIdRequest) returns (FindByIdResponse); + rpc Register (RegisterRequest) returns (RegisterResponse); +} + +message EndpointInfo { + string protocol = 1; + repeated string operations = 2; + string uri = 3; + string context = 4; +} + +message EntityAccessInfo { + string name = 1; + string id = 2; + string description = 3; + repeated EndpointInfo endpointInfoList = 4; +} + +message FindByIdRequest { + string id = 1; +} + +message FindByIdResponse { + EntityAccessInfo entityAccessInfo = 1; +} + +message RegisterRequest { + repeated EntityAccessInfo entityAccessInfoList = 1; +} + +message RegisterResponse { +} diff --git a/proto/digitaltwin.proto b/proto/digitaltwin.proto deleted file mode 100644 index da72627e..00000000 --- a/proto/digitaltwin.proto +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -syntax = "proto3"; - -package digitaltwin; - -service DigitalTwin { - rpc FindById (FindByIdRequest) returns (FindByIdResponse); - rpc Register (RegisterRequest) returns (RegisterResponse); - rpc Unregister (UnregisterRequest) returns (UnregisterResponse); -} - -message FindByIdRequest { - string entity_id = 1; -} - -message FindByIdResponse { - string dtdl = 1; -} - -message RegisterRequest { - string dtdl = 1; -} - -message RegisterResponse { -} - -message UnregisterRequest { - string entity_id = 1; -} - -message UnregisterResponse { -} \ No newline at end of file diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 1b717619..07e147e6 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -2,20 +2,7 @@ // Licensed under the MIT license. // SPDX-License-Identifier: MIT -pub mod consumer { +pub mod digital_twin { #![allow(clippy::derive_partial_eq_without_eq)] - - tonic::include_proto!("consumer"); -} - -pub mod digitaltwin { - #![allow(clippy::derive_partial_eq_without_eq)] - - tonic::include_proto!("digitaltwin"); -} - -pub mod provider { - #![allow(clippy::derive_partial_eq_without_eq)] - - tonic::include_proto!("provider"); + tonic::include_proto!("digital_twin"); } diff --git a/samples/command/consumer/Cargo.toml b/samples/command/consumer/Cargo.toml index 6c60ea36..a9d4706d 100644 --- a/samples/command/consumer/Cargo.toml +++ b/samples/command/consumer/Cargo.toml @@ -10,14 +10,14 @@ license = "MIT" [dependencies] async-std = { workspace = true, features = ["attributes"] } -dtdl-parser = { path = "../../../dtdl-parser" } -dt-model-identifiers = { path = "../../../dt-model/dt-model-identifiers" } +digital-twin-model = { path = "../../../digital-twin-model" } env_logger= { workspace = true } iref = { workspace = true } json-ld = { git = "https://github.com/blast-hardcheese/json-ld", branch = "resolve-issue-40" } log = { workspace = true } prost = { workspace = true } -proto = { path = "../../../proto" } +samples_common = { path = "../../common" } +samples_proto = { path = "../../proto" } serde_json = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tonic = { workspace = true } diff --git a/samples/command/consumer/src/consumer_impl.rs b/samples/command/consumer/src/consumer_impl.rs index 96d1e254..2577fbb8 100644 --- a/samples/command/consumer/src/consumer_impl.rs +++ b/samples/command/consumer/src/consumer_impl.rs @@ -3,15 +3,17 @@ // SPDX-License-Identifier: MIT use log::{info, warn}; -use proto::consumer::consumer_server::Consumer; -use proto::consumer::{PublishRequest, PublishResponse, RespondRequest, RespondResponse}; +use samples_proto::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_server::DigitalTwinConsumer; +use samples_proto::sample_grpc::v1::digital_twin_consumer::{ + PublishRequest, PublishResponse, RespondRequest, RespondResponse, +}; use tonic::{Request, Response, Status}; #[derive(Debug, Default)] pub struct ConsumerImpl {} #[tonic::async_trait] -impl Consumer for ConsumerImpl { +impl DigitalTwinConsumer for ConsumerImpl { /// Publish implementation. /// /// # Arguments diff --git a/samples/command/consumer/src/main.rs b/samples/command/consumer/src/main.rs index 61473c02..e053c118 100644 --- a/samples/command/consumer/src/main.rs +++ b/samples/command/consumer/src/main.rs @@ -4,23 +4,20 @@ mod consumer_impl; -use dt_model_identifiers::sdv_v1 as sdv; -use dtdl_parser::dtmi::{create_dtmi, Dtmi}; -use dtdl_parser::model_parser::ModelParser; +use digital_twin_model::sdv_v1 as sdv; use env_logger::{Builder, Target}; use log::{debug, info, warn, LevelFilter}; -use proto::consumer::consumer_server::ConsumerServer; -use proto::digitaltwin::digital_twin_client::DigitalTwinClient; -use proto::digitaltwin::FindByIdRequest; -use proto::provider::provider_client::ProviderClient; -use proto::provider::InvokeRequest; +use samples_common::{digital_twin_operation, digital_twin_protocol, find_provider_endpoint}; +use samples_proto::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_server::DigitalTwinConsumerServer; +use samples_proto::sample_grpc::v1::digital_twin_provider::digital_twin_provider_client::DigitalTwinProviderClient; +use samples_proto::sample_grpc::v1::digital_twin_provider::InvokeRequest; use std::net::SocketAddr; use tokio::time::{sleep, Duration}; use tonic::transport::Server; use uuid::Uuid; const IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI: &str = "http://[::1]:50010"; // Devskim: ignore DS137138 -const CONSUMER_ADDR: &str = "[::1]:60010"; +const CONSUMER_AUTHORITY: &str = "[::1]:60010"; /// Start the show notification repeater. /// @@ -31,12 +28,12 @@ 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 = String::from("The show-notification request."); + 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); - let client_result = ProviderClient::connect(provider_uri.clone()).await; + 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; @@ -47,9 +44,8 @@ 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: String::from( - sdv::vehicle::cabin::infotainment::hmi::show_notification::ID, - ), + entity_id: sdv::vehicle::cabin::infotainment::hmi::show_notification::ID + .to_string(), consumer_uri: consumer_uri.clone(), response_id, payload, @@ -76,66 +72,26 @@ async fn main() -> Result<(), Box> { info!("The Consumer has started."); // Setup the HTTP server. - let consumer_authority = String::from(CONSUMER_ADDR); - let addr: SocketAddr = consumer_authority.parse()?; + let addr: SocketAddr = CONSUMER_AUTHORITY.parse()?; let consumer_impl = consumer_impl::ConsumerImpl::default(); let server_future = - Server::builder().add_service(ConsumerServer::new(consumer_impl)).serve(addr); - info!("The HTTP server is listening on address '{CONSUMER_ADDR}'"); - - // Obtain the DTDL for the send_notification command. - info!("Sending a find_by_id request for entity id {} to the In-Vehicle Digital Twin Service URI {IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI}", - sdv::vehicle::cabin::infotainment::hmi::show_notification::ID); - let mut client = DigitalTwinClient::connect(IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI).await?; - let request = tonic::Request::new(FindByIdRequest { - entity_id: String::from(sdv::vehicle::cabin::infotainment::hmi::show_notification::ID), - }); - let response = client.find_by_id(request).await?; - let dtdl = response.into_inner().dtdl; - debug!("Received the response for the find_by_id request"); - - debug!("Parsing the DTDL."); - let mut parser = ModelParser::new(); - let json_texts = vec![dtdl]; - let model_dict_result = parser.parse(&json_texts); - if let Err(error) = model_dict_result { - panic!("Failed to parse the DTDL: {error}"); - } - let model_dict = model_dict_result.unwrap(); - debug!("The DTDL parser has successfully parsed the DTDL"); - - // Create the id (as a DTMI) for the show-notification command. - let show_notification_command_id: Option = - create_dtmi(sdv::vehicle::cabin::infotainment::hmi::show_notification::ID); - if show_notification_command_id.is_none() { - panic!("Unable to create the dtmi"); - } - - // Get the entity from the DTDL for the show-notification command. - let entity_result = model_dict.get(&show_notification_command_id.unwrap()); - if entity_result.is_none() { - panic!("Unable to find the entity"); - } - let entity = entity_result.unwrap(); - - // Get the URI property from the entity. - let uri_property_result = entity.undefined_properties().get(sdv::property::uri::ID); - if uri_property_result.is_none() { - panic!("Unable to find the URI property"); - } - let uri_property = uri_property_result.unwrap(); - - // Get the value for the URI property. - let uri_property_value_result = uri_property.get("@value"); - if uri_property_value_result.is_none() { - panic!("Unable to find the value for the URI for the show-notification's provider."); - } - let uri_property_value = uri_property_value_result.unwrap(); - let uri_str_option = uri_property_value.as_str(); - let provider_uri = String::from(uri_str_option.unwrap()); - info!("The URI for the show-notification command's provider is {provider_uri}"); - - let consumer_uri = format!("http://{CONSUMER_ADDR}"); // Devskim: ignore DS137138 + Server::builder().add_service(DigitalTwinConsumerServer::new(consumer_impl)).serve(addr); + info!("The HTTP server is listening on address '{CONSUMER_AUTHORITY}'"); + + let provider_endpoint_info = find_provider_endpoint( + IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI, + sdv::vehicle::cabin::infotainment::hmi::show_notification::ID, + digital_twin_protocol::GRPC, + &[digital_twin_operation::INVOKE.to_string()], + ) + .await + .unwrap(); + + let provider_uri = provider_endpoint_info.uri; + + info!("The URI for the ShowNotification command's provider is {provider_uri}"); + + let consumer_uri = format!("http://{CONSUMER_AUTHORITY}"); // Devskim: ignore DS137138 start_show_notification_repeater(provider_uri, consumer_uri); diff --git a/samples/command/dtdl/content/show_notification.json b/samples/command/dtdl/content/show_notification.json deleted file mode 100644 index 7f69cb1e..00000000 --- a/samples/command/dtdl/content/show_notification.json +++ /dev/null @@ -1,28 +0,0 @@ -[ - { - "@context": ["dtmi:dtdl:context;2", "dtmi:sdv:context;2"], - "@type": "Interface", - "@id": "dtmi:sdv:Vehicle:Cabin:Infotainment:HMI;1", - "description": "The Human Machine Interface.", - "contents": [ - { - "@type": ["Command", "RemotelyAccessible"], - "@id": "dtmi:sdv:Vehicle:Cabin:Infotainment:HMI:ShowNotification;1", - "name": "ShowNotification", - "request": { - "name": "ShowNotification", - "displayName": "Show Notification", - "descriptiption": "Show a notification on the HMI.", - "schema": "string" - }, - "remote_access": [ - { - "@type": "Endpoint", - "uri": "http://[::1]:40010", - "operations": [ "Invoke" ] - } - ] - } - ] - } -] \ No newline at end of file diff --git a/samples/command/provider/Cargo.toml b/samples/command/provider/Cargo.toml index bb2d8040..61eb2773 100644 --- a/samples/command/provider/Cargo.toml +++ b/samples/command/provider/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" [dependencies] async-std = { workspace = true, features = ["attributes"] } -dt-model-identifiers = { path = "../../../dt-model/dt-model-identifiers" } +digital-twin-model = { path = "../../../digital-twin-model" } env_logger= { workspace = true } ibeji-common = { path = "../../../common" } json-ld = { git = "https://github.com/blast-hardcheese/json-ld", branch = "resolve-issue-40" } @@ -18,6 +18,8 @@ log = { workspace = true} parking_lot = { workspace = true } prost = { workspace = true } proto = { path = "../../../proto" } +samples_common = { path = "../../common" } +samples_proto = { path = "../../proto" } serde_json = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tonic = { workspace = true } diff --git a/samples/command/provider/src/main.rs b/samples/command/provider/src/main.rs index 4c02b0ea..0b1c49fd 100644 --- a/samples/command/provider/src/main.rs +++ b/samples/command/provider/src/main.rs @@ -4,13 +4,14 @@ mod provider_impl; +use digital_twin_model::sdv_v1 as sdv; use env_logger::{Builder, Target}; -use ibeji_common::{find_full_path, retrieve_dtdl}; use log::{debug, info, LevelFilter}; use parking_lot::Mutex; -use proto::digitaltwin::digital_twin_client::DigitalTwinClient; -use proto::digitaltwin::RegisterRequest; -use proto::provider::provider_server::ProviderServer; +use proto::digital_twin::digital_twin_client::DigitalTwinClient; +use proto::digital_twin::{EndpointInfo, EntityAccessInfo, RegisterRequest}; +use samples_common::{digital_twin_operation, digital_twin_protocol}; +use samples_proto::sample_grpc::v1::digital_twin_provider::digital_twin_provider_server::DigitalTwinProviderServer; use std::net::SocketAddr; use std::sync::Arc; use tonic::transport::Server; @@ -18,7 +19,7 @@ use tonic::transport::Server; use crate::provider_impl::{ProviderImpl, SubscriptionMap}; const IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI: &str = "http://[::1]:50010"; // Devskim: ignore DS137138 -const PROVIDER_ADDR: &str = "[::1]:40010"; +const PROVIDER_AUTHORITY: &str = "[::1]:40010"; #[tokio::main] #[allow(clippy::collapsible_else_if)] @@ -28,22 +29,32 @@ async fn main() -> Result<(), Box> { info!("The Provider has started."); - debug!("Preparing the Provider's DTDL."); - let provider_dtdl_path = find_full_path("content/show_notification.json")?; - let dtdl = retrieve_dtdl(&provider_dtdl_path)?; - debug!("Prepared the Provider's DTDL."); + let endpoint_info = EndpointInfo { + protocol: digital_twin_protocol::GRPC.to_string(), + operations: vec![digital_twin_operation::INVOKE.to_string()], + uri: "http://[::1]:40010".to_string(), // Devskim: ignore DS137138 + context: sdv::vehicle::cabin::infotainment::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(), + endpoint_info_list: vec![endpoint_info], + }; // Setup the HTTP server. - let addr: SocketAddr = PROVIDER_ADDR.parse()?; + 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 server_future = - Server::builder().add_service(ProviderServer::new(provider_impl)).serve(addr); - info!("The HTTP server is listening on address '{PROVIDER_ADDR}'"); + 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 with the Provider's DTDL to the In-Vehicle Digital Twin Service URI {IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI}"); let mut client = DigitalTwinClient::connect(IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI).await?; - let request = tonic::Request::new(RegisterRequest { dtdl }); + let request = + tonic::Request::new(RegisterRequest { entity_access_info_list: vec![entity_access_info] }); let _response = client.register(request).await?; debug!("The Provider's DTDL has been registered."); diff --git a/samples/command/provider/src/provider_impl.rs b/samples/command/provider/src/provider_impl.rs index bd29774c..6ded9258 100644 --- a/samples/command/provider/src/provider_impl.rs +++ b/samples/command/provider/src/provider_impl.rs @@ -4,10 +4,12 @@ use log::{info, warn}; use parking_lot::Mutex; -use proto::consumer::{consumer_client::ConsumerClient, RespondRequest}; -use proto::provider::{ - provider_server::Provider, GetRequest, GetResponse, InvokeRequest, InvokeResponse, SetRequest, - SetResponse, SubscribeRequest, SubscribeResponse, UnsubscribeRequest, UnsubscribeResponse, +use samples_proto::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_client::DigitalTwinConsumerClient; +use samples_proto::sample_grpc::v1::digital_twin_consumer::RespondRequest; +use samples_proto::sample_grpc::v1::digital_twin_provider::digital_twin_provider_server::DigitalTwinProvider; +use samples_proto::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; @@ -21,7 +23,7 @@ pub struct ProviderImpl { } #[tonic::async_trait] -impl Provider for ProviderImpl { +impl DigitalTwinProvider for ProviderImpl { /// Subscribe implementation. /// /// # Arguments @@ -89,11 +91,9 @@ impl Provider for ProviderImpl { info!("Notification: '{payload}'"); tokio::spawn(async move { - let client_result = ConsumerClient::connect(consumer_uri.clone()).await; - if client_result.is_err() { - return Err(Status::internal(format!("{:?}", client_result.unwrap_err()))); - } - let mut client = client_result.unwrap(); + let mut client = DigitalTwinConsumerClient::connect(consumer_uri.clone()) + .await + .map_err(|error| Status::internal(error.to_string()))?; let respond_request = tonic::Request::new(RespondRequest { entity_id: entity_id.clone(), diff --git a/dt-model/dt-model-identifiers/src/Cargo.toml b/samples/common/Cargo.toml similarity index 61% rename from dt-model/dt-model-identifiers/src/Cargo.toml rename to samples/common/Cargo.toml index e250d14a..9f3ee0df 100644 --- a/dt-model/dt-model-identifiers/src/Cargo.toml +++ b/samples/common/Cargo.toml @@ -3,10 +3,12 @@ # SPDX-License-Identifier: MIT [package] -name = "model-identifiers" +name = "samples_common" version = "0.1.0" edition = "2021" license = "MIT" [dependencies] - +log = { workspace = true } +proto = { path = "../../proto" } +tonic = { workspace = true } \ No newline at end of file diff --git a/samples/common/src/lib.rs b/samples/common/src/lib.rs new file mode 100644 index 00000000..6c44676d --- /dev/null +++ b/samples/common/src/lib.rs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +use log::{debug, info}; +use proto::digital_twin::digital_twin_client::DigitalTwinClient; +use proto::digital_twin::{EndpointInfo, FindByIdRequest}; + +/// Supported digital twin operations. +pub mod digital_twin_operation { + pub const GET: &str = "Get"; + pub const SET: &str = "Set"; + pub const SUBSCRIBE: &str = "Subscribe"; + pub const UNSUBSCRIBE: &str = "Unsubscribe"; + pub const INVOKE: &str = "Invoke"; +} + +// Supported gitial twin protocols. +pub mod digital_twin_protocol { + pub const GRPC: &str = "grpc"; +} + +/// Is the provided subset a subset of the provided superset? +/// +/// # Arguments +/// `subset` - The provided subset. +/// `superset` - The provided superset. +pub fn is_subset(subset: &[String], superset: &[String]) -> bool { + subset.iter().all(|subset_member| { + superset.iter().any(|supserset_member| subset_member == supserset_member) + }) +} + +/// Find a provider endpoint that satifies the requirements. +/// +/// # Arguments +/// `in_vehcile_digitial_twin_servuce_uri` - iI-vehicle digital twin service URI. +/// `entity_id` - The matching entity id. +/// `protocol` - The required protocol. +/// `operations` - The required operations. +pub async fn find_provider_endpoint( + in_vehicle_digitial_twin_servuce_uri: &'static str, + entity_id: &str, + protocol: &str, + operations: &[String], +) -> Result { + info!("Sending a find_by_id request for entity id {entity_id} to the In-Vehicle Digital Twin Service URI {in_vehicle_digitial_twin_servuce_uri}"); + let mut client = DigitalTwinClient::connect(in_vehicle_digitial_twin_servuce_uri) + .await + .map_err(|error| format!("{error}"))?; + let request = tonic::Request::new(FindByIdRequest { id: entity_id.to_string() }); + let response = client.find_by_id(request).await.map_err(|error| error.to_string())?; + let response_inner = response.into_inner(); + debug!("Received the response for the find_by_id request"); + info!("response_payload: {:?}", response_inner.entity_access_info); + + let entity_access_info = response_inner.entity_access_info.expect("Did not find the entity"); + + let mut matching_endpoint_info_option: Option = None; + for endpoint_info in entity_access_info.endpoint_info_list { + // We require and endpoint that supports the protocol and supports all of the operations. + if endpoint_info.protocol == protocol + && is_subset(operations, endpoint_info.operations.as_slice()) + { + matching_endpoint_info_option = Some(endpoint_info); + break; + } + } + + if matching_endpoint_info_option.is_none() { + return Err("Did not find an endpoint that met our requirements".to_string()); + } + + let result = matching_endpoint_info_option.unwrap(); + + info!("Found a matching endpoint for entity id {entity_id} that has URI {}", result.uri); + + Ok(result) +} + +#[cfg(test)] +mod ibeji_common_tests { + use super::*; + + #[test] + fn is_subset_test() { + assert!(is_subset(&[], &[])); + assert!(is_subset(&[], &["one".to_string()])); + assert!(is_subset(&[], &["one".to_string(), "two".to_string()])); + assert!(is_subset(&["one".to_string()], &["one".to_string(), "two".to_string()])); + assert!(is_subset( + &["one".to_string(), "two".to_string()], + &["one".to_string(), "two".to_string()] + )); + assert!(!is_subset( + &["one".to_string(), "two".to_string(), "three".to_string()], + &["one".to_string(), "two".to_string()] + )); + assert!(!is_subset( + &["one".to_string(), "two".to_string(), "three".to_string()], + &["one".to_string()] + )); + assert!(!is_subset(&["one".to_string(), "two".to_string(), "three".to_string()], &[])); + } +} diff --git a/samples/mixed/consumer/Cargo.toml b/samples/mixed/consumer/Cargo.toml index e1cdc2fe..783834ea 100644 --- a/samples/mixed/consumer/Cargo.toml +++ b/samples/mixed/consumer/Cargo.toml @@ -10,14 +10,15 @@ license = "MIT" [dependencies] async-std = { workspace = true, features = ["attributes"] } -dtdl-parser = { path = "../../../dtdl-parser" } -dt-model-identifiers = { path = "../../../dt-model/dt-model-identifiers" } +digital-twin-model = { path = "../../../digital-twin-model" } env_logger= { workspace = true } iref = { workspace = true } json-ld = { git = "https://github.com/blast-hardcheese/json-ld", branch = "resolve-issue-40" } log = { workspace = true } prost = { workspace = true } -proto = { path = "../../../proto" } +# proto = { path = "../../../proto" } +samples_common = { path = "../../common" } +samples_proto = { path = "../../proto" } serde_json = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tonic = { workspace = true } diff --git a/samples/mixed/consumer/src/consumer_impl.rs b/samples/mixed/consumer/src/consumer_impl.rs index 2fe6637c..2a8b457f 100644 --- a/samples/mixed/consumer/src/consumer_impl.rs +++ b/samples/mixed/consumer/src/consumer_impl.rs @@ -3,15 +3,17 @@ // SPDX-License-Identifier: MIT use log::info; -use proto::consumer::consumer_server::Consumer; -use proto::consumer::{PublishRequest, PublishResponse, RespondRequest, RespondResponse}; +use samples_proto::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_server::DigitalTwinConsumer; +use samples_proto::sample_grpc::v1::digital_twin_consumer::{ + PublishRequest, PublishResponse, RespondRequest, RespondResponse, +}; use tonic::{Request, Response, Status}; #[derive(Debug, Default)] pub struct ConsumerImpl {} #[tonic::async_trait] -impl Consumer for ConsumerImpl { +impl DigitalTwinConsumer for ConsumerImpl { /// Publish implementation. /// /// # Arguments diff --git a/samples/mixed/consumer/src/main.rs b/samples/mixed/consumer/src/main.rs index 7da83a2b..f7e06c29 100644 --- a/samples/mixed/consumer/src/main.rs +++ b/samples/mixed/consumer/src/main.rs @@ -4,16 +4,15 @@ mod consumer_impl; -use dt_model_identifiers::sdv_v1 as sdv; -use dtdl_parser::dtmi::{create_dtmi, Dtmi}; -use dtdl_parser::model_parser::ModelParser; +use digital_twin_model::sdv_v1 as sdv; use env_logger::{Builder, Target}; use log::{debug, info, warn, LevelFilter}; -use proto::consumer::consumer_server::ConsumerServer; -use proto::digitaltwin::digital_twin_client::DigitalTwinClient; -use proto::digitaltwin::FindByIdRequest; -use proto::provider::provider_client::ProviderClient; -use proto::provider::{InvokeRequest, SetRequest, SubscribeRequest}; +use samples_common::{digital_twin_operation, digital_twin_protocol, find_provider_endpoint}; +use samples_proto::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_server::DigitalTwinConsumerServer; +use samples_proto::sample_grpc::v1::digital_twin_provider::digital_twin_provider_client::DigitalTwinProviderClient; +use samples_proto::sample_grpc::v1::digital_twin_provider::{ + InvokeRequest, SetRequest, SubscribeRequest, +}; use std::net::SocketAddr; use tokio::time::{sleep, Duration}; use tonic::transport::Server; @@ -21,7 +20,7 @@ use uuid::Uuid; const IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI: &str = "http://[::1]:50010"; // Devskim: ignore DS137138 -const CONSUMER_ADDR: &str = "[::1]:60010"; +const CONSUMER_AUTHORITY: &str = "[::1]:60010"; /// Start the show-notification repeater. /// @@ -32,12 +31,12 @@ 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 = String::from("show-notification request"); + let payload: String = "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); - let client_result = ProviderClient::connect(provider_uri.clone()).await; + 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; @@ -48,9 +47,8 @@ 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: String::from( - sdv::vehicle::cabin::infotainment::hmi::show_notification::ID, - ), + entity_id: sdv::vehicle::cabin::infotainment::hmi::show_notification::ID + .to_string(), consumer_uri: consumer_uri.clone(), response_id, payload, @@ -72,7 +70,7 @@ fn start_show_notification_repeater(provider_uri: String, consumer_uri: String) /// Start the activate-air-conditioing repeater. /// /// # Arguments -/// `provider_uri` - The provider_uri.. +/// `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 { @@ -81,7 +79,7 @@ fn start_activate_air_conditioning_repeater(provider_uri: String) { 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); - let client_result = ProviderClient::connect(provider_uri.clone()).await; + 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; @@ -92,7 +90,7 @@ fn start_activate_air_conditioning_repeater(provider_uri: String) { let value: String = format!("{is_active}"); let request = tonic::Request::new(SetRequest { - entity_id: String::from(sdv::vehicle::cabin::hvac::is_air_conditioning_active::ID), + entity_id: sdv::vehicle::cabin::hvac::is_air_conditioning_active::ID.to_string(), value, }); @@ -109,69 +107,21 @@ fn start_activate_air_conditioning_repeater(provider_uri: String) { }); } -async fn get_provider_uri(entity_id: &str) -> Result { - // Obtain the DTDL for the send_notification command. - info!("Sending a find_by_id request for entity id {entity_id} to the In-Vehicle Digital Twin Service URI {IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI}"); - let mut client = DigitalTwinClient::connect(IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI) - .await - .map_err(|error| format!("{error}"))?; - let request = tonic::Request::new(FindByIdRequest { entity_id: String::from(entity_id) }); - let response = client.find_by_id(request).await.map_err(|error| format!("{error}"))?; - let dtdl = response.into_inner().dtdl; - debug!("Received the response for the find_by_id request"); - - debug!("Parsing the DTDL."); - let mut parser = ModelParser::new(); - let json_texts = vec![dtdl]; - let model_dict_result = parser.parse(&json_texts); - if let Err(error) = model_dict_result { - return Err(format!("Failed to parse the DTDL: {error}")); - } - let model_dict = model_dict_result.unwrap(); - debug!("The DTDL parser has successfully parsed the DTDL"); - - // Create the id (as a DTMI) for the send_notification command. - let dtmi_id: Option = create_dtmi(entity_id); - if dtmi_id.is_none() { - return Err(String::from("Unable to create the dtmi")); - } - - // Get the entity from the DTDL for the dtmi id. - let entity_result = model_dict.get(&dtmi_id.unwrap()); - if entity_result.is_none() { - return Err(String::from("Unable to find the entity")); - } - let entity = entity_result.unwrap(); - - // Get the URI property from the entity. - let uri_property_result = entity.undefined_properties().get(sdv::property::uri::ID); - if uri_property_result.is_none() { - return Err(String::from("Unable to find the URI property")); - } - let uri_property = uri_property_result.unwrap(); - - // Get the value for the URI property. - let uri_property_value_result = uri_property.get("@value"); - if uri_property_value_result.is_none() { - return Err(String::from("Unable to find the value for the URI.")); - } - let uri_property_value = uri_property_value_result.unwrap(); - let uri_str_option = uri_property_value.as_str(); - let provider_uri = String::from(uri_str_option.unwrap()); - info!("The provider URI for entity id {entity_id} is {provider_uri}"); - - Ok(provider_uri) -} - +/// Send a subscribe request. +/// +/// # Arguments +/// `provider_uri` - The provider's URI. +/// `entity_id` - The entity id. +/// `consumer_uri` - The consumer's URI. async fn send_subscribe_request( provider_uri: &str, entity_id: &str, consumer_uri: &str, ) -> Result<(), Box> { info!("Sending a subscribe request for entity id {entity_id} to provider URI {provider_uri}"); - let mut client = ProviderClient::connect(provider_uri.to_string()).await?; + let mut client = DigitalTwinProviderClient::connect(provider_uri.to_string()).await?; let request = tonic::Request::new(SubscribeRequest { - entity_id: String::from(entity_id), + entity_id: entity_id.to_string(), consumer_uri: consumer_uri.to_string(), }); let _response = client.subscribe(request).await?; @@ -187,27 +137,57 @@ async fn main() -> Result<(), Box> { info!("The Consumer has started."); // Setup the HTTP server. - let addr: SocketAddr = CONSUMER_ADDR.parse()?; + let addr: SocketAddr = CONSUMER_AUTHORITY.parse()?; let consumer_impl = consumer_impl::ConsumerImpl::default(); let server_future = - Server::builder().add_service(ConsumerServer::new(consumer_impl)).serve(addr); - info!("The HTTP server is listening on address '{CONSUMER_ADDR}'"); - + Server::builder().add_service(DigitalTwinConsumerServer::new(consumer_impl)).serve(addr); + info!("The HTTP server is listening on address '{CONSUMER_AUTHORITY}'"); + + let show_notification_command_provider_endpoint_info = find_provider_endpoint( + IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI, + sdv::vehicle::cabin::infotainment::hmi::show_notification::ID, + digital_twin_protocol::GRPC, + &[digital_twin_operation::INVOKE.to_string()], + ) + .await + .unwrap(); let show_notification_command_provider_uri = - get_provider_uri(sdv::vehicle::cabin::infotainment::hmi::show_notification::ID) - .await - .unwrap(); + show_notification_command_provider_endpoint_info.uri; + let ambient_air_temperature_property_provider_endpoint_info = find_provider_endpoint( + IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI, + sdv::vehicle::cabin::hvac::ambient_air_temperature::ID, + digital_twin_protocol::GRPC, + &[digital_twin_operation::SUBSCRIBE.to_string()], + ) + .await + .unwrap(); let ambient_air_temperature_property_provider_uri = - get_provider_uri(sdv::vehicle::cabin::hvac::ambient_air_temperature::ID).await.unwrap(); + ambient_air_temperature_property_provider_endpoint_info.uri; - let is_air_conditioning_active_property_uri = - get_provider_uri(sdv::vehicle::cabin::hvac::is_air_conditioning_active::ID).await.unwrap(); + let is_air_conditioning_active_property_provider_endpoint_info = find_provider_endpoint( + IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI, + sdv::vehicle::cabin::hvac::is_air_conditioning_active::ID, + digital_twin_protocol::GRPC, + &[digital_twin_operation::SUBSCRIBE.to_string(), digital_twin_operation::SET.to_string()], + ) + .await + .unwrap(); + let is_air_conditioning_active_property_provider_uri = + is_air_conditioning_active_property_provider_endpoint_info.uri; - let hybrid_battery_remaining_property_uri = - get_provider_uri(sdv::vehicle::obd::hybrid_battery_remaining::ID).await.unwrap(); + let hybrid_battery_remaining_property_provider_endpoint_info = find_provider_endpoint( + IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI, + sdv::vehicle::obd::hybrid_battery_remaining::ID, + digital_twin_protocol::GRPC, + &[digital_twin_operation::SUBSCRIBE.to_string()], + ) + .await + .unwrap(); + let hybrid_battery_remaining_property_provider_uri = + hybrid_battery_remaining_property_provider_endpoint_info.uri; - let consumer_uri = format!("http://{CONSUMER_ADDR}"); // Devskim: ignore DS137138 + let consumer_uri = format!("http://{CONSUMER_AUTHORITY}"); // Devskim: ignore DS137138 send_subscribe_request( &ambient_air_temperature_property_provider_uri, @@ -217,20 +197,20 @@ async fn main() -> Result<(), Box> { .await?; send_subscribe_request( - &is_air_conditioning_active_property_uri, + &is_air_conditioning_active_property_provider_uri, sdv::vehicle::cabin::hvac::is_air_conditioning_active::ID, &consumer_uri, ) .await?; send_subscribe_request( - &hybrid_battery_remaining_property_uri, + &hybrid_battery_remaining_property_provider_uri, sdv::vehicle::obd::hybrid_battery_remaining::ID, &consumer_uri, ) .await?; - start_activate_air_conditioning_repeater(is_air_conditioning_active_property_uri); + start_activate_air_conditioning_repeater(is_air_conditioning_active_property_provider_uri); start_show_notification_repeater( show_notification_command_provider_uri.clone(), diff --git a/samples/mixed/provider/Cargo.toml b/samples/mixed/provider/Cargo.toml index 39980461..7a65409e 100644 --- a/samples/mixed/provider/Cargo.toml +++ b/samples/mixed/provider/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" [dependencies] async-std = { workspace = true, features = ["attributes"] } -dt-model-identifiers = { path = "../../../dt-model/dt-model-identifiers" } +digital-twin-model = { path = "../../../digital-twin-model" } env_logger= { workspace = true } ibeji-common = { path = "../../../common" } json-ld = { git = "https://github.com/blast-hardcheese/json-ld", branch = "resolve-issue-40" } @@ -18,6 +18,8 @@ log = { workspace = true } parking_lot = { workspace = true } prost = { workspace = true } proto = { path = "../../../proto" } +samples_common = { path = "../../common" } +samples_proto = { path = "../../proto" } serde_json = { workspace = true } tokio = { workspace = true , features = ["macros", "rt-multi-thread"] } tonic = { workspace = true } diff --git a/samples/mixed/provider/src/main.rs b/samples/mixed/provider/src/main.rs index fb91802a..8d473195 100644 --- a/samples/mixed/provider/src/main.rs +++ b/samples/mixed/provider/src/main.rs @@ -5,16 +5,16 @@ mod provider_impl; mod vehicle; -use dt_model_identifiers::sdv_v1 as sdv; +use digital_twin_model::sdv_v1 as sdv; use env_logger::{Builder, Target}; -use ibeji_common::{find_full_path, retrieve_dtdl}; use log::{debug, info, warn, LevelFilter}; use parking_lot::{Mutex, MutexGuard}; -use proto::consumer::consumer_client::ConsumerClient; -use proto::consumer::PublishRequest; -use proto::digitaltwin::digital_twin_client::DigitalTwinClient; -use proto::digitaltwin::RegisterRequest; -use proto::provider::provider_server::ProviderServer; +use proto::digital_twin::digital_twin_client::DigitalTwinClient; +use proto::digital_twin::{EndpointInfo, EntityAccessInfo, RegisterRequest}; +use samples_common::{digital_twin_operation, digital_twin_protocol}; +use samples_proto::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_client::DigitalTwinConsumerClient; +use samples_proto::sample_grpc::v1::digital_twin_consumer::PublishRequest; +use samples_proto::sample_grpc::v1::digital_twin_provider::digital_twin_provider_server::DigitalTwinProviderServer; use std::collections::HashSet; use std::net::SocketAddr; use std::sync::Arc; @@ -25,7 +25,7 @@ use crate::provider_impl::{ProviderImpl, SubscriptionMap}; use crate::vehicle::Vehicle; const IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI: &str = "http://[::1]:50010"; // Devskim: ignore DS137138 -const PROVIDER_ADDR: &str = "[::1]:40010"; +const PROVIDER_AUTHORITY: &str = "[::1]:40010"; async fn publish(subscription_map: Arc>, entity_id: &str, value: &str) { let urls; @@ -45,7 +45,7 @@ async fn publish(subscription_map: Arc>, entity_id: &str, "Sending a publish request for {entity_id} with value {value} to consumer URI {url}" ); - let client_result = ConsumerClient::connect(url).await; + let client_result = DigitalTwinConsumerClient::connect(url).await; if client_result.is_err() { warn!("Unable to connect. We will retry in a moment."); sleep(Duration::from_secs(1)).await; @@ -54,8 +54,8 @@ async fn publish(subscription_map: Arc>, entity_id: &str, let mut client = client_result.unwrap(); let request = tonic::Request::new(PublishRequest { - entity_id: String::from(entity_id), - value: String::from(value), + entity_id: entity_id.to_string(), + value: value.to_string(), }); let response = client.publish(request).await; @@ -117,33 +117,101 @@ async fn start_vehicle_simulator( } #[tokio::main] -#[allow(clippy::collapsible_else_if)] +// #[allow(clippy::collapsible_else_if)] async fn main() -> Result<(), Box> { // Setup logging. Builder::new().filter(None, LevelFilter::Info).target(Target::Stdout).init(); info!("The Provider has started."); - debug!("Preparing the Provider's DTDL."); - let provider_dtdl_path = find_full_path("content/mixed.json")?; - let dtdl = retrieve_dtdl(&provider_dtdl_path)?; - debug!("Prepared the Provider's DTDL."); + // 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(), + ], + uri: "http://[::1]:40010".to_string(), // Devskim: ignore DS137138 + context: sdv::vehicle::cabin::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(), + endpoint_info_list: vec![ambient_air_temperature_endpoint_info], + }; + + // IsAirConditioningActive + let is_air_conditioning_active_endpoint_info = EndpointInfo { + 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: "http://[::1]:40010".to_string(), // Devskim: ignore DS137138 + context: sdv::vehicle::cabin::hvac::ambient_air_temperature::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(), + 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(), + ], + uri: "http://[::1]:40010".to_string(), // Devskim: ignore DS137138 + context: sdv::vehicle::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(), + endpoint_info_list: vec![hybrid_battery_remaining_endpoint_info], + }; + + // ShowNotification + let show_notification_endpoint_info = EndpointInfo { + protocol: digital_twin_protocol::GRPC.to_string(), + operations: vec![digital_twin_operation::INVOKE.to_string()], + uri: "http://[::1]:40010".to_string(), // Devskim: ignore DS137138 + context: sdv::vehicle::cabin::infotainment::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(), + endpoint_info_list: vec![show_notification_endpoint_info], + }; + + let entity_access_info_list = vec![ + ambient_air_temperature_access_info, + is_air_conditioning_active_access_info, + hybrid_battery_remaining_access_info, + show_notification_access_info, + ]; // Setup the HTTP server. - let addr: SocketAddr = PROVIDER_ADDR.parse()?; + let addr: SocketAddr = PROVIDER_AUTHORITY.parse()?; let subscription_map = Arc::new(Mutex::new(SubscriptionMap::new())); let vehicle = Arc::new(Mutex::new(Vehicle::new())); let provider_impl = ProviderImpl { subscription_map: subscription_map.clone(), vehicle: vehicle.clone() }; let server_future = - Server::builder().add_service(ProviderServer::new(provider_impl)).serve(addr); - info!("The HTTP server is listening on address '{PROVIDER_ADDR}'"); + 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 with the Provider's DTDL to the In-Vehicle Digital Twin Service URI {IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI}"); + info!("Sending a register request with the Provider's entity access info to the In-Vehicle Digital Twin Service URI {IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI}"); let mut client = DigitalTwinClient::connect(IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI).await?; - let request = tonic::Request::new(RegisterRequest { dtdl }); + let request = tonic::Request::new(RegisterRequest { entity_access_info_list }); let _response = client.register(request).await?; - debug!("The Provider's DTDL has been registered."); + debug!("The Provider's entity access info has been registered."); start_vehicle_simulator(subscription_map.clone(), vehicle).await; diff --git a/samples/mixed/provider/src/provider_impl.rs b/samples/mixed/provider/src/provider_impl.rs index f1375821..79e9d830 100644 --- a/samples/mixed/provider/src/provider_impl.rs +++ b/samples/mixed/provider/src/provider_impl.rs @@ -2,13 +2,15 @@ // Licensed under the MIT license. // SPDX-License-Identifier: MIT -use dt_model_identifiers::sdv_v1 as sdv; +use digital_twin_model::sdv_v1 as sdv; use log::{debug, info, warn}; use parking_lot::{Mutex, MutexGuard}; -use proto::consumer::{consumer_client::ConsumerClient, RespondRequest}; -use proto::provider::{ - provider_server::Provider, GetRequest, GetResponse, InvokeRequest, InvokeResponse, SetRequest, - SetResponse, SubscribeRequest, SubscribeResponse, UnsubscribeRequest, UnsubscribeResponse, +use samples_proto::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_client::DigitalTwinConsumerClient; +use samples_proto::sample_grpc::v1::digital_twin_consumer::RespondRequest; +use samples_proto::sample_grpc::v1::digital_twin_provider::digital_twin_provider_server::DigitalTwinProvider; +use samples_proto::sample_grpc::v1::digital_twin_provider::{ + GetRequest, GetResponse, InvokeRequest, InvokeResponse, SetRequest, SetResponse, + SubscribeRequest, SubscribeResponse, UnsubscribeRequest, UnsubscribeResponse, }; use std::collections::{HashMap, HashSet}; use std::str::FromStr; @@ -47,7 +49,7 @@ impl ProviderImpl { } #[tonic::async_trait] -impl Provider for ProviderImpl { +impl DigitalTwinProvider for ProviderImpl { /// Subscribe implementation. /// /// # Arguments @@ -170,7 +172,7 @@ impl Provider for ProviderImpl { "Sending an invoke response for entity id {entity_id} to consumer URI {consumer_uri} " ); - let client_result = ConsumerClient::connect(consumer_uri).await; + let client_result = DigitalTwinConsumerClient::connect(consumer_uri).await; if client_result.is_err() { return Err(Status::internal(format!("{:?}", client_result.unwrap_err()))); } @@ -238,14 +240,14 @@ mod provider_impl_tests { let first_get_result = lock.get(&first_id); assert!(first_get_result.is_some()); let first_value = first_get_result.unwrap(); - assert!(first_value.len() == 2); + assert_eq!(first_value.len(), 2); assert!(first_value.contains(&first_uri)); assert!(first_value.contains(&second_uri)); let second_get_result = lock.get(&second_id); assert!(second_get_result.is_some()); let second_value = second_get_result.unwrap(); - assert!(second_value.len() == 1); + assert_eq!(second_value.len(), 1); assert!(second_value.contains(&third_uri)); } } diff --git a/samples/property/consumer/Cargo.toml b/samples/property/consumer/Cargo.toml index 1f62eda7..6faf3e54 100644 --- a/samples/property/consumer/Cargo.toml +++ b/samples/property/consumer/Cargo.toml @@ -10,14 +10,15 @@ license = "MIT" [dependencies] async-std = { workspace = true, features = ["attributes"] } -dtdl-parser = { path = "../../../dtdl-parser" } -dt-model-identifiers = { path = "../../../dt-model/dt-model-identifiers" } +digital-twin-model = { path = "../../../digital-twin-model" } env_logger= { workspace = true } iref = { workspace = true } json-ld = { git = "https://github.com/blast-hardcheese/json-ld", branch = "resolve-issue-40" } log = { workspace = true } prost = { workspace = true } proto = { path = "../../../proto" } +samples_common = { path = "../../common" } +samples_proto = { path = "../../proto" } serde_json = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tonic = { workspace = true } diff --git a/samples/property/consumer/src/consumer_impl.rs b/samples/property/consumer/src/consumer_impl.rs index bfa48039..c06f31f7 100644 --- a/samples/property/consumer/src/consumer_impl.rs +++ b/samples/property/consumer/src/consumer_impl.rs @@ -3,15 +3,17 @@ // SPDX-License-Identifier: MIT use log::{info, warn}; -use proto::consumer::consumer_server::Consumer; -use proto::consumer::{PublishRequest, PublishResponse, RespondRequest, RespondResponse}; +use samples_proto::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_server::DigitalTwinConsumer; +use samples_proto::sample_grpc::v1::digital_twin_consumer::{ + PublishRequest, PublishResponse, RespondRequest, RespondResponse, +}; use tonic::{Request, Response, Status}; #[derive(Debug, Default)] pub struct ConsumerImpl {} #[tonic::async_trait] -impl Consumer for ConsumerImpl { +impl DigitalTwinConsumer for ConsumerImpl { /// Publish implementation. /// /// # Arguments diff --git a/samples/property/consumer/src/main.rs b/samples/property/consumer/src/main.rs index 9a3ec9dc..bc425a5b 100644 --- a/samples/property/consumer/src/main.rs +++ b/samples/property/consumer/src/main.rs @@ -2,16 +2,13 @@ // Licensed under the MIT license. // SPDX-License-Identifier: MIT -use dt_model_identifiers::sdv_v1 as sdv; -use dtdl_parser::dtmi::{create_dtmi, Dtmi}; -use dtdl_parser::model_parser::ModelParser; +use digital_twin_model::sdv_v1 as sdv; use env_logger::{Builder, Target}; use log::{debug, info, LevelFilter}; -use proto::consumer::consumer_server::ConsumerServer; -use proto::digitaltwin::digital_twin_client::DigitalTwinClient; -use proto::digitaltwin::FindByIdRequest; -use proto::provider::provider_client::ProviderClient; -use proto::provider::SubscribeRequest; +use samples_common::{digital_twin_operation, digital_twin_protocol, find_provider_endpoint}; +use samples_proto::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_server::DigitalTwinConsumerServer; +use samples_proto::sample_grpc::v1::digital_twin_provider::digital_twin_provider_client::DigitalTwinProviderClient; +use samples_proto::sample_grpc::v1::digital_twin_provider::SubscribeRequest; use std::net::SocketAddr; use tonic::transport::Server; @@ -19,7 +16,7 @@ mod consumer_impl; const IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI: &str = "http://[::1]:50010"; // Devskim: ignore DS137138 -const CONSUMER_ADDR: &str = "[::1]:60010"; +const CONSUMER_AUTHORITY: &str = "[::1]:60010"; #[tokio::main] async fn main() -> Result<(), Box> { @@ -29,73 +26,35 @@ async fn main() -> Result<(), Box> { info!("The Consumer has started."); // Setup the HTTP server. - let addr: SocketAddr = CONSUMER_ADDR.parse()?; + let addr: SocketAddr = CONSUMER_AUTHORITY.parse()?; let consumer_impl = consumer_impl::ConsumerImpl::default(); let server_future = - Server::builder().add_service(ConsumerServer::new(consumer_impl)).serve(addr); + Server::builder().add_service(DigitalTwinConsumerServer::new(consumer_impl)).serve(addr); + info!("The HTTP server is listening on address '{CONSUMER_AUTHORITY}'"); - // Obtain the DTDL for the ambient air temmpterature. - info!("Sending a find_by_id request for entity id {} to the In-Vehicle Digital Twin Service URI {IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI}", - sdv::vehicle::cabin::hvac::ambient_air_temperature::ID); - let mut client = DigitalTwinClient::connect(IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI).await?; - let request = tonic::Request::new(FindByIdRequest { - entity_id: String::from(sdv::vehicle::cabin::hvac::ambient_air_temperature::ID), - }); - let response = client.find_by_id(request).await?; - let dtdl = response.into_inner().dtdl; - debug!("Received the response for the find_by_id request."); - - debug!("Parsing the DTDL."); - let mut parser = ModelParser::new(); - let json_texts = vec![dtdl]; - let model_dict_result = parser.parse(&json_texts); - if let Err(error) = model_dict_result { - panic!("Failed to parse the DTDL: {error}"); - } - let model_dict = model_dict_result.unwrap(); - debug!("The DTDL parser has successfully parsed the DTDL."); - - // Create the id (as a DTMI) for the ambient air temperature property. - let ambient_air_temperature_property_id: Option = - create_dtmi(sdv::vehicle::cabin::hvac::ambient_air_temperature::ID); - if ambient_air_temperature_property_id.is_none() { - panic!("Unable to create the dtmi"); - } - - // Get the entity from the DTDL for the ambient air temperature property. - let entity_result = model_dict.get(&ambient_air_temperature_property_id.unwrap()); - if entity_result.is_none() { - panic!("Unable to find the entity"); - } - let entity = entity_result.unwrap(); + let provider_endpoint_info = find_provider_endpoint( + IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI, + sdv::vehicle::cabin::hvac::ambient_air_temperature::ID, + digital_twin_protocol::GRPC, + &[digital_twin_operation::SUBSCRIBE.to_string()], + ) + .await + .unwrap(); - // Get the URI property from the entity. - let uri_property_result = entity.undefined_properties().get(sdv::property::uri::ID); - if uri_property_result.is_none() { - panic!("Unable to find the URI property"); - } - let uri_property = uri_property_result.unwrap(); + let provider_uri = provider_endpoint_info.uri; - // Get the value for the URI property. - let uri_property_value_result = uri_property.get("@value"); - if uri_property_value_result.is_none() { - panic!("Unable to find the value for the URI for ambient air temperature's provider."); - } - let uri_property_value = uri_property_value_result.unwrap(); - let uri_str_option = uri_property_value.as_str(); - let uri = String::from(uri_str_option.unwrap()); - info!("The URI for the ambient air temperature's provider is {uri}"); + info!("The URI for the AmbientAirTemperature property's provider is {provider_uri}"); - let consumer_uri = format!("http://{CONSUMER_ADDR}"); // Devskim: ignore DS137138 + let consumer_uri = format!("http://{CONSUMER_AUTHORITY}"); // Devskim: ignore DS137138 // Subscribing to the ambient air temperature data feed. info!( - "Sending a subscribe request for entity id {} to provider URI {uri}", + "Sending a subscribe request for entity id {} to provider URI {provider_uri}", sdv::vehicle::cabin::hvac::ambient_air_temperature::ID ); - let mut client = ProviderClient::connect(uri).await?; + let mut client = DigitalTwinProviderClient::connect(provider_uri).await?; let request = tonic::Request::new(SubscribeRequest { - entity_id: String::from(sdv::vehicle::cabin::hvac::ambient_air_temperature::ID), + entity_id: sdv::vehicle::cabin::hvac::ambient_air_temperature::ID.to_string(), consumer_uri, }); let _response = client.subscribe(request).await?; diff --git a/samples/property/dtdl/content/ambient_air_temperature.json b/samples/property/dtdl/content/ambient_air_temperature.json deleted file mode 100644 index a5b62978..00000000 --- a/samples/property/dtdl/content/ambient_air_temperature.json +++ /dev/null @@ -1,25 +0,0 @@ -[ - { - "@context": ["dtmi:dtdl:context;2", "dtmi:sdv:context;2"], - "@type": "Interface", - "@id": "dtmi:sdv:Vehicle:Cabin:HVAC;1", - "description": "Heat, Ventilation and Air Conditioning", - "contents": [ - { - "@type": ["Property", "Temperature", "RemotelyAccessible"], - "@id": "dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1", - "name": "ambient_air_temperature", - "description": "The immediate surroundings air temperature (in Fahrenheit).", - "schema": "integer", - "unit": "degreeFahrenheit", - "remote_access": [ - { - "@type": "Endpoint", - "uri": "http://[::1]:40010", - "operations": [ "Subscribe", "Unsubscribe" ] - } - ] - } - ] - } -] \ No newline at end of file diff --git a/samples/property/provider/Cargo.toml b/samples/property/provider/Cargo.toml index 9dcc9dda..77568949 100644 --- a/samples/property/provider/Cargo.toml +++ b/samples/property/provider/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" [dependencies] async-std = { workspace = true, features = ["attributes"] } -dt-model-identifiers = { path = "../../../dt-model/dt-model-identifiers" } +digital-twin-model = { path = "../../../digital-twin-model" } env_logger= { workspace = true } ibeji-common = { path = "../../../common" } json-ld = { git = "https://github.com/blast-hardcheese/json-ld", branch = "resolve-issue-40" } @@ -18,6 +18,8 @@ log = { workspace = true} parking_lot = { workspace = true } prost = { workspace = true } proto = { path = "../../../proto" } +samples_common = { path = "../../common" } +samples_proto = { path = "../../proto" } serde_json = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tonic = { workspace = true } diff --git a/samples/property/provider/src/main.rs b/samples/property/provider/src/main.rs index 4c54ea3e..c26a1ec8 100644 --- a/samples/property/provider/src/main.rs +++ b/samples/property/provider/src/main.rs @@ -4,16 +4,16 @@ mod provider_impl; -use dt_model_identifiers::sdv_v1 as sdv; +use digital_twin_model::sdv_v1 as sdv; use env_logger::{Builder, Target}; -use ibeji_common::{find_full_path, retrieve_dtdl}; use log::{debug, info, warn, LevelFilter}; use parking_lot::{Mutex, MutexGuard}; -use proto::consumer::consumer_client::ConsumerClient; -use proto::consumer::PublishRequest; -use proto::digitaltwin::digital_twin_client::DigitalTwinClient; -use proto::digitaltwin::RegisterRequest; -use proto::provider::provider_server::ProviderServer; +use proto::digital_twin::digital_twin_client::DigitalTwinClient; +use proto::digital_twin::{EndpointInfo, EntityAccessInfo, RegisterRequest}; +use samples_common::{digital_twin_operation, digital_twin_protocol}; +use samples_proto::sample_grpc::v1::digital_twin_consumer::digital_twin_consumer_client::DigitalTwinConsumerClient; +use samples_proto::sample_grpc::v1::digital_twin_consumer::PublishRequest; +use samples_proto::sample_grpc::v1::digital_twin_provider::digital_twin_provider_server::DigitalTwinProviderServer; use std::collections::HashSet; use std::net::SocketAddr; use std::sync::Arc; @@ -23,7 +23,7 @@ use tonic::transport::Server; use crate::provider_impl::{ProviderImpl, SubscriptionMap}; const IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI: &str = "http://[::1]:50010"; // Devskim: ignore DS137138 -const PROVIDER_ADDR: &str = "[::1]:40010"; +const PROVIDER_AUTHORITY: &str = "[::1]:40010"; /// Start the ambient air temperature data stream. /// @@ -52,7 +52,7 @@ fn start_ambient_air_temperature_data_stream(subscription_map: Arc Result<(), Box> { info!("The Provider has started."); - debug!("Preparing the Provider's DTDL."); - let provider_dtdl_path = find_full_path("content/ambient_air_temperature.json")?; - let dtdl = retrieve_dtdl(&provider_dtdl_path)?; - debug!("Prepared the Provider's DTDL."); + 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(), + ], + uri: "http://[::1]:40010".to_string(), // Devskim: ignore DS137138 + context: sdv::vehicle::cabin::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(), + endpoint_info_list: vec![endpoint_info], + }; // Setup the HTTP server. - let addr: SocketAddr = PROVIDER_ADDR.parse()?; + 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 server_future = - Server::builder().add_service(ProviderServer::new(provider_impl)).serve(addr); - info!("The HTTP server is listening on address '{PROVIDER_ADDR}'"); + 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 with the Provider's DTDL to the In-Vehicle Digital Twin Service URI {IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI}"); let mut client = DigitalTwinClient::connect(IN_VEHICLE_DIGITAL_TWIN_SERVICE_URI).await?; - let request = tonic::Request::new(RegisterRequest { dtdl }); + let request = + tonic::Request::new(RegisterRequest { entity_access_info_list: vec![entity_access_info] }); let _response = client.register(request).await?; - info!("The Provider's DTDL has been registered."); + debug!("The Provider's DTDL has been registered."); start_ambient_air_temperature_data_stream(subscription_map.clone()); diff --git a/samples/property/provider/src/provider_impl.rs b/samples/property/provider/src/provider_impl.rs index dde20ade..7c889a9e 100644 --- a/samples/property/provider/src/provider_impl.rs +++ b/samples/property/provider/src/provider_impl.rs @@ -4,9 +4,10 @@ use log::{debug, info, warn}; use parking_lot::{Mutex, MutexGuard}; -use proto::provider::{ - provider_server::Provider, GetRequest, GetResponse, InvokeRequest, InvokeResponse, SetRequest, - SetResponse, SubscribeRequest, SubscribeResponse, UnsubscribeRequest, UnsubscribeResponse, +use samples_proto::sample_grpc::v1::digital_twin_provider::{ + digital_twin_provider_server::DigitalTwinProvider, GetRequest, GetResponse, InvokeRequest, + InvokeResponse, SetRequest, SetResponse, SubscribeRequest, SubscribeResponse, + UnsubscribeRequest, UnsubscribeResponse, }; use std::collections::{HashMap, HashSet}; use std::sync::Arc; @@ -20,7 +21,7 @@ pub struct ProviderImpl { } #[tonic::async_trait] -impl Provider for ProviderImpl { +impl DigitalTwinProvider for ProviderImpl { /// Subscribe implementation. /// /// # Arguments @@ -145,14 +146,14 @@ mod provider_impl_tests { let first_get_result = lock.get(&first_id); assert!(first_get_result.is_some()); let first_value = first_get_result.unwrap(); - assert!(first_value.len() == 2); + assert_eq!(first_value.len(), 2); assert!(first_value.contains(&first_uri)); assert!(first_value.contains(&second_uri)); let second_get_result = lock.get(&second_id); assert!(second_get_result.is_some()); let second_value = second_get_result.unwrap(); - assert!(second_value.len() == 1); + assert_eq!(second_value.len(), 1); assert!(second_value.contains(&third_uri)); } } diff --git a/samples/proto/Cargo.toml b/samples/proto/Cargo.toml new file mode 100644 index 00000000..b5e9cc25 --- /dev/null +++ b/samples/proto/Cargo.toml @@ -0,0 +1,17 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +[package] +name = "samples_proto" +version = "1.0.0" +edition = "2021" +license = "MIT" + +[dependencies] +tonic = { workspace = true } +prost = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } + +[build-dependencies] +tonic-build = { workspace = true } diff --git a/samples/proto/build.rs b/samples/proto/build.rs new file mode 100644 index 00000000..da6e3d0b --- /dev/null +++ b/samples/proto/build.rs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +fn main() -> Result<(), Box> { + tonic_build::compile_protos("sample_grpc/v1/digital_twin_consumer.proto")?; + tonic_build::compile_protos("sample_grpc/v1/digital_twin_provider.proto")?; + Ok(()) +} diff --git a/proto/consumer.proto b/samples/proto/sample_grpc/v1/digital_twin_consumer.proto similarity index 88% rename from proto/consumer.proto rename to samples/proto/sample_grpc/v1/digital_twin_consumer.proto index d81c2c13..b34c4863 100644 --- a/proto/consumer.proto +++ b/samples/proto/sample_grpc/v1/digital_twin_consumer.proto @@ -4,9 +4,9 @@ syntax = "proto3"; -package consumer; +package digital_twin_consumer; -service Consumer { +service DigitalTwinConsumer { rpc Publish (PublishRequest) returns (PublishResponse); rpc Respond (RespondRequest) returns (RespondResponse); } diff --git a/proto/provider.proto b/samples/proto/sample_grpc/v1/digital_twin_provider.proto similarity index 94% rename from proto/provider.proto rename to samples/proto/sample_grpc/v1/digital_twin_provider.proto index a33f111c..a2363dd1 100644 --- a/proto/provider.proto +++ b/samples/proto/sample_grpc/v1/digital_twin_provider.proto @@ -4,9 +4,9 @@ syntax = "proto3"; -package provider; +package digital_twin_provider; -service Provider { +service DigitalTwinProvider { rpc Subscribe (SubscribeRequest) returns (SubscribeResponse); rpc Unsubscribe (UnsubscribeRequest) returns (UnsubscribeResponse); rpc Get (GetRequest) returns (GetResponse); diff --git a/samples/proto/src/lib.rs b/samples/proto/src/lib.rs new file mode 100644 index 00000000..2e86a309 --- /dev/null +++ b/samples/proto/src/lib.rs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +pub mod sample_grpc { + pub mod v1 { + pub mod digital_twin_consumer { + tonic::include_proto!("digital_twin_consumer"); + } + + pub mod digital_twin_provider { + tonic::include_proto!("digital_twin_provider"); + } + } +}