diff --git a/README.md b/README.md index 9e455a5..4756407 100644 --- a/README.md +++ b/README.md @@ -1,206 +1,33 @@ +#### Wheelchair Assistant Scenario Overview +In this scenario we try to provide a handicapped person an easier access to a vehicle and thus more mobility. Just as musicians in an orchestra must work together and communicate effectively to create beautiful music, various systems and technologies must work seamlessly together to provide complete support to the handicapped person. Our system detects that a handicapped person with a wheelchair is approaching a vehicle and aims to provide complete support by personalizing to the individual needs of the user. The system demonstrates a full driving scenario from arriving at the car to reaching the desired destination with maximum support from automation software. -# Maestro Challenge šŸš—šŸ’»šŸŽ¶ -- [About](#about) -- [Sample Scenarios](#sample-scenarios) - - [Provided Sample Scenario](#provided-sample-scenario) - - [Other Sample Scenarios](#other-sample-scenarios) -- [Useful References for Creating and Enhancing Sample Scenarios](#useful-references-for-creating-and-enhancing-sample-scenarios) - - [In-Vehicle Stack](#in-vehicle-stack) - - [General](#general) - - [Eclipse Ankaios](#eclipse-ankaios) -- [Getting Started](#getting-started) - - [Need to know](#need-to-know) - - [Prebuilt Container Images](#prebuilt-container-images) - - [Eclipse Ankaios Orchestrator](#eclipse-ankaios-orchestrator) - - [Eclipse BlueChi Orchestrator](#eclipse-bluechi-orchestrator) - - [Azure Subscription](#azure-subscription) -- [In-Vehicle Software Stack Overview](#in-vehicle-software-stack-overview) -- [Projects Involved](#projects-involved) - - [In-Vehicle Software Stack](#in-vehicle-software-stack) - - [In-Vehicle Software Orchestrators](#in-vehicle-software-orchestrators) -- [Hack Coaches](#hack-coaches) +A signal is raised to the orchestrator to start up the necessary providers and applications to manage the wheelchair as well as to make adjustments inside the car. These include a Digital Twin Provider, which exposes signals from the wheelchair to higher-level applications, and a Wheelchair Assistant application, which makes use of and reacts to these signals. In order to start our orchestra, the tools we use are: Eclipse Chariott, Ibeji, Agemo, Freyja, and an Eclipse Mosquitto MQTT broker. -**Do you want to be the next maestro of the next generation of vehicle software? The time is now!** +##### Ankaios as Orchestrator -Imagine yourself as the **maestro**. You are not just writing code. You are composing a masterpiece that will drive the future of transportation. Your work will ensure that every component, from the engine control unit to the infotainment system, works in perfect harmony. So, step up to the podium, take a deep breath, and let your creativity flow. The stage is set for you to become the maestro of in-vehicle software šŸš—šŸ’»šŸŽ¶. +Ankaios is developed specifically for automotive use cases. It's independant of communication frameworks like SOME/IP, DDS or REST API. +It also does not depend on systemd or any specific systems and can be started with any init system. +With this it's possible to dynamically startup an application that is only required in a particular situation. Since Ankaios supports ASPICE processes, it can be used widely by leading car manufacturers. +For these reasons Ankaios fits in perfectly for the wheelchair scenario. -**Come hack with us!** +##### Dynamic Orchestration -![Maestro](docs/diagrams/orchestra_picture.jpg) ->Photo by Manuel NƤgeli. +![orchester.png](docs%2Fwheelchair_assistant_use_case%2Forchester.png) -## About +This use case demonstrates a more complex example of dynamic orchestration. -We supply an in-vehicle stack with software orchestrators. Your assignment is to utilize our in-vehicle stack and software orchestrators to construct your own scenario or replicate the scenarios provided in our [Sample Scenarios](#sample-scenarios). +At the beginning the car is parked and in the init state when the person arrives at the parking lot. While approaching, the person unlocks the car and it switches to the open state in which the door is unlocked. In order to simulate this, Digital Twin Provider "carkey_unlock_provider" sends the signal to set the cars state to open. Another Digital Twin Provider "wheelchair_distance_decreasing_provider" simulates the approaching process. Meanwhile a [script](./in-vehicle-stack/scenarios/wheelchair_assistant_use_case/scripts/) will be run to monitor Ibeji and detect when the handicapped person is approaching the vehicle. The script will continuously monitor the wheelchair distance from the vehicle and based on the distance read, automatic configuration of the car starts to make the entry experience as effortless as possible. This automatic configuration is implemented by the [Wheelchair Assistant Application](./in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_assistant_application/). Through this application, the front and back doors open up, the steering wheel adjusts to a higher position and the driver seat is adjusted to the back and lowered. Once this is done, the car is in Hold state and ready to be turned on which is simulated by the "car_on_provider". Once the driver enters and turns on the ignition, the doors close and steering wheel and seat go back to their default state. Once the person arrives at the desired destination, the described process takes place in the reverse way to make sure they leave the car comfortably. This process is achieved by the "car_off_provider", "wheelchair_distance_increasing_provider" and the "car_lock_provider". -The tech stack in this challenge showcases complex in-vehicle services and workloads, utilizing the vehicle’s computing resources and capabilities, as well as other in-vehicle applications. +Every action and state change through transition is uploaded to the cloud through Freyja cloud syncronization and can be visualized by Azure. +All our services are registered by Chariott. -Enjoy the process of bringing your vision to life! +##### The Wheelchair Assistant Applications + An explanation of the Wheelchair Assistant Applications can be found in [Wheelchair Assistant Application](./in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_assistant_application/). -## Sample Scenarios + ##### State machine overview -Here is a list of potential scenarios your team could develop. Feel free to invent your own scenarios too. Let your creativity shine ā˜€ļø and have fun! +![wheel_assistant_states.png](docs%2Fwheelchair_assistant_use_case%2Fdiagrams%2Fwheel_assistant_states.png) -### Provided Sample Scenario + ##### Architecture -#### Smart Trailer Scenario Overview -The system detects that a smart trailer is being connected to the vehicle. A signal is raised to the orchestrator to start up the necessary providers and applications to manage the smart trailer. These include a Digital Twin Provider, which exposes signals from the trailer to higher-level applications, and a Smart Trailer application, which consumes these signals. The provided smart trailer application prints the value of a property it subscribes to, the `TrailerWeight`. - -The first diagrams in the [Eclipse Ankaios](./eclipse-ankaios/README.md) and [Eclipse BlueChi](./eclipse-bluechi/README.md) depict this provided sample scenario. - -The in-vehicle-stack is started with Eclipse Chariott, Ibeji, Agemo, Freyja, and an Eclipse Mosquitto MQTT broker inside the orchestrator environment. - -#### Dynamic Orchestration -This use case demonstrates a simple example of dynamic orchestration. A [script](./in-vehicle-stack/scenarios/smart_trailer_use_case/scripts/) (one implemented for each orchestrator) will be run to monitor Ibeji and detect when the trailer is connected. The script will continuously poll for the "Trailer Connected" Digital Twin Provider, and print "NotFound" until it is started. You can simulate the trailer being connected to the vehicle by starting the ["Trailer Connected" Digital Twin Provider](./in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/). This provider will register itself with Ibeji, the script will detect this change, and start up the ["Trailer Properties" Digital Twin Provider](./in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider/) and the [Smart Trailer Application](./in-vehicle-stack/scenarios/smart_trailer_use_case/applications/smart_trailer_application/). This shows a simple example of reacting to an event in the vehicle by starting up other workloads. - -#### Trailer Applications -The Trailer Properties Provider supports the ManagedSubscribe operation so that the Smart Trailer Application can specify that it wants to receive the `TrailerWeight` property's value every 10 seconds. Using the provided configuration, Freyja is configured to sync the `TrailerWeight` every 3 seconds to a mocked cloud endpoint (which will log the signal data on standard output). See the [Cloud Connectivity Doc](./docs/in-vehicle-stack/azure-cloud-connection.md) for instructions to synchronize signals to an Azure Digital Twins instance in the cloud. The [Digital Twin Model](./in-vehicle-stack/scenarios/smart_trailer_use_case/digital-twin-model/dtdl/trailer.json) defines the digital twin model for the trailer, which is used as a reference for the Digital Twin Providers and Applications. - -#### Run the use case -Once you've chosen an orchestrator and gone through their environment setup steps, please refer to [Ankaios's Dev Environment README](./eclipse-ankaios/README.md#startup-check-before-development) or [BlueChi's Dev Environment README](./eclipse-bluechi/README.md#running-the-smart-trailer-example-with-bluechis-devcontainer) for instructions on running this scenario. - -#### Hack Challenge - Extend the use case -Take a look at the source code for the Digital Twin Model, Digital Twin Providers, and Applications used in this example and add to them or use them as a reference to create your own! See the [references](#useful-references-for-creating-and-enhancing-sample-scenarios) to help guide you as well. - -When developing new workloads to run in the orchestrator environments, it is recommended to: -1. Write the code for your workload in the language of your choice. -1. Build a container image for it. -1. Push it to a container registry. You can create an [Azure Container Registry](./docs/azure/azure_container_registry_instructions.md#aditional-information) with your [Azure Subscription](./docs/azure/azure_code_redeem_instructions.md) -1. Follow the orchestrator-specific instructions for plugging your container image into the environments. - - If you are using Eclipse Ankaios, please see [Workload Development](./eclipse-ankaios/README.md#workload-development) section. - - If you are using Eclipse BlueChi, please see the [Managing Workloads](./eclipse-bluechi/README.md#managing-workloads) section. - -Here are a few suggested ways to extend this use case, but feel free to use your imagination to come up with your own as well! - -- Extend the smart trailer application to adjust some body control or powertrain functions based on the weight of the trailer to ensure a smooth trip. - -- Create a web UI that displays the signals which are synced to the cloud. - -- Create your own application which leverages vehicle signals to implement a different use case, such as a data collector. - -### Other Sample Scenarios -- Leverage OpenAI to enhance the vehicle’s user experience. You could develop an application that uses OpenAI’s GPT model to power an in-vehicle virtual assistant. - -- Consider using OpenAI to enhance our software orchestrators, effectively creating intelligent orchestrators! OpenAI could optimize workload placements across compute nodes and/or cloud based on factors such compute usage, memory usage, network coverage, and latency. - -- Create a web user interface application, such as a dashboard, that displays the various workloads' health. - -## Useful References for Creating and Enhancing Sample Scenarios - -This section offers guidance for creating a new sample scenario or extending the [Provided Sample Scenario](#provided-sample-scenario). While it does not provide a comprehensive list of resources, it aims to steer you in the right direction. If this section does not provide the guidance you need, please refer to the respective project’s documentation. See [Projects Involved](#projects-involved) for the project links. - -### In-Vehicle Stack -If you are using the [In-Vehicle Stack](./in-vehicle-stack/README.md), these references may be useful. - -- [How do I override a configuration file for a service in the in-vehicle stack?](./docs/in-vehicle-stack/config-overrides.md) -- What is Chariott's default Service Discovery's URI? - > The default URI for the Service Discovery is `http://0.0.0.0:50000`. Please see Chariott's FAQ for more info on [Service Discovery](#chariott). -- After I pushed a newly created image to my container registry, how do I get the orchestrator to start my container image? - > If you are using Eclipse Ankaios, please see [Workload Development](./eclipse-ankaios/README.md#workload-development) section. If you are using Eclipse BlueChi, please see the [Managing Workloads](./eclipse-bluechi/README.md#managing-workloads) section. -- After I created a new component or edited an existing component, why should I push its image to a container registry? - > Pushing an image to the container registry is the recommended approach because it enables the orchestrators to easily reference that image, pull it from the container registry and run it as a container. -- After I decided which orchestrator to use, why should I use run its devcontainer using VSCode? - > Running the devcontainer environment through VSCode offers an interactive approach for development. However, this is just a suggestion. You also have the option to use a devcontainer without VSCode. The devcontainer environment guarantees that you have the necessary tools to interact with each orchestrator and the in-vehicle software stack. While you can choose to develop on your local machine, you would need to install the required tools yourself. Please see [Install additional software](https://code.visualstudio.com/docs/devcontainers/create-dev-container#_install-additional-software) if you wish to install additional software into your VSCode devcontainer. -- After I edited the configuration file of a service, what should I do next? - > You will need to restart the smart trailer scenario. If you are using Eclipse Ankaios, please see [Workload Development](./eclipse-ankaios/README.md#workload-development) section. If you are using Eclipse BlueChi, please see the [Managing Workloads](./eclipse-bluechi/README.md#managing-workloads) section. - -### General -This section contains general references that may provide guidance to the scenario you choose. - -#### Configuration Files -- [Which configuration files can I override?](./docs/in-vehicle-stack/config-overrides.md#how-to-override-configuration-for-in-vehicle-stack-containers) - -#### Ibeji -- [How do I create an in-vehicle digital twin model?](https://github.com/eclipse-ibeji/ibeji/blob/main/docs/tutorials/in_vehicle_model/README.md) - > This helps you construct or enhance an in-vehicle digital twin model, and enables you to reference your in-vehicle digital twin model in your code. -- [How do I create a digital twin provider or add additional capabilities to an existing provider?](https://github.com/eclipse-ibeji/ibeji/blob/main/docs/tutorials/provider/README.md) - > A digital twin provider exposes a subset of the in-vehicle's hardware capabilities. This enables digital twin consumers to utilize that subset. Also you may find that you want to add additional capabilities, such as a new in-vehicle signal, to an existing digital twin provider. When you add new capabilities to a provider, such as a new in-vehicle signal, you will need to update the mapping client's configuration or the mapping service that you are using with Freyja. See the [Freyja FAQ](#freyja) for more details. Please see the digital twin provider [smart trailer properties' source code](./in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider/src/main.rs) for a digital twin provider example. - - [How do I build a container image for my digital twin provider?](https://github.com/eclipse-ibeji/ibeji/blob/main/samples/container/README.md#provider) - > You will need to build a container image if you are updating the smart trailer digital twin provider's source code or creating your own digital twin provider. - - [If my digital twin provider is running in a container, how do I override its configuration file?](https://github.com/eclipse-ibeji/ibeji/blob/main/samples/container/README.md#run) - > You do not need to rebuild the container image if you are overriding your digital twin provider's configuration file. - - [How do I use the Managed Subscribe module?](https://github.com/eclipse-ibeji/ibeji/blob/main/samples/managed_subscribe/README.md) - > Using the Managed Subscribe module and dynamic topics in your digital twin provider allows your digital twin consumers to specify the frequency at which they want to receive updates. Please see the digital twin provider [smart trailer properties' source code](./in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider/src/main.rs) for an example. -- [How do I create a digital twin consumer?](https://github.com/eclipse-ibeji/ibeji/blob/main/docs/tutorials/consumer/README.md) - > A digital twin consumer is a software entity that interfaces with the digital representation of the in-vehicle hardware components. Please see the digital twin consumer [smart trailer application's source code](./in-vehicle-stack/scenarios/smart_trailer_use_case/applications/smart_trailer_application/src/main.rs) for a digital twin consumer example. - - [How do I build a container image for my digital twin consumer?](https://github.com/eclipse-ibeji/ibeji/blob/main/samples/container/README.md#consumer) - > You will need to build a container image if you are updating the smart trailer digital twin consumer's source code or creating your own digital twin consumer. - - [If my digital twin consumer is running in a container, how do I override its configuration file?](./docs/in-vehicle-stack/config-overrides.md) - > You do not need to rebuild the container image if you are overriding your digital twin consumer's configuration file. - -#### Chariott -- [How can I use the Service Discovery to register and discover other applications/services?](https://github.com/eclipse-chariott/chariott/blob/main/service_discovery/README.md) - > An application can utilize Chariott's Service Discovery to register with the system and enable other applications to discover it through the Service Discovery system. This can also be used to discover other components in the in-vehicle-stack like Ibeji. - -#### Freyja -- [How do I configure new mappings for Freyja's in-memory mapping client?](https://github.com/eclipse-ibeji/freyja/blob/main/mapping_clients/in_memory_mock_mapping_client/README.md) - > Adding a new in-vehicle signal requires you to configure a new mapping for that in-vehicle signal. -- [How do I override the mapping configuration?](https://github.com/eclipse-ibeji/freyja/blob/main/docs/config-overrides.md) -- [How do I sync in-vehicle signals to the cloud?](./docs/in-vehicle-stack/azure-cloud-connection.md) - > You may want to sync your in-vehicle signals to a cloud digital representation of your in-vehicle. - -### Eclipse Ankaios - -- [Quickstart](https://eclipse-ankaios.github.io/ankaios/0.2/usage/quickstart/) -- [User documentation](https://eclipse-ankaios.github.io/ankaios/0.2/) -- [Working with the startup configuration](https://eclipse-ankaios.github.io/ankaios/0.2/reference/startup-configuration/) -- [Architecture](https://eclipse-ankaios.github.io/ankaios/0.2/architecture/) -- [Control interface examples](https://github.com/eclipse-ankaios/ankaios/tree/v0.2.0-rc1/examples) -- [Suggestions for improvement & Feedback](https://github.com/eclipse-ankaios/ankaios/discussions) - -## Getting Started - -Please note that it is not necessary to use both software orchestrators. You can choose either one to implement your scenario. - -### Need to know -- You need basic knowledge about containerization technologies and tools (e.g. Docker, Podman), but if you have not dealt with it yet, don't worry, just check out a little tutorial (https://docs.docker.com/get-started/) to get a basic understanding of containers and you are prepared. -- Basic skills to deal with distributed systems - -### Prebuilt Container Images - -Please see the instructions on the [Azure Container Registry documentation](./docs/azure/azure_container_registry_instructions.md) for pulling prebuilt container images for the in-vehicle software stack. - -### Eclipse Ankaios Orchestrator - -If you have decided to use Ankaios, you will find an easy to use development environment in the subfolder [eclipse-ankaios](./eclipse-ankaios/README.md), -which you can use for all maestro challenges. - -### Eclipse BlueChi Orchestrator - -If you have decided to use BlueChi, you will find an easy to use development environment in the subfolder [eclipse-bluechi](./eclipse-bluechi/README.md), -which you can use for all maestro challenges. - -### Azure Subscription - -Please see the [SDV Hackathon Azure Pass Code Instructions](./docs/azure/azure_code_redeem_instructions.md) for redeeming an Azure pass code if you are interested in using resources on Azure. - -## In-Vehicle Software Stack Overview - -The in-vehicle stack comprises Eclipse Ibeji, Eclipse Agemo, Eclipse Freyja, and Eclipse Chariott. This stack enables a universal vehicle model to be used across different vehicles, dynamic management of vehicle signal topics for publishing and subscribing, synchronization of in-vehicle signals to a cloud-based digital twin, and the development of applications without the need for specific knowledge about the location of the resources they use. - -We provide two software orchestrators, Ankaios and BlueChi, to orchestrate the in-vehicle stack. Feel free to choose either for this hackathon challenge. - -## Projects Involved - -### In-Vehicle Software Stack -- [Eclipse Agemo](https://github.com/eclipse-chariott/Agemo): Agemo incorporates a Pub Sub Service, a [gRPC](https://grpc.io/docs/what-is-grpc/introduction/) service that facilitates publish/subscribe operations for in-vehicle applications, including but not limited to Eclipse Ibeji and Eclipse Chariott. This service has the capability to register with Chariott, enhancing its discoverability by other applications such as Eclipse Ibeji. It offers dynamic creation and management of topics. - -- [Eclipse Chariott](https://github.com/eclipse-chariott/chariott): Chariott operates as a [gRPC](https://grpc.io/docs/what-is-grpc/introduction/) service, offering a unified interface for application interaction. Chariott enables [Service Discovery](https://github.com/eclipse-chariott/chariott/blob/main/service_discovery/README.md), allowing provider applications to promote their capabilities by registering with Chariott’s service registry. Consumer applications in need of specific resources and capabilities can discover them via Chariott’s service registry. - -- [Eclipse Freyja](https://github.com/eclipse-ibeji/freyja/): Freyja enables seamless synchronization between the vehicle’s digital twin and its cloud-based digital twin. This synchronization allows for a consistent and unified digital representation of the vehicle across both platforms. - -- [Eclipse Ibeji](https://github.com/eclipse-ibeji/ibeji): Ibeji is designed with the goal of enabling a digital depiction of the vehicle’s state and capabilities. It achieves this through an adaptable, open, and dynamic architecture that provides access to the vehicle’s hardware, sensors, and capabilities. This extensible framework allows for a comprehensive and accurate representation of the vehicle’s current status and potential functionalities. - -### In-Vehicle Software Orchestrators -- [Eclipse Ankaios](https://eclipse-ankaios.github.io/ankaios): Ankaios provides workload and container orchestration for automotive High Performance Computing (HPC) software . While it can be used for various fields of applications, it is developed from scratch for automotive use cases and provides a slim yet powerful solution to manage containerized applications. It supports various container runtimes with Podman as the first one, but other container runtimes and even native applications can be supported. Eclipse Ankaios is independent of existing communication frameworks like SOME/IP, DDS, or REST API. - -- [Eclipse BlueChi](https://github.com/containers/bluechi): BlueChi is a systemd service controller intended for multi-node environments with a predefined number of nodes and with a focus on highly regulated ecosystems such as those requiring functional safety. Potential use cases can be found in domains such as transportation, where services need to be controlled across different edge devices and where traditional orchestration tools are not compliant with regulatory requirements. - -## Hack Coaches -Who can be contacted for this challenge for questions. (Slack-handle) - -- Eclipse Agemo, Chariott, Ibeji and Freyja: Jordan Chiu (Slack handle: @Jordan Chiu) -- Eclipse Agemo, Chariott, Ibeji and Freyja: Filipe Prezado (Slack handle: @fprezado) -- Eclipse Ankaios: Chatree Akasarn (Slack handle: @Chatree Akasarn) -- Eclipse Ankaios: Oliver Klapper (Slack handle: @Oliver Klapper) -- Eclipse BlueChi: Leonardo Rossetti (Slack handle: @Leonardo Rossetti) +![wheel_chair_scenario.png](docs%2Fwheelchair_assistant_use_case%2Fdiagrams%2Fwheel_chair_scenario.png) \ No newline at end of file diff --git a/docs/Wheelchair assistant application b/docs/Wheelchair assistant application new file mode 100644 index 0000000..64235aa --- /dev/null +++ b/docs/Wheelchair assistant application @@ -0,0 +1,7 @@ +#### The Wheelchair Assistant Applications +This application takes care of the software implementation corresponding to the person arriving at the car, getting in ,driving, getting out and leaving the car. + - When a person approaches the car, the car lowers itself, opens the front and the back doors. The seat and the steering wheel get adjusted in order for the person to get in. + - A mechanical setting (ramp or a crane) can be used for getting the wheelchair inside the car. + - Once the person gets in, the seat and the steering gets adjusted back for driving, while closing the backdoor. + - On reaching destination, the car is lowered. As the backdoor opens,the wheelchair is retrieved. + - The seats and steering get adjusted for the person to get out of the car. Once they"re out, everything gets readjusted to normal position. diff --git a/docs/wheelchair_assistant_use_case/diagrams/wheel_assistant_states.png b/docs/wheelchair_assistant_use_case/diagrams/wheel_assistant_states.png new file mode 100644 index 0000000..55ea7eb Binary files /dev/null and b/docs/wheelchair_assistant_use_case/diagrams/wheel_assistant_states.png differ diff --git a/docs/wheelchair_assistant_use_case/diagrams/wheel_chair_scenario.png b/docs/wheelchair_assistant_use_case/diagrams/wheel_chair_scenario.png new file mode 100644 index 0000000..9a4f5b7 Binary files /dev/null and b/docs/wheelchair_assistant_use_case/diagrams/wheel_chair_scenario.png differ diff --git a/docs/wheelchair_assistant_use_case/orchester.png b/docs/wheelchair_assistant_use_case/orchester.png new file mode 100644 index 0000000..eeba177 Binary files /dev/null and b/docs/wheelchair_assistant_use_case/orchester.png differ diff --git a/eclipse-bluechi/.devcontainer/devcontainer.json b/eclipse-bluechi/.devcontainer/devcontainer.json deleted file mode 100644 index 49bcb1a..0000000 --- a/eclipse-bluechi/.devcontainer/devcontainer.json +++ /dev/null @@ -1,13 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/rust -{ - "name": "autosd", - "privileged": true, - "image": "sdvblueprint.azurecr.io/sdvblueprint/eclipse-bluechi/devenv:latest", - "overrideCommand": false, - "mounts": [ - "source=${localWorkspaceFolder}/../in-vehicle-stack,target=/workspaces/app/in-vehicle-stack,type=bind" - ], - "workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/app/,type=bind", - "workspaceFolder": "/workspaces/app/" -} \ No newline at end of file diff --git a/eclipse-bluechi/README.md b/eclipse-bluechi/README.md deleted file mode 100644 index cb2a046..0000000 --- a/eclipse-bluechi/README.md +++ /dev/null @@ -1,232 +0,0 @@ -# Eclipse BlueChi - -![Smart trailer blueprint](../docs/diagrams/bluechi.png) - -Upstream documentation: - -BlueChi is a systemd controller that adds a thin layer to enable multi-node -workload management and cross-node dependencies. - -It can handle various workloads such as containers, virtual machines or -applications running on bare metal. To run containers under systemd in an -optimal way it uses Podman's Quadlet implementation. This also enables the usage -of Kubernetes resource definitions to define the workload. - -## Links - -* [BlueChi documentation](https://bluechi.readthedocs.io/en/latest/) -* [BlueChi CLI - documentation]() -* [Podman](https://docs.podman.io/en/latest/) -* [Podman and Quadlet](https://www.redhat.com/sysadmin/quadlet-podman) - -## Prerequisites - -* A container runtime such as [Docker](https://docs.docker.com/get-docker/) or [Podman](https://podman.io/docs/installation) - * Note: non linux machines need to run docker/podman machine. - -## Development Environment - -There are two development environments mentioned in the next section that provide the following components: - -* Eclipse Chariott -* Eclipse Agemo -* Eclipse Ibeji -* Eclipse Freyja -* Eclipse BlueChi -* systemd -* Podman -* Quadlet - -All services are accessible via `localhost:$port`. - -## Two Development Environments - -It is strongly recommended that you use the devcontainer with VSCode. - -NOTE: You can build the development environment image yourself in case you are having trouble running it from this repository: https://github.com/odra/eclipse-bluechi-hackathon-image - -## Run the devcontainer with VSCode - -### Prerequisite -* Ensure that the [Remote Development extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) installed in VSCode - -* Upstream documentation: - * - * https://github.com/devcontainers/cli - -The following steps below uses the VSCode devcontainer extension. If you prefer, you can use [VSCode's devcontainer's CLI](https://github.com/devcontainers/cli) instead. - -1. Login to the Azure's container registry to allow VSCode to pull the devcontainer image: - ```shell - docker login sdvblueprint.azurecr.io - ``` -1. You can use the VSCode devcontainer extension to start your containerized development environment. - ```shell - cd /maestro-challenge/eclipse-bluechi - code . - ``` - -1. VSCode detects automatically that a `.devcontainer` folder exists inside this subfolder. Please confirm the dialog to reopen VSCode inside the devcontainer. Afterwards, open a new terminal inside the devcontainer in VSCode. - -## Run the devcontainer without VSCode - -Upstream documentation: - -1. Login to the Azure's container registry: - ```shell - docker login sdvblueprint.azurecr.io - ``` - -1. Start the devcontainer by running: - ```sh - docker run \ - -d \ - --privileged \ - --name autosd-eclipse \ - -v /maestro-challenge/in-vehicle-stack:/workspaces/app/in-vehicle-stack \ - --workdir /workspaces/app \ - sdvblueprint.azurecr.io/sdvblueprint/eclipse-bluechi/devenv:latest - ``` - - Ensure to replace `` with your own value. - -1. Enter into the devcontainer and interact with BlueChi: - ```sh - docker exec -it autosd-eclipse /bin/bash - bluechictl list-units - ``` - -## Bootstrapping - -You need to bootstrap all the Eclipse services once you got your eclipse-bluechi devcontainer running. - -### Starting all the services -1. Inside your devcontainer, you will need to login to Azure's container registry to pull all required images: - ```sh - podman login \ - --username \ - --password \ - sdvblueprint.azurecr.io - ``` - -2. Then it is time to start all services which can be done by executing the bootstrap script: - ```sh - $ bluechi-env-bootstrap - ``` -The above command will pull all the required images and start all services. - -### Cleanup -1. There is also a script to stop all services: - ```sh - $ bluechi-env-cleanup - ``` -Keep in mind that stopping services will purge all the containers that are related to such services as well. - -Both the `bluechi-env-bootstrap` and `bluechi-env-cleaup` scripts are located in `/usr/local/bin/` in case you are interested in checking them out. - -## Managing Workloads - -This section describes how to deploy and perform administrative's tasks using -systemd and BlueChi. - -### Deploying Applications - -BlueChi relies on three components to handle containerized applications: - -* systemd -* Quadlet -* Podman - -Application definitions are stored in `/etc/containers/systemd`. An application -needs two essential files: - -* `{SERVICE_NAME}.kube`: Used by systemd to point to a Kubernetes resource definition - containing the workload definition. - - Example of `freyja.kube`: - ```kube - # https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html - [Kube] - Yaml=freyja.yml - - # Commented to disable the service to automatically start - # [Install] - # WantedBy=default.target - ``` -* `{SERVICE_NAME}.yaml`: A Kubernetes resource definition (either `v1.Pod` or - `apps/v1.Deployment`) that describes the workload. - - Example of `freyja.yml`: - ```yaml - --- - apiVersion: apps/v1 - kind: Deployment - metadata: - labels: - app: freyja - name: freyja - spec: - replicas: 1 - selector: - matchLabels: - app: freyja - template: - metadata: - labels: - app: freyja - spec: - hostNetwork: true - containers: - - name: local - image: sdvblueprint.azurecr.io/sdvblueprint/eclipse-freyja/local-with-ibeji:0.1.0 - imagePullPolicy: IfNotPresent - ``` - -If you edit the source code of a component then build and push an image of it to your container registry, you will need to edit the corresponding `{SERVICE_NAME}.yaml` file in the `/etc/containers/systemd` directory. The value of the `image` field in the `{SERVICE_NAME}.yaml` file should point to the image in your container registry. - -Creating, changing or updating a file in `/etc/containers/systemd` requires you to run `systemctl daemon-reload` afterwards to generate the corresponding systemd unit files in -`/run/systemd/generator`. - -### Service Lifecycle - -Services can be managed by using `systemctl`, systemd's administrative CLI. - -Starting, stopping, restarting services is as easy as: - -* `systemctl stop {SERVICE_NAME}` -* `systemctl start {SERVICE_NAME}` -* `systemctl restart {SERVICE_NAME}` - -> Make sure to run `systemctl daemon-reload` in case something changed in either Quadlet or systemd unit files. - -### Monitoring and Logs - -BlueChi's CLI (`bluechictl`), can be used to retrieve information from -managed nodes: -. - -#### Using Systemctl - -Simply run `systemctl status {SERVICE_NAME}` where `{SERVICE_NAME}` is the name of your .kube file. - -#### Using journalctl - -This is valid for any systemd defined service, simply run `journalctl -xeu {SERVICE_NAME}` - -#### Podman - -You can also list all active containers by running `podman ps` and then `podman logs {CONTAINER_NAME_OR_ID}` to -get logs from the container using podman. Replace `{CONTAINER_NAME_OR_ID}` with the container's name or ID. - -## Running the Smart Trailer Example with BlueChi's devcontainer -Inside of the [devcontainer](#two-development-environments): -1. Follow the instructions in [Starting All the Services](#starting-all-the-services) to start up the in-vehicle stack. -1. Run the script `start_trailer_applications_bluechi.sh` to monitor for the trailer to be connected. It can be found at `in-vehicle-stack/scenarios/smart_trailer_use_case/scripts/start_trailer_applications_bluechi.sh`. -1. In another terminal window inside the devcontainer, start the `trailer-connected` service to simulate the trailer being connected: - ```shell - systemctl start trailer-connected - ``` -1. Verify the output in the terminal window of the `start_trailer_applications_bluechi.sh` script. You should see that two more services were started in response to the trailer being connected. -1. Use [Monitoring and Logs](#monitoring-and-logs) to check that the `smart-trailer` service is now receiving the value of the trailer weight every 10 seconds. -1. When you are ready to clean up, use the cleanup script mentioned in [Cleanup](#cleanup). diff --git a/in-vehicle-stack/Cargo.toml b/in-vehicle-stack/Cargo.toml index 2e1fb78..eda5044 100644 --- a/in-vehicle-stack/Cargo.toml +++ b/in-vehicle-stack/Cargo.toml @@ -1,33 +1,46 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. -# SPDX-License-Identifier: MIT - -[workspace] - -members = [ - # in-vehicle-stack interfaces - "proto_build", - - # smart_trailer_use_case - "scenarios/smart_trailer_use_case/applications/smart_trailer_application", - "scenarios/smart_trailer_use_case/digital_twin_providers/common", - "scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider", - "scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider", - "scenarios/smart_trailer_use_case/proto_build", -] - -[workspace.dependencies] -env_logger= "0.10.0" -log = "0.4.20" -paho-mqtt = "0.12" -parking_lot = "0.12.1" -prost = "0.12.1" -serde = "1.0.190" -serde_derive = "1.0.163" -serde_json = "^1.0" -strum = "0.25" -strum_macros = "0.25.1" -tokio = "1.29.1" -tonic = "0.10.2" -tonic-build = "0.10.2" -uuid = "1.2.2" +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +[workspace] + +members = [ + # in-vehicle-stack interfaces + "proto_build", + + # smart_trailer_use_case + "scenarios/smart_trailer_use_case/applications/smart_trailer_application", + "scenarios/smart_trailer_use_case/digital_twin_providers/common", + "scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider", + "scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider", + "scenarios/smart_trailer_use_case/proto_build", + + # wheelchair_assistant_use_case + "scenarios/wheelchair_assistant_use_case/digital_twin_providers/common", + "scenarios/wheelchair_assistant_use_case/proto_build", + "scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_off_provider", + "scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_on_provider", + "scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_lock_provider", + "scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_unlock_provider", + "scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider", + "scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_increasing_provider", + "scenarios/wheelchair_assistant_use_case/applications/wheelchair_distance_application", + "scenarios/wheelchair_assistant_use_case/applications/wheelchair_assistant_application", + +] + +[workspace.dependencies] +env_logger= "0.10.0" +log = "0.4.20" +paho-mqtt = "0.12" +parking_lot = "0.12.1" +prost = "0.12.1" +serde = "1.0.190" +serde_derive = "1.0.163" +serde_json = "^1.0" +strum = "0.25" +strum_macros = "0.25.1" +tokio = "1.29.1" +tonic = "0.10.2" +tonic-build = "0.10.2" +uuid = "1.2.2" diff --git a/in-vehicle-stack/build_wheelchair_assistant_container.sh b/in-vehicle-stack/build_wheelchair_assistant_container.sh new file mode 100755 index 0000000..9287292 --- /dev/null +++ b/in-vehicle-stack/build_wheelchair_assistant_container.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Deklariere das Array +PROVIDER_CONTAINERS=( + "car_off_provider" + "car_on_provider" + "carkey_lock_provider" + "carkey_unlock_provider" + "wheelchair_distance_decreasing_provider" + "wheelchair_distance_increasing_provider" +) + +APPLICATION_CONTAINERS=( + "wheelchair_distance_application" +) + +# Iterate over provider containers to build +for CONTAINER in "${PROVIDER_CONTAINERS[@]}"; do + podman build -t sdvblueprint.azurecr.io/sdvblueprint/in-vehicle-stack/${CONTAINER}:0.1.0 -f "scenarios/wheelchair_assistant_use_case/digital_twin_providers/${CONTAINER}/Dockerfile" . +done + +# Iterate over provider containers to build +for CONTAINER in "${APPLICATION_CONTAINERS[@]}"; do + podman build -t sdvblueprint.azurecr.io/sdvblueprint/in-vehicle-stack/${CONTAINER}:0.1.0 -f "scenarios/wheelchair_assistant_use_case/applications/${CONTAINER}/Dockerfile" . +done \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital-twin-model/dtdl/trailer.json b/in-vehicle-stack/scenarios/smart_trailer_use_case/digital-twin-model/dtdl/trailer.json deleted file mode 100644 index 43aa1ab..0000000 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital-twin-model/dtdl/trailer.json +++ /dev/null @@ -1,24 +0,0 @@ -[ - { - "@context": ["dtmi:dtdl:context;3"], - "@type": "Interface", - "@id": "dtmi:sdv:Trailer;1", - "description": "Trailer used for transporting cargo", - "contents": [ - { - "@type": "Property", - "@id": "dtmi:sdv:Trailer:Weight;1", - "name": "TrailerWeight", - "description": "The weight of the trailer", - "schema": "integer" - }, - { - "@type": "Property", - "@id": "dtmi:sdv:Trailer:IsTrailerConnected;1", - "name": "IsTrailerConnected", - "description": "Is trailer connected?", - "schema": "boolean" - } - ] - } -] \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital-twin-model/src/trailer_v1.rs b/in-vehicle-stack/scenarios/smart_trailer_use_case/digital-twin-model/src/trailer_v1.rs deleted file mode 100644 index 0391b88..0000000 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital-twin-model/src/trailer_v1.rs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -// Note: This code was manually written based on the structure of the -// vehicle model in "../dtdl/trailer.json" -// In the future this code could be generated from a DTDL spec. - -pub mod trailer { - pub mod trailer_weight { - pub const ID: &str = "dtmi:sdv:Trailer:Weight;1"; - pub const NAME: &str = "TrailerWeight"; - pub const DESCRIPTION: &str = "The weight of the trailer"; - pub type TYPE = i32; - } - - pub mod is_trailer_connected { - pub const ID: &str = "dtmi:sdv:Trailer:IsTrailerConnected;1"; - pub const NAME: &str = "IsTrailerConnected"; - pub const DESCRIPTION: &str = "Is trailer connected?"; - pub type TYPE = bool; - } -} diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/src/trailer_connected_provider_impl.rs b/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/src/trailer_connected_provider_impl.rs deleted file mode 100644 index 8444a94..0000000 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/src/trailer_connected_provider_impl.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -//! Module containing gRPC service implementation based on [`interfaces::digital_twin_get_provider.proto`]. -//! -//! Provides a gRPC endpoint for getting if the trailer is connected -use smart_trailer_interfaces::digital_twin_get_provider::v1::digital_twin_get_provider_server::DigitalTwinGetProvider; -use smart_trailer_interfaces::digital_twin_get_provider::v1::{GetRequest, GetResponse}; -use tonic::{Request, Response, Status}; - -/// Base structure for the Trailer Connected Provider gRPC service. -#[derive(Default)] -pub struct TrailerConnectedProviderImpl {} - -#[tonic::async_trait] -impl DigitalTwinGetProvider for TrailerConnectedProviderImpl { - /// This function returns the value of "is_trailer_connected" property - async fn get(&self, _request: Request) -> Result, Status> { - // For now, we assume that if this provider is active, the trailer is connected - // To expand this use case, we could simulate the trailer being disconnected as well - let get_response = GetResponse { - property_value: true, - }; - Ok(Response::new(get_response)) - } -} diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/scripts/start_trailer_applications_ankaios.sh b/in-vehicle-stack/scenarios/smart_trailer_use_case/scripts/start_trailer_applications_ankaios.sh deleted file mode 100755 index 694e614..0000000 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/scripts/start_trailer_applications_ankaios.sh +++ /dev/null @@ -1,113 +0,0 @@ -#!/bin/bash -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. -# SPDX-License-Identifier: MIT - -set -e - -# This script requires jq and grpcurl to be installed -# These are included in the ankaios devcontainer, but if you want to run it outside -# you could add the commands to install them here -# Check if grpcurl is installed -if !command -v grpcurl &> /dev/null -then - echo "grpcurl could not be found; please install it and run again" - exit 1 -fi - -# Check if jq is installed -if !command -v jq &> /dev/null -then - echo "jq could not be found; please install it and run again" - exit 1 -fi - -# Get the directory of where the script is located -# All relative paths will be in relation to this -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) - -# The Ibeji gRPC server address -SERVER="0.0.0.0:5010" - -# The Ibeji FindById gRPC service and method -SERVICE="invehicle_digital_twin.InvehicleDigitalTwin" -METHOD="FindById" - -# The request body: The IsTrailerConnected signal -BODY='{"id":"dtmi:sdv:Trailer:IsTrailerConnected;1"}' - -PROTO_PATH="${SCRIPT_DIR}/../../../interfaces/invehicle_digital_twin/v1" -PROTO="invehicle_digital_twin.proto" - -EXPECTED_PROTOCOL="grpc" -EXPECTED_OPERATION="get" - -# Call FindById in a loop until something is returned -while true; do - STATUS=0 - OUTPUT=$(grpcurl -import-path $PROTO_PATH -proto $PROTO -plaintext -d "$BODY" $SERVER $SERVICE/$METHOD 2>&1) || STATUS=$? - - # Check if the output contains entityAccessInfo (the response from Ibeji when a provider is found) - if echo "$OUTPUT" | grep -iq "EntityAccessInfo" - then - echo "The FindById call was successful. Output:" - echo "$OUTPUT" - break - else - echo "Provider not found. Status Code '$STATUS' Error '$OUTPUT'" - echo "The trailer is not connected. Retrying..." - sleep 5 - fi -done - -# Parse the output as a JSON object using jq and extract the endpoints -ENDPOINTS=$(echo $OUTPUT | jq -c '.entityAccessInfo.endpointInfoList[]') - -# Loop through each endpoint -for ENDPOINT in $ENDPOINTS -do - # Check if protocol is what we expect - if [[ $(echo $ENDPOINT | jq -r '.protocol' | tr '[:upper:]' '[:lower:]') == $EXPECTED_PROTOCOL ]] - then - OPERATIONS=$(echo $ENDPOINT | jq -r '.operations[]') - # Loop through each operation and check if this endpoint supports the expected operation - for OPERATION in $OPERATIONS - do - if [[ $(echo $OPERATION | tr '[:upper:]' '[:lower:]') == $EXPECTED_OPERATION ]] - then - URI=$(echo $ENDPOINT | jq -r '.uri') - CONTEXT=$(echo $ENDPOINT | jq -r '.context') - - # We need the authority for the server, so remove the http:// - get_server=$(echo "$URI" | sed 's/http:\/\///g') - - # Call get for the "trailer connected provider" to check if it's connected - GET_PROTO_PATH="${SCRIPT_DIR}/../interfaces" - GET_PROTO="digital_twin_get_provider.proto" - GET_SERVER=$get_server - GET_SERVICE="digital_twin_get_provider.DigitalTwinGetProvider" - GET_METHOD="Get" - GET_OUTPUT=$(grpcurl -import-path $GET_PROTO_PATH -proto $GET_PROTO -plaintext $GET_SERVER $GET_SERVICE/$GET_METHOD 2>&1) - - # For now, this always returns true, this can be expanded to simulate connecting and disconnecting the trailer - if [[ $(echo $GET_OUTPUT | jq -r '.propertyValue') ]] - then - echo "Trailer is connected! Starting workloads to manage it" - - # Start up the other workloads using podman - CFG_PROVIDER=$'image: sdvblueprint.azurecr.io/sdvblueprint/in-vehicle-stack/trailer_properties_provider:0.1.0\ncommandOptions: ["--network", "host", "--name", "trailer_properties_provider"]' - CFG_APP=$'image: sdvblueprint.azurecr.io/sdvblueprint/in-vehicle-stack/smart_trailer_application:0.1.0\ncommandOptions: ["--network", "host", "--name", "smart_trailer_application"]' - - ank run workload trailer_properties_provider --runtime podman --config "$CFG_PROVIDER" --agent agent_A - ank run workload smart_trailer_application --runtime podman --config "$CFG_APP" --agent agent_A - - echo "Called Ankaios to start the Trailer Properties Digital Twin Provider and Smart Trailer Application" - echo "Check Ankaios status with 'ank get workloads'" - exit 0 - fi - fi - done - fi -done -# We didn't find an endpoint which satisfied our conditions -exit 1 \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/scripts/start_trailer_applications_bluechi.sh b/in-vehicle-stack/scenarios/smart_trailer_use_case/scripts/start_trailer_applications_bluechi.sh deleted file mode 100755 index 7089d7e..0000000 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/scripts/start_trailer_applications_bluechi.sh +++ /dev/null @@ -1,112 +0,0 @@ -#!/bin/bash -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. -# SPDX-License-Identifier: MIT - -set -e - -# This script requires jq and grpcurl to be installed -# These are included in the bluechi devcontainer, but if you want to run it outside -# you could add the commands to install them here -# Check if grpcurl is installed -if !command -v grpcurl &> /dev/null -then - echo "grpcurl could not be found; please install it and run again" - exit 1 -fi - -# Check if jq is installed -if !command -v jq &> /dev/null -then - echo "jq could not be found; please install it and run again" - exit 1 -fi - -# Get the directory of where the script is located -# All relative paths will be in relation to this -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) - -# The Ibeji gRPC server address -SERVER="0.0.0.0:5010" - -# The Ibeji FindById gRPC service and method -SERVICE="invehicle_digital_twin.InvehicleDigitalTwin" -METHOD="FindById" - -# The request body: The IsTrailerConnected signal -BODY='{"id":"dtmi:sdv:Trailer:IsTrailerConnected;1"}' - -PROTO_PATH="${SCRIPT_DIR}/../../../interfaces/invehicle_digital_twin/v1" -PROTO="invehicle_digital_twin.proto" - -EXPECTED_PROTOCOL="grpc" -EXPECTED_OPERATION="get" - -# Call FindById in a loop until something is returned -while true; do - STATUS=0 - OUTPUT=$(grpcurl -import-path $PROTO_PATH -proto $PROTO -plaintext -d "$BODY" $SERVER $SERVICE/$METHOD 2>&1) || STATUS=$? - - # Check if the output contains entityAccessInfo (the response from Ibeji when a provider is found) - if echo "$OUTPUT" | grep -iq "EntityAccessInfo" - then - echo "The FindById call was successful. Output:" - echo "$OUTPUT" - break - else - echo "Provider not found. Status Code '$STATUS' Error '$OUTPUT'" - echo "The trailer is not connected. Retrying..." - sleep 5 - fi -done - -# Parse the output as a JSON object using jq and extract the endpoints -ENDPOINTS=$(echo $OUTPUT | jq -c '.entityAccessInfo.endpointInfoList[]') - -# Loop through each endpoint -for ENDPOINT in $ENDPOINTS -do - # Check if protocol is what we expect - if [[ $(echo $ENDPOINT | jq -r '.protocol' | tr '[:upper:]' '[:lower:]') == $EXPECTED_PROTOCOL ]] - then - OPERATIONS=$(echo $ENDPOINT | jq -r '.operations[]') - # Loop through each operation and check if this endpoint supports the expected operation - for OPERATION in $OPERATIONS - do - if [[ $(echo $OPERATION | tr '[:upper:]' '[:lower:]') == $EXPECTED_OPERATION ]] - then - URI=$(echo $ENDPOINT | jq -r '.uri') - CONTEXT=$(echo $ENDPOINT | jq -r '.context') - - # We need the authority for the server, so remove the http:// - get_server=$(echo "$URI" | sed 's/http:\/\///g') - - # Call get for the "trailer connected provider" to check if it's connected - GET_PROTO_PATH="${SCRIPT_DIR}/../interfaces" - GET_PROTO="digital_twin_get_provider.proto" - GET_SERVER=$get_server - GET_SERVICE="digital_twin_get_provider.DigitalTwinGetProvider" - GET_METHOD="Get" - GET_OUTPUT=$(grpcurl -import-path $GET_PROTO_PATH -proto $GET_PROTO -plaintext $GET_SERVER $GET_SERVICE/$GET_METHOD 2>&1) - - # For now, this always returns true, this can be expanded to simulate connecting and disconnecting the trailer - if [[ $(echo $GET_OUTPUT | jq -r '.propertyValue') ]] - then - echo "Trailer is connected! Starting workloads to manage it" - - # The service.kube and service.yml files for these two services are included in the BlueChi devcontainer - # See /etc/containers/systemd for these files - # Start up the other workloads using systemctl - systemctl start trailer-properties - systemctl start smart-trailer - - echo "Called systemctl to start the Trailer Properties Digital Twin Provider and Smart Trailer Application" - echo "Check systemctl status with 'systemctl status trailer-properties' and 'systemctl status smart-trailer' for status" - exit 0 - fi - fi - done - fi -done -# We didn't find an endpoint which satisfied our conditions -exit 1 \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_assistant_application/Cargo.toml b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_assistant_application/Cargo.toml new file mode 100644 index 0000000..0d36000 --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_assistant_application/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "wheelchair_assistant_application" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[dependencies] +wheelchair_digital_twin_model = { path = "../../digital-twin-model" } +wheelchair_digital_twin_providers_common = { path = "../../digital_twin_providers/common" } +env_logger= { workspace = true } +log = { workspace = true } +interfaces = { path = "../../../../proto_build"} +paho-mqtt = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] } +tonic = { workspace = true } +uuid = { workspace = true, features = ["v4", "fast-rng", "macro-diagnostics"] } +serde = { workspace = true } +serde_derive = { workspace = true } +serde_json = { workspace = true } \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/applications/smart_trailer_application/Dockerfile b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_assistant_application/Dockerfile similarity index 95% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/applications/smart_trailer_application/Dockerfile rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_assistant_application/Dockerfile index 440e072..65bbcc2 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/applications/smart_trailer_application/Dockerfile +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_assistant_application/Dockerfile @@ -1,71 +1,71 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. -# SPDX-License-Identifier: MIT - -# Comments are provided throughout this file to help you get started. -# If you need more help, visit the Dockerfile reference guide at -# https://docs.docker.com/engine/reference/builder/ - -################################################################################ -# Create a stage for building the application. - -ARG RUST_VERSION=1.72.1 -FROM docker.io/library/rust:${RUST_VERSION}-slim-bullseye AS build -ARG APP_NAME=smart_trailer_application -WORKDIR /sdv - -COPY ./ . - -# Add Build dependencies. -RUN apt update && apt upgrade -y && apt install -y \ - cmake \ - libssl-dev \ - pkg-config \ - protobuf-compiler - -# Check that APP_NAME argument is valid. -RUN sanitized=$(echo "${APP_NAME}" | tr -dc '^[a-zA-Z_0-9-]+$'); \ -[ "$sanitized" = "${APP_NAME}" ] || { \ - echo "ARG 'APP_NAME' is invalid. APP_NAME='${APP_NAME}' sanitized='${sanitized}'"; \ - exit 1; \ -} - -# Build the application -RUN cargo build --release --bin "${APP_NAME}" - -# Copy the built application to working directory. -RUN cp ./target/release/"${APP_NAME}" /sdv/service - -################################################################################ -# Create a new stage for running the application that contains the minimal -# runtime dependencies for the application. This often uses a different base -# image from the build stage where the necessary files are copied from the build -# stage. -# -# The example below uses the debian bullseye image as the foundation for running the app. -# By specifying the "bullseye-slim" tag, it will also use whatever happens to be the -# most recent version of that tag when you build your Dockerfile. If -# reproducability is important, consider using a digest -# (e.g., debian@sha256:ac707220fbd7b67fc19b112cee8170b41a9e97f703f588b2cdbbcdcecdd8af57). -FROM docker.io/library/debian:bullseye-slim AS final - -# Create a non-privileged user that the app will run under. -# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user -ARG UID=10001 -RUN adduser \ - --disabled-password \ - --gecos "" \ - --home "/nonexistent" \ - --shell "/sbin/nologin" \ - --no-create-home \ - --uid "${UID}" \ - appuser -USER appuser - -WORKDIR /sdv - -# Copy the executable from the "build" stage. -COPY --from=build /sdv/service /sdv/ - -# What the container should run when it is started. +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Dockerfile reference guide at +# https://docs.docker.com/engine/reference/builder/ + +################################################################################ +# Create a stage for building the application. + +ARG RUST_VERSION=1.72.1 +FROM docker.io/library/rust:${RUST_VERSION}-slim-bullseye AS build +ARG APP_NAME=wheelchair_assistant_application +WORKDIR /sdv + +COPY ./ . + +# Add Build dependencies. +RUN apt update && apt upgrade -y && apt install -y \ + cmake \ + libssl-dev \ + pkg-config \ + protobuf-compiler + +# Check that APP_NAME argument is valid. +RUN sanitized=$(echo "${APP_NAME}" | tr -dc '^[a-zA-Z_0-9-]+$'); \ +[ "$sanitized" = "${APP_NAME}" ] || { \ + echo "ARG 'APP_NAME' is invalid. APP_NAME='${APP_NAME}' sanitized='${sanitized}'"; \ + exit 1; \ +} + +# Build the application +RUN cargo build --release --bin "${APP_NAME}" + +# Copy the built application to working directory. +RUN cp ./target/release/"${APP_NAME}" /sdv/service + +################################################################################ +# Create a new stage for running the application that contains the minimal +# runtime dependencies for the application. This often uses a different base +# image from the build stage where the necessary files are copied from the build +# stage. +# +# The example below uses the debian bullseye image as the foundation for running the app. +# By specifying the "bullseye-slim" tag, it will also use whatever happens to be the +# most recent version of that tag when you build your Dockerfile. If +# reproducability is important, consider using a digest +# (e.g., debian@sha256:ac707220fbd7b67fc19b112cee8170b41a9e97f703f588b2cdbbcdcecdd8af57). +FROM docker.io/library/debian:bullseye-slim AS final + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser +USER appuser + +WORKDIR /sdv + +# Copy the executable from the "build" stage. +COPY --from=build /sdv/service /sdv/ + +# What the container should run when it is started. CMD ["/sdv/service"] \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_assistant_application/src/main.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_assistant_application/src/main.rs new file mode 100644 index 0000000..4f30322 --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_assistant_application/src/main.rs @@ -0,0 +1,480 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +use std::env; + +use wheelchair_digital_twin_model::{car_v1, Metadata}; +use wheelchair_digital_twin_providers_common::constants::chariott::{ + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_KIND, + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_REFERENCE, INVEHICLE_DIGITAL_TWIN_SERVICE_NAME, + INVEHICLE_DIGITAL_TWIN_SERVICE_NAMESPACE, INVEHICLE_DIGITAL_TWIN_SERVICE_VERSION, +}; +use wheelchair_digital_twin_providers_common::constants::{ + constraint_type, digital_twin_operation, digital_twin_protocol, +}; +use wheelchair_digital_twin_providers_common::utils::{ + discover_digital_twin_provider_using_ibeji, discover_service_using_chariott, get_uri, +}; +use env_logger::{Builder, Target}; +use interfaces::module::managed_subscribe::v1::managed_subscribe_client::ManagedSubscribeClient; +use interfaces::module::managed_subscribe::v1::{ + Constraint, SubscriptionInfoRequest, SubscriptionInfoResponse, +}; +use interfaces::invehicle_digital_twin::v1::invehicle_digital_twin_client::InvehicleDigitalTwinClient; +use interfaces::invehicle_digital_twin::v1::{EndpointInfo, EntityAccessInfo, RegisterRequest}; +use log::{debug, info, warn, LevelFilter}; +use paho_mqtt as mqtt; +use tokio::signal; +use tokio::task::JoinHandle; +use tokio::time::{sleep, Duration}; +use tonic::{Request, Status}; +use tokio::sync::watch; +use uuid::Uuid; +use serde_derive::{Deserialize, Serialize}; +use std::sync::atomic::{AtomicBool, Ordering}; + +const FREQUENCY_MS_FLAG: &str = "freq_ms="; +const MQTT_CLIENT_ID: &str = "wheelchair-assistant-consumer"; + +// TODO: These could be added in configuration +const CHARIOTT_SERVICE_DISCOVERY_URI: &str = "http://0.0.0.0:50000"; + +const DEFAULT_FREQUENCY_MS: u64 = 10000; // 10 seconds + +// Constants used for retry logic +const MAX_RETRIES: i32 = 10; // for demo purposes we will retry a maximum of 10 times + // By default we will wait 5 seconds between retry attempts +const DURATION_BETWEEN_ATTEMPTS: Duration = Duration::from_secs(5); + +static door_open: AtomicBool = AtomicBool::new(false); +static steering_wheel_up: AtomicBool = AtomicBool::new(false); +static driver_seat_back: AtomicBool = AtomicBool::new(false); + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag="type")] +struct WheelchairAssistantStateProperty { + #[serde(rename = "WheelchairAssistantState")] + car_wheelchair_assistant_state: car_v1::car::car_wheelchair_assistant_state::TYPE, + #[serde(rename = "$metadata")] + metadata: Metadata, +} + +/// Get car adjustment subscription information from managed subscribe endpoint. +/// +/// # Arguments +/// * `managed_subscribe_uri` - The managed subscribe URI. +/// * `constraints` - Constraints for the managed topic. +async fn get_car_adjust_subscription_info( + managed_subscribe_uri: &str, + constraints: Vec +) -> Result { + // Create gRPC client. + let mut client = ManagedSubscribeClient::connect(managed_subscribe_uri.to_string()) + .await + .map_err(|err| Status::from_error(err.into()))?; + + let request = Request::new(SubscriptionInfoRequest { + entity_id: car_v1::car::car_wheelchair_assistant_state::ID.to_string(), + constraints, + }); + + let response = client.get_subscription_info(request).await?; + + Ok(response.into_inner()) +} + +/// Receive car adjustment updates. +/// +/// # Arguments +/// * `broker_uri` - The broker URI. +/// * `topic` - The topic. +async fn receive_car_adjust_updates( + broker_uri: &str, + topic: &str, +) -> Result, String> { + // Create a unique id for the client. + let client_id = format!("{MQTT_CLIENT_ID}-{}", Uuid::new_v4()); + + let create_opts = mqtt::CreateOptionsBuilder::new() + .server_uri(broker_uri) + .client_id(client_id) + .finalize(); + + let client = mqtt::Client::new(create_opts) + .map_err(|err| format!("Failed to create the client due to '{err:?}'"))?; + + let receiver = client.start_consuming(); + + // Setup task to handle clean shutdown. + let ctrlc_cli = client.clone(); + tokio::spawn(async move { + _ = signal::ctrl_c().await; + + // Tells the client to shutdown consuming thread. + ctrlc_cli.stop_consuming(); + }); + + // Last Will and Testament + let lwt = mqtt::MessageBuilder::new() + .topic("test") + .payload("Receiver lost connection") + .finalize(); + + let conn_opts = mqtt::ConnectOptionsBuilder::new_v5() + .keep_alive_interval(Duration::from_secs(30)) + .clean_session(false) + .will_message(lwt) + .finalize(); + + let _connect_response = client + .connect(conn_opts) + .map_err(|err| format!("Failed to connect due to '{err:?}")); + + let mut _subscribe_response = client + .subscribe(topic, mqtt::types::QOS_1) + .map_err(|err| format!("Failed to subscribe to topic {topic} due to '{err:?}'")); + + // Copy topic for separate thread. + let topic_string = topic.to_string(); + + let sub_handle = tokio::spawn(async move { + for msg in receiver.iter() { + if let Some(msg) = msg { + // Here we log the message received. This could be expanded to parsing the message, + // Obtaining the wheelchair assistant state and making decisions based on the weight + // For example, adjusting body functions or powertrain of the towing vehicle. + let payload_str = msg.payload_str(); + let msg_des: WheelchairAssistantStateProperty = serde_json::from_str(&payload_str).unwrap(); + let new_state = msg_des.car_wheelchair_assistant_state; + info!("{}", new_state); + info!("{}", msg); + + if new_state == 3 { //HOLD + info!("Adjusting the car!"); + door_open.store(true, Ordering::Relaxed); + steering_wheel_up.store(true, Ordering::Relaxed); + driver_seat_back.store(true, Ordering::Relaxed); + } else { + info!("No need to rearrange"); + } + + } else if !client.is_connected() { + if client.reconnect().is_ok() { + _subscribe_response = client + .subscribe(topic_string.as_str(), mqtt::types::QOS_1) + .map_err(|err| { + format!("Failed to subscribe to topic {topic_string} due to '{err:?}'") + }); + } else { + break; + } + } + } + + if client.is_connected() { + debug!("Disconnecting"); + client.unsubscribe(topic_string.as_str()).unwrap(); + client.disconnect(None).unwrap(); + } + }); + + Ok(sub_handle) +} + +/// Register the car seat adjustment property's endpoint. +/// +/// # Arguments +/// * `invehicle_digital_twin_uri` - The In-Vehicle Digital Twin URI. +/// * `provider_uri` - The provider's URI. +async fn provider_register_seat_adjustment( + invehicle_digital_twin_uri: &str, + provider_uri: &str, +) -> Result<(), Status> { + let endpoint_info = EndpointInfo { + protocol: digital_twin_protocol::GRPC.to_string(), + operations: vec![digital_twin_operation::MANAGEDSUBSCRIBE.to_string()], + uri: provider_uri.to_string(), + context: "GetSubscriptionInfo".to_string(), + }; + + let entity_access_info = EntityAccessInfo { + name: car_v1::car::is_car_seat_in_assist_position::NAME.to_string(), + id: car_v1::car::is_car_seat_in_assist_position::ID.to_string(), + description: car_v1::car::is_car_seat_in_assist_position::DESCRIPTION.to_string(), + endpoint_info_list: vec![endpoint_info], + }; + + let mut client = InvehicleDigitalTwinClient::connect(invehicle_digital_twin_uri.to_string()) + .await + .map_err(|e| Status::internal(e.to_string()))?; + let request = tonic::Request::new(RegisterRequest { + entity_access_info_list: vec![entity_access_info], + }); + let _response = client.register(request).await?; + + Ok(()) +} + +/// Register the car door adjustment property's endpoint. +/// +/// # Arguments +/// * `invehicle_digital_twin_uri` - The In-Vehicle Digital Twin URI. +/// * `provider_uri` - The provider's URI. +async fn provider_register_door_adjustment( + invehicle_digital_twin_uri: &str, + provider_uri: &str, +) -> Result<(), Status> { + let endpoint_info = EndpointInfo { + protocol: digital_twin_protocol::GRPC.to_string(), + operations: vec![digital_twin_operation::MANAGEDSUBSCRIBE.to_string()], + uri: provider_uri.to_string(), + context: "GetSubscriptionInfo".to_string(), + }; + + let entity_access_info = EntityAccessInfo { + name: car_v1::car::is_car_door_open::NAME.to_string(), + id: car_v1::car::is_car_door_open::ID.to_string(), + description: car_v1::car::is_car_door_open::DESCRIPTION.to_string(), + endpoint_info_list: vec![endpoint_info], + }; + + let mut client = InvehicleDigitalTwinClient::connect(invehicle_digital_twin_uri.to_string()) + .await + .map_err(|e| Status::internal(e.to_string()))?; + let request = tonic::Request::new(RegisterRequest { + entity_access_info_list: vec![entity_access_info], + }); + let _response = client.register(request).await?; + + Ok(()) +} + +/// Register the car steering wheel adjustment property's endpoint. +/// +/// # Arguments +/// * `invehicle_digital_twin_uri` - The In-Vehicle Digital Twin URI. +/// * `provider_uri` - The provider's URI. +async fn provider_register_steering_wheel_adjustment( + invehicle_digital_twin_uri: &str, + provider_uri: &str, +) -> Result<(), Status> { + let endpoint_info = EndpointInfo { + protocol: digital_twin_protocol::GRPC.to_string(), + operations: vec![digital_twin_operation::MANAGEDSUBSCRIBE.to_string()], + uri: provider_uri.to_string(), + context: "GetSubscriptionInfo".to_string(), + }; + + let entity_access_info = EntityAccessInfo { + name: car_v1::car::is_car_steeringwheel_in_assist_position::NAME.to_string(), + id: car_v1::car::is_car_steeringwheel_in_assist_position::ID.to_string(), + description: car_v1::car::is_car_steeringwheel_in_assist_position::DESCRIPTION.to_string(), + endpoint_info_list: vec![endpoint_info], + }; + + let mut client = InvehicleDigitalTwinClient::connect(invehicle_digital_twin_uri.to_string()) + .await + .map_err(|e| Status::internal(e.to_string()))?; + let request = tonic::Request::new(RegisterRequest { + entity_access_info_list: vec![entity_access_info], + }); + let _response = client.register(request).await?; + + Ok(()) +} + +/// Start the seat adjustment data stream. +fn provider_start_seat_adjustment_data_stream() { + debug!("Starting the Provider's seat adjustment data stream."); + + let (sender, reciever) = watch::channel(driver_seat_back.load(Ordering::Relaxed)); + tokio::spawn(async move { + + loop { + debug!( + "Recording new value for {} of {}", + car_v1::car::is_car_seat_in_assist_position::ID, + driver_seat_back.load(Ordering::Relaxed) + ); + + if let Err(err) = sender.send(driver_seat_back.load(Ordering::Relaxed)) { + warn!("Failed to get new value due to '{err:?}'"); + break; + } + + debug!("Completed the publish request"); + } + }); +} + +/// Start the door adjustment data stream. +fn provider_start_door_adjustment_data_stream() { + debug!("Starting the Provider's door adjustment data stream."); + + let (sender, reciever) = watch::channel(door_open.load(Ordering::Relaxed)); + tokio::spawn(async move { + + loop { + debug!( + "Recording new value for {} of {}", + car_v1::car::is_car_door_open::ID, + door_open.load(Ordering::Relaxed) + ); + + if let Err(err) = sender.send(door_open.load(Ordering::Relaxed)) { + warn!("Failed to get new value due to '{err:?}'"); + break; + } + + debug!("Completed the publish request"); + } + }); +} + +/// Start the steering wheel adjustment data stream. +fn provider_start_steering_wheel_adjustment_data_stream() { + debug!("Starting the Provider's steering wheel adjustment data stream."); + + let (sender, reciever) = watch::channel(steering_wheel_up.load(Ordering::Relaxed)); + tokio::spawn(async move { + + loop { + debug!( + "Recording new value for {} of {}", + car_v1::car::is_car_steeringwheel_in_assist_position::ID, + steering_wheel_up.load(Ordering::Relaxed) + ); + + if let Err(err) = sender.send(steering_wheel_up.load(Ordering::Relaxed)) { + warn!("Failed to get new value due to '{err:?}'"); + break; + } + + debug!("Completed the publish request"); + } + }); +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup logging. + Builder::new() + .filter(None, LevelFilter::Info) + .target(Target::Stdout) + .init(); + + info!("The Wheelchair Assistant Application has started."); + + const PROVIDER_AUTHORITY_SEAT: &str = "0.0.0.0:4070"; + const PROVIDER_AUTHORITY_DOOR: &str = "0.0.0.0:4080"; + const PROVIDER_AUTHORITY_STEER: &str = "0.0.0.0:4090"; + + // Get the In-vehicle Digital Twin Uri from the service discovery system + // This could be enhanced to add retries for robustness + let invehicle_digital_twin_uri = discover_service_using_chariott( + CHARIOTT_SERVICE_DISCOVERY_URI, + INVEHICLE_DIGITAL_TWIN_SERVICE_NAMESPACE, + INVEHICLE_DIGITAL_TWIN_SERVICE_NAME, + INVEHICLE_DIGITAL_TWIN_SERVICE_VERSION, + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_KIND, + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_REFERENCE, + ) + .await?; + + // Get subscription constraints. + let frequency_ms = env::args() + .find_map(|arg| { + if arg.contains(FREQUENCY_MS_FLAG) { + return Some(arg.replace(FREQUENCY_MS_FLAG, "")); + } + + None + }) + .unwrap_or_else(|| DEFAULT_FREQUENCY_MS.to_string()); + + // Retrieve the provider URI. + let mut provider_endpoint_info = None; + let mut retries: i32 = 0; + while provider_endpoint_info.is_none() { + provider_endpoint_info = match discover_digital_twin_provider_using_ibeji( + &invehicle_digital_twin_uri, + car_v1::car::car_wheelchair_assistant_state::ID, + digital_twin_protocol::GRPC, + &[digital_twin_operation::MANAGEDSUBSCRIBE.to_string()], + ) + .await + { + Ok(response) => Some(response), + Err(status) => { + info!( + "A provider was not found in the digital twin service for id '{}' with: '{:?}'", + car_v1::car::car_wheelchair_assistant_state::ID, + status + ); + None + } + }; + + if provider_endpoint_info.is_none() && retries < MAX_RETRIES { + info!("Retrying FindById to retrieve the properties provider endpoint in {DURATION_BETWEEN_ATTEMPTS:?}."); + sleep(DURATION_BETWEEN_ATTEMPTS).await; + retries += 1; + } else { + break; + } + } + + let managed_subscribe_uri = provider_endpoint_info.ok_or("Maximum amount of retries was reached while trying to retrieve the digital twin provider.")?.uri; + info!("The Managed Subscribe URI for the IsCarUnlocked property's provider is {managed_subscribe_uri}"); + + // Create constraint for the managed subscribe call. + let frequency_constraint = Constraint { + r#type: constraint_type::FREQUENCY_MS.to_string(), + value: frequency_ms.to_string(), + }; + + // Get the subscription information for a managed topic with constraints. + let subscription_info = + get_car_adjust_subscription_info(&managed_subscribe_uri, vec![frequency_constraint]) + .await?; + + // Deconstruct subscription information. + let broker_uri = get_uri(&subscription_info.uri)?; + let topic = subscription_info.context; + info!("The broker URI for the car_wheelchair_assistant_state property's provider is {broker_uri}"); + + // Subscribe to topic. + let sub_handle = receive_car_adjust_updates(&broker_uri, &topic) + .await + .map_err(|err| Status::internal(format!("{err:?}")))?; + + + provider_start_seat_adjustment_data_stream(); + signal::ctrl_c().await?; + + let provider_uri_seat = format!("http://{PROVIDER_AUTHORITY_SEAT}"); + provider_register_seat_adjustment(&invehicle_digital_twin_uri, &provider_uri_seat).await?; + debug!("The Provider Seat has registered with Ibeji."); + + provider_start_door_adjustment_data_stream(); + signal::ctrl_c().await?; + + let provider_uri_door = format!("http://{PROVIDER_AUTHORITY_DOOR}"); + provider_register_door_adjustment(&invehicle_digital_twin_uri, &provider_uri_door).await?; + debug!("The Provider Door has registered with Ibeji."); + + provider_start_steering_wheel_adjustment_data_stream(); + signal::ctrl_c().await?; + + let provider_uri_steer = format!("http://{PROVIDER_AUTHORITY_STEER}"); + provider_register_steering_wheel_adjustment(&invehicle_digital_twin_uri, &provider_uri_steer).await?; + debug!("The Provider SteeringWheel has registered with Ibeji."); + + info!("The Consumer has completed. Shutting down..."); + + // Wait for subscriber task to cleanly shutdown. + _ = sub_handle.await; + + Ok(()) +} \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_assistant_application_demo_gui/wheelchair_assistant_demo.py b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_assistant_application_demo_gui/wheelchair_assistant_demo.py new file mode 100644 index 0000000..bfb39b2 --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_assistant_application_demo_gui/wheelchair_assistant_demo.py @@ -0,0 +1,93 @@ +""" +This is a simple GUI that demonstrates the functionality +of the Wheelchair Assistant Application. It simulates the general workflow of the application as follows. + +You start it by pushing START Button. The virtual car is now in a neutral stand. +By pushing the KEY Button you unlock the car and then with a Scale you can simulate +the distance measurement from the car to the handicap driver. +If the distance drops below 2 m the certain car equipment (door, steering wheel, seat) are being adjusted +to facilitate access to the car. +By pushing START THE CAR Button the equipment is being readjusted for the comfort driving. +""" + + +from tkinter import * + + +def switch_key(): + global lock_switch + + # Determine is on or off + if not lock_switch: + my_label.config(text="The Car is unlocked", fg="green") + Key_Button.config(text='KEY') + lock_switch = True + else: + my_label.config(text="The Car is locked", fg="grey") + Key_Button.config(text='KEY') + lock_switch = False + + +def switch_car(): + global lock_switch, car_on, dist_value + if not car_on: + if lock_switch and int(dist_value.get()) < 1: + switch_key() + seat_label.config(text='Seat: readjusted', fg='blue') + door_label.config(text='Door: closed', fg='blue') + steering_wheel_label.config(text='Steering Wheel: readjusted', fg='blue') + Start_Button.config(text='STOP THE CAR') + car_on = True + else: + switch_key() + seat_label.config(text='Seat: adjusted', fg='green') + door_label.config(text='Door: opened', fg='green') + steering_wheel_label.config(text='Steering Wheel: adjusted', fg='green') + Start_Button.config(text='START THE CAR') + car_on = False + + +def switch_distance(val): + global lock_switch + + if lock_switch and int(val) <= 2: + seat_label.config(text='Seat: adjusted', fg='green') + door_label.config(text='Door: opened', fg='green') + steering_wheel_label.config(text='Steering Wheel: adjusted', fg='green') + else: + seat_label.config(text='Seat: neutral', fg='grey') + door_label.config(text='Door: closed', fg='grey') + steering_wheel_label.config(text='Steering Wheel: neutral', fg='grey') + + +lock_switch = True +car_on = False + +top = Tk() +top.geometry("1000x600") +# top.config(background='lightblue') + +my_label = Label(top, fg="grey", font=("Helvetica", 20)) +my_label.place(x=100, y=50) + +seat_label = Label(top,text='Seat', fg='grey', font=('Helvetica', 20)) +seat_label.place(x=600, y=50) + +door_label = Label(top,text='Door', fg='grey', font=('Helvetica', 20)) +door_label.place(x=600, y=100) + +steering_wheel_label = Label(top,text='Steering_Wheel', fg='grey', font=('Helvetica', 20)) +steering_wheel_label.place(x=600, y=150) + +dist_value = IntVar() +distance = Scale(top, orient='horizontal', from_=10, to=0, resolution=2, length=200, + label='Distance to car (m)', command=switch_distance, font=('Helvetica', 15), variable=dist_value) +distance.set(500) +distance.place(x=100, y=200) + +Key_Button = Button(top, text ="START", width=15, height=4, font=("Helvetica", 10), command=switch_key) +Key_Button.place(x=150, y=100) + +Start_Button = Button(top, text ="START THE CAR", width=15, height=4, font=("Helvetica", 10), command=switch_car) +Start_Button.place(x=150, y=400) +top.mainloop() diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/applications/smart_trailer_application/Cargo.toml b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_distance_application/Cargo.toml similarity index 55% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/applications/smart_trailer_application/Cargo.toml rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_distance_application/Cargo.toml index c5c2185..76650bf 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/applications/smart_trailer_application/Cargo.toml +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_distance_application/Cargo.toml @@ -1,16 +1,19 @@ [package] -name = "smart_trailer_application" +name = "wheelchair_distance_application" version = "0.1.0" edition = "2021" license = "MIT" [dependencies] -digital-twin-model = { path = "../../digital-twin-model" } -digital_twin_providers_common = { path = "../../digital_twin_providers/common" } +wheelchair_digital_twin_model = { path = "../../digital-twin-model" } +wheelchair_digital_twin_providers_common = { path = "../../digital_twin_providers/common" } env_logger= { workspace = true } log = { workspace = true } interfaces = { path = "../../../../proto_build"} paho-mqtt = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] } tonic = { workspace = true } -uuid = { workspace = true, features = ["v4", "fast-rng", "macro-diagnostics"] } \ No newline at end of file +uuid = { workspace = true, features = ["v4", "fast-rng", "macro-diagnostics"] } +serde = { workspace = true } +serde_derive = { workspace = true } +serde_json = { workspace = true } \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_distance_application/Dockerfile b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_distance_application/Dockerfile new file mode 100644 index 0000000..262da08 --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_distance_application/Dockerfile @@ -0,0 +1,71 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Dockerfile reference guide at +# https://docs.docker.com/engine/reference/builder/ + +################################################################################ +# Create a stage for building the application. + +ARG RUST_VERSION=1.72.1 +FROM docker.io/library/rust:${RUST_VERSION}-slim-bullseye AS build +ARG APP_NAME=wheelchair_distance_application +WORKDIR /sdv + +COPY ./ . + +# Add Build dependencies. +RUN apt update && apt upgrade -y && apt install -y \ + cmake \ + libssl-dev \ + pkg-config \ + protobuf-compiler + +# Check that APP_NAME argument is valid. +RUN sanitized=$(echo "${APP_NAME}" | tr -dc '^[a-zA-Z_0-9-]+$'); \ +[ "$sanitized" = "${APP_NAME}" ] || { \ + echo "ARG 'APP_NAME' is invalid. APP_NAME='${APP_NAME}' sanitized='${sanitized}'"; \ + exit 1; \ +} + +# Build the application +RUN cargo build --release --bin "${APP_NAME}" + +# Copy the built application to working directory. +RUN cp ./target/release/"${APP_NAME}" /sdv/service + +################################################################################ +# Create a new stage for running the application that contains the minimal +# runtime dependencies for the application. This often uses a different base +# image from the build stage where the necessary files are copied from the build +# stage. +# +# The example below uses the debian bullseye image as the foundation for running the app. +# By specifying the "bullseye-slim" tag, it will also use whatever happens to be the +# most recent version of that tag when you build your Dockerfile. If +# reproducability is important, consider using a digest +# (e.g., debian@sha256:ac707220fbd7b67fc19b112cee8170b41a9e97f703f588b2cdbbcdcecdd8af57). +FROM docker.io/library/debian:bullseye-slim AS final + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser +USER appuser + +WORKDIR /sdv + +# Copy the executable from the "build" stage. +COPY --from=build /sdv/service /sdv/ + +# What the container should run when it is started. +CMD ["/sdv/service"] \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/applications/smart_trailer_application/src/main.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_distance_application/src/main.rs similarity index 60% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/applications/smart_trailer_application/src/main.rs rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_distance_application/src/main.rs index 222ed3b..f256754 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/applications/smart_trailer_application/src/main.rs +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/applications/wheelchair_distance_application/src/main.rs @@ -1,36 +1,43 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) IAV GmbH. // Licensed under the MIT license. // SPDX-License-Identifier: MIT use std::env; +use std::str; -use digital_twin_model::trailer_v1; -use digital_twin_providers_common::constants::chariott::{ +use wheelchair_digital_twin_model::{car_v1, Metadata}; +use wheelchair_digital_twin_providers_common::constants::chariott::{ INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_KIND, INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_REFERENCE, INVEHICLE_DIGITAL_TWIN_SERVICE_NAME, INVEHICLE_DIGITAL_TWIN_SERVICE_NAMESPACE, INVEHICLE_DIGITAL_TWIN_SERVICE_VERSION, }; -use digital_twin_providers_common::constants::{ +use wheelchair_digital_twin_providers_common::constants::{ constraint_type, digital_twin_operation, digital_twin_protocol, }; -use digital_twin_providers_common::utils::{ +use wheelchair_digital_twin_providers_common::utils::{ discover_digital_twin_provider_using_ibeji, discover_service_using_chariott, get_uri, }; + use env_logger::{Builder, Target}; use interfaces::module::managed_subscribe::v1::managed_subscribe_client::ManagedSubscribeClient; use interfaces::module::managed_subscribe::v1::{ Constraint, SubscriptionInfoRequest, SubscriptionInfoResponse, }; -use log::{debug, info, LevelFilter}; +use interfaces::invehicle_digital_twin::v1::invehicle_digital_twin_client::InvehicleDigitalTwinClient; +use interfaces::invehicle_digital_twin::v1::{EndpointInfo, EntityAccessInfo, RegisterRequest}; +use log::{debug, info, warn, LevelFilter}; use paho_mqtt as mqtt; use tokio::signal; use tokio::task::JoinHandle; use tokio::time::{sleep, Duration}; use tonic::{Request, Status}; +use tokio::sync::watch; use uuid::Uuid; +use serde_derive::{Deserialize, Serialize}; +use std::sync::atomic::{AtomicBool, Ordering}; const FREQUENCY_MS_FLAG: &str = "freq_ms="; -const MQTT_CLIENT_ID: &str = "smart-trailer-consumer"; +const MQTT_CLIENT_ID: &str = "wheelchair-distance-consumer"; // TODO: These could be added in configuration const CHARIOTT_SERVICE_DISCOVERY_URI: &str = "http://0.0.0.0:50000"; @@ -42,14 +49,26 @@ const MAX_RETRIES: i32 = 10; // for demo purposes we will retry a maximum of 10 // By default we will wait 5 seconds between retry attempts const DURATION_BETWEEN_ATTEMPTS: Duration = Duration::from_secs(5); -/// Get trailer weight's subscription information from managed subscribe endpoint. + +static STATE_DISTANCE: AtomicBool = AtomicBool::new(false); + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag="type")] +struct WheelchairDistanceProperty { + #[serde(rename = "WheelchairDistance")] + wheelchair_distance: car_v1::car::car_wheelchair_distance::TYPE, + #[serde(rename = "$metadata")] + metadata: Metadata, +} + +/// Get wheelchair distance subscription information from managed subscribe endpoint. /// /// # Arguments /// * `managed_subscribe_uri` - The managed subscribe URI. /// * `constraints` - Constraints for the managed topic. -async fn get_trailer_weight_subscription_info( +async fn get_car_wheelchair_distance_subscription_info( managed_subscribe_uri: &str, - constraints: Vec, + constraints: Vec ) -> Result { // Create gRPC client. let mut client = ManagedSubscribeClient::connect(managed_subscribe_uri.to_string()) @@ -57,7 +76,7 @@ async fn get_trailer_weight_subscription_info( .map_err(|err| Status::from_error(err.into()))?; let request = Request::new(SubscriptionInfoRequest { - entity_id: trailer_v1::trailer::trailer_weight::ID.to_string(), + entity_id: car_v1::car::car_wheelchair_distance::ID.to_string(), constraints, }); @@ -66,12 +85,12 @@ async fn get_trailer_weight_subscription_info( Ok(response.into_inner()) } -/// Receive Trailer Weight updates. +/// Receive wheelchair distance updates. /// /// # Arguments /// * `broker_uri` - The broker URI. /// * `topic` - The topic. -async fn receive_trailer_weight_updates( +async fn receive_car_wheelchair_distance_updates( broker_uri: &str, topic: &str, ) -> Result, String> { @@ -126,7 +145,21 @@ async fn receive_trailer_weight_updates( // Here we log the message received. This could be expanded to parsing the message, // Obtaining the weight and making decisions based on the weight // For example, adjusting body functions or powertrain of the towing vehicle. + + // TODO: move interfaces into global package + let payload_str = msg.payload_str(); + let msg_des: WheelchairDistanceProperty = serde_json::from_str(&payload_str).unwrap(); + let distance = msg_des.wheelchair_distance; + info!("{}", distance); info!("{}", msg); + + if distance <= 200 { + info!("Near!"); + STATE_DISTANCE.store(true, Ordering::Relaxed); + } else { + info!("Far!"); + STATE_DISTANCE.store(false, Ordering::Relaxed); + } } else if !client.is_connected() { if client.reconnect().is_ok() { _subscribe_response = client @@ -150,6 +183,70 @@ async fn receive_trailer_weight_updates( Ok(sub_handle) } +/// Register the wheelchair distance property's endpoint. +/// +/// # Arguments +/// * `invehicle_digital_twin_uri` - The In-Vehicle Digital Twin URI. +/// * `provider_uri` - The provider's URI. +async fn provider_register_wheelchair_distance( + invehicle_digital_twin_uri: &str, + provider_uri: &str, +) -> Result<(), Status> { + let endpoint_info = EndpointInfo { + protocol: digital_twin_protocol::GRPC.to_string(), + operations: vec![digital_twin_operation::MANAGEDSUBSCRIBE.to_string()], + uri: provider_uri.to_string(), + context: "GetSubscriptionInfo".to_string(), + }; + + let entity_access_info = EntityAccessInfo { + name: car_v1::car::car_wheelchair_distance_state::NAME.to_string(), + id: car_v1::car::car_wheelchair_distance_state::ID.to_string(), + description: car_v1::car::car_wheelchair_distance_state::DESCRIPTION.to_string(), + endpoint_info_list: vec![endpoint_info], + }; + + let mut client = InvehicleDigitalTwinClient::connect(invehicle_digital_twin_uri.to_string()) + .await + .map_err(|e| Status::internal(e.to_string()))?; + let request = tonic::Request::new(RegisterRequest { + entity_access_info_list: vec![entity_access_info], + }); + let _response = client.register(request).await?; + + Ok(()) +} + +/// Start the wheelchair distance data stream. +fn provider_start_wheelchair_distance_data_stream() { + debug!("Starting the Provider's wheelchair distance data stream."); + + let (sender, _receiver) = watch::channel(STATE_DISTANCE.load(Ordering::Relaxed)); + tokio::spawn(async move { + + loop { + if STATE_DISTANCE.load(Ordering::Relaxed) == true { + debug!( + "Recording new value for {} of true", + car_v1::car::car_wheelchair_distance_state::ID + ); + } else { + debug!( + "Recording new value for {} of false", + car_v1::car::car_wheelchair_distance_state::ID + ); + } + + if let Err(err) = sender.send(STATE_DISTANCE.load(Ordering::Relaxed)) { + warn!("Failed to get new value due to '{err:?}'"); + break; + } + + debug!("Completed the publish request"); + } + }); +} + #[tokio::main] async fn main() -> Result<(), Box> { // Setup logging. @@ -158,8 +255,10 @@ async fn main() -> Result<(), Box> { .target(Target::Stdout) .init(); - info!("The Smart Trailer Application has started."); + info!("The Wheelchair Distance Application has started."); + const PROVIDER_AUTHORITY: &str = "0.0.0.0:4030"; + let provider_uri = format!("http://{PROVIDER_AUTHORITY}"); // Get the In-vehicle Digital Twin Uri from the service discovery system // This could be enhanced to add retries for robustness let invehicle_digital_twin_uri = discover_service_using_chariott( @@ -189,7 +288,7 @@ async fn main() -> Result<(), Box> { while provider_endpoint_info.is_none() { provider_endpoint_info = match discover_digital_twin_provider_using_ibeji( &invehicle_digital_twin_uri, - trailer_v1::trailer::trailer_weight::ID, + car_v1::car::is_car_unlocked::ID, digital_twin_protocol::GRPC, &[digital_twin_operation::MANAGEDSUBSCRIBE.to_string()], ) @@ -199,7 +298,7 @@ async fn main() -> Result<(), Box> { Err(status) => { info!( "A provider was not found in the digital twin service for id '{}' with: '{:?}'", - trailer_v1::trailer::trailer_weight::ID, + car_v1::car::is_car_unlocked::ID, status ); None @@ -216,7 +315,7 @@ async fn main() -> Result<(), Box> { } let managed_subscribe_uri = provider_endpoint_info.ok_or("Maximum amount of retries was reached while trying to retrieve the digital twin provider.")?.uri; - info!("The Managed Subscribe URI for the TrailerWeight property's provider is {managed_subscribe_uri}"); + info!("The Managed Subscribe URI for the IsCarUnlocked property's provider is {managed_subscribe_uri}"); // Create constraint for the managed subscribe call. let frequency_constraint = Constraint { @@ -226,21 +325,24 @@ async fn main() -> Result<(), Box> { // Get the subscription information for a managed topic with constraints. let subscription_info = - get_trailer_weight_subscription_info(&managed_subscribe_uri, vec![frequency_constraint]) + get_car_wheelchair_distance_subscription_info(&managed_subscribe_uri, vec![frequency_constraint]) .await?; // Deconstruct subscription information. let broker_uri = get_uri(&subscription_info.uri)?; let topic = subscription_info.context; - info!("The broker URI for the TrailerWeight property's provider is {broker_uri}"); + info!("The broker URI for the IsCarUnlocked property's provider is {broker_uri}"); // Subscribe to topic. - let sub_handle = receive_trailer_weight_updates(&broker_uri, &topic) + let sub_handle = receive_car_wheelchair_distance_updates(&broker_uri, &topic) .await .map_err(|err| Status::internal(format!("{err:?}")))?; - + provider_start_wheelchair_distance_data_stream(); signal::ctrl_c().await?; + provider_register_wheelchair_distance(&invehicle_digital_twin_uri, &provider_uri).await?; + debug!("The Provider has registered with Ibeji."); + info!("The Consumer has completed. Shutting down..."); // Wait for subscriber task to cleanly shutdown. diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital-twin-model/Cargo.toml b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital-twin-model/Cargo.toml similarity index 86% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/digital-twin-model/Cargo.toml rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital-twin-model/Cargo.toml index 3f6e8dd..042dbd2 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital-twin-model/Cargo.toml +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital-twin-model/Cargo.toml @@ -1,17 +1,17 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. -# SPDX-License-Identifier: MIT - -[package] -name = "digital-twin-model" -version = "0.1.0" -edition = "2021" -license = "MIT" - -[dependencies] -serde = { workspace = true } -serde_derive = { workspace = true } - -[lib] -path = "src/lib.rs" +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +[package] +name = "wheelchair_digital_twin_model" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[dependencies] +serde = { workspace = true } +serde_derive = { workspace = true } + +[lib] +path = "src/lib.rs" crate-type = ["lib"] \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital-twin-model/dtdl/car.json b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital-twin-model/dtdl/car.json new file mode 100644 index 0000000..4bd68f5 --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital-twin-model/dtdl/car.json @@ -0,0 +1,68 @@ +[ + { + "@context": [ + "dtmi:dtdl:context;3" + ], + "@type": "Interface", + "@id": "dtmi:sdv:Car;1", + "description": "The car used by a wheelchair driver", + "contents": [ + { + "@type": "Property", + "@id": "dtmi:sdv:Car:IsDoorOpen;1", + "name": "IsDoorOpen", + "description": "Is door open?", + "schema": "boolean" + }, + { + "@type": "Property", + "@id": "dtmi:sdv:Car:IsSteeringwheelInAssistPosition;1", + "name": "IsSteeringwheelInAssistPosition", + "description": "Is steering wheel in assist position?", + "schema": "boolean" + }, + { + "@type": "Property", + "@id": "dtmi:sdv:Car:IsCarRunning;1", + "name": "IsCarRunning", + "description": "Is car running?", + "schema": "boolean" + }, + { + "@type": "Property", + "@id": "dtmi:sdv:Car:IsSeatInAssistPosition;1", + "name": "IsSeatInAssistPosition", + "description": "Is seat in assist position?", + "schema": "boolean" + }, + { + "@type": "Property", + "@id": "dtmi:sdv:Car:IsCarUnlocked;1", + "name": "IsCarUnlocked", + "description": "Is car unlocked?", + "schema": "boolean" + }, + { + "@type": "Property", + "@id": "dtmi:sdv:Car:WheelchairDistance;1", + "name": "WheelchairDistance", + "description": "Distance of wheelchair to car in cm", + "schema": "integer" + }, + { + "@type": "Property", + "@id": "dtmi:sdv:Car:WheelchairDistanceState;1", + "name": "WheelchairDistanceState", + "description": "Distance of wheelchair to car near = true and far = false", + "schema": "boolean" + }, + { + "@type": "Property", + "@id": "dtmi:sdv:Car:WheelchairAssistantState;1", + "name": "WheelchairAssistantState", + "description": "Wheelchair assistant state. One of INIT, OPEN, HOLD, DRIVE", + "schema": "integer" + } + ] + } +] \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital-twin-model/src/car_v1.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital-twin-model/src/car_v1.rs new file mode 100644 index 0000000..1911b7a --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital-twin-model/src/car_v1.rs @@ -0,0 +1,73 @@ +// Copyright (c) IAV GmbH. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +// Note: This code was manually written based on the structure of the +// vehicle model in "../dtdl/car.json" +// In the future this code could be generated from a DTDL spec. + +pub mod car { + pub mod is_car_door_open { + pub const ID: &str = "dtmi:sdv:Car:IsDoorOpen;1"; + pub const NAME: &str = "IsDoorOpen"; + pub const DESCRIPTION: &str = "Is door open?"; + + pub type TYPE = bool; + } + + pub mod is_car_steeringwheel_in_assist_position { + pub const ID: &str = "dtmi:sdv:Car:IsSteeringwheelInAssistPosition;1"; + pub const NAME: &str = "IsSteeringwheelInAssistPosition"; + pub const DESCRIPTION: &str = "Is steering wheel in assist position?"; + + pub type TYPE = bool; + } + + pub mod is_car_running { + pub const ID: &str = "dtmi:sdv:Car:IsCarRunning;1"; + pub const NAME: &str = "IsCarRunning"; + pub const DESCRIPTION: &str = "Is car running?"; + + pub type TYPE = bool; + } + + pub mod is_car_seat_in_assist_position { + pub const ID: &str = "dtmi:sdv:Car:IsSeatInAssistPosition;1"; + pub const NAME: &str = "IsSeatInAssistPosition"; + pub const DESCRIPTION: &str = "Is seat in assist position?"; + + pub type TYPE = bool; + } + + pub mod is_car_unlocked { + pub const ID: &str = "dtmi:sdv:Car:IsCarUnlocked;1"; + pub const NAME: &str = "IsCarUnlocked"; + pub const DESCRIPTION: &str = "Is car unlocked?"; + + pub type TYPE = bool; + } + + pub mod car_wheelchair_distance { + pub const ID: &str = "dtmi:sdv:Car:WheelchairDistance;1"; + pub const NAME: &str = "WheelchairDistance"; + pub const DESCRIPTION: &str = "Distance of wheelchair to car in metres"; + + pub type TYPE = i32; + } + + pub mod car_wheelchair_distance_state { + pub const ID: &str = "dtmi:sdv:Car:WheelchairDistanceState;1"; + pub const NAME: &str = "WheelchairDistanceState"; + pub const DESCRIPTION: &str = "Distance of wheelchair to car near = true and far = false"; + + pub type TYPE = bool; + } + + pub mod car_wheelchair_assistant_state { + pub const ID: &str = "dtmi:sdv:Car:WheelchairAssistantState;1"; + pub const NAME: &str = "WheelchairAssistantState"; + pub const DESCRIPTION: &str = "Wheelchair assistant state. One of INIT=1, OPEN=2, HOLD=3, DRIVE=4"; + + pub type TYPE = i32; + } +} diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital-twin-model/src/lib.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital-twin-model/src/lib.rs similarity index 79% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/digital-twin-model/src/lib.rs rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital-twin-model/src/lib.rs index d09afad..14151a8 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital-twin-model/src/lib.rs +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital-twin-model/src/lib.rs @@ -1,8 +1,8 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) IAV GmbH. // Licensed under the MIT license. // SPDX-License-Identifier: MIT -pub mod trailer_v1; +pub mod car_v1; use serde_derive::{Deserialize, Serialize}; diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_off_provider/Cargo.toml b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_off_provider/Cargo.toml new file mode 100644 index 0000000..52a8a2b --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_off_provider/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "car_off_provider" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[dependencies] +wheelchair_digital_twin_model= { path = "../../digital-twin-model" } +wheelchair_digital_twin_providers_common = { path = "../common" } +env_logger = { workspace = true } +interfaces = { path = "../../../../proto_build"} +log = { workspace = true } +serde = { workspace = true } +serde_derive = { workspace = true } +serde_json = { workspace = true } +wheelchair_assistant_interfaces = { path = "../../proto_build" } +tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] } +tonic = { workspace = true } diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/Dockerfile b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_off_provider/Dockerfile similarity index 95% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/Dockerfile rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_off_provider/Dockerfile index d553ece..c819302 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/Dockerfile +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_off_provider/Dockerfile @@ -1,74 +1,74 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. -# SPDX-License-Identifier: MIT - -# Comments are provided throughout this file to help you get started. -# If you need more help, visit the Dockerfile reference guide at -# https://docs.docker.com/engine/reference/builder/ - -################################################################################ -# Create a stage for building the application. - -ARG RUST_VERSION=1.72.1 -FROM docker.io/library/rust:${RUST_VERSION}-slim-bullseye AS build -ARG APP_NAME=trailer_connected_provider -WORKDIR /sdv - -COPY ./ . - -# Add Build dependencies. -RUN apt update -y && apt upgrade -y && apt install -y \ - cmake \ - libssl-dev \ - pkg-config \ - protobuf-compiler - -# Check that APP_NAME argument is valid. -RUN sanitized=$(echo "${APP_NAME}" | tr -dc '^[a-zA-Z_0-9-]+$'); \ -[ "$sanitized" = "${APP_NAME}" ] || { \ - echo "ARG 'APP_NAME' is invalid. APP_NAME='${APP_NAME}' sanitized='${sanitized}'"; \ - exit 1; \ -} - -# Build the application -RUN cargo build --release --bin "${APP_NAME}" - -# Copy the built application to working directory. -RUN cp ./target/release/"${APP_NAME}" /sdv/service - -################################################################################ -# Create a new stage for running the application that contains the minimal -# runtime dependencies for the application. This often uses a different base -# image from the build stage where the necessary files are copied from the build -# stage. -# -# The example below uses the debian bullseye image as the foundation for running the app. -# By specifying the "bullseye-slim" tag, it will also use whatever happens to be the -# most recent version of that tag when you build your Dockerfile. If -# reproducability is important, consider using a digest -# (e.g., debian@sha256:ac707220fbd7b67fc19b112cee8170b41a9e97f703f588b2cdbbcdcecdd8af57). -FROM docker.io/library/debian:bullseye-slim AS final - -# Create a non-privileged user that the app will run under. -# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user -ARG UID=10001 -RUN adduser \ - --disabled-password \ - --gecos "" \ - --home "/nonexistent" \ - --shell "/sbin/nologin" \ - --no-create-home \ - --uid "${UID}" \ - appuser -USER appuser - -WORKDIR /sdv - -# Copy the executable from the "build" stage. -COPY --from=build /sdv/service /sdv/ - -# Expose the port that the application listens on. -EXPOSE 4020 - -# What the container should run when it is started. +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Dockerfile reference guide at +# https://docs.docker.com/engine/reference/builder/ + +################################################################################ +# Create a stage for building the application. + +ARG RUST_VERSION=1.72.1 +FROM docker.io/library/rust:${RUST_VERSION}-slim-bullseye AS build +ARG APP_NAME=car_off_provider +WORKDIR /sdv + +COPY ./ . + +# Add Build dependencies. +RUN apt update -y && apt upgrade -y && apt install -y \ + cmake \ + libssl-dev \ + pkg-config \ + protobuf-compiler + +# Check that APP_NAME argument is valid. +RUN sanitized=$(echo "${APP_NAME}" | tr -dc '^[a-zA-Z_0-9-]+$'); \ +[ "$sanitized" = "${APP_NAME}" ] || { \ + echo "ARG 'APP_NAME' is invalid. APP_NAME='${APP_NAME}' sanitized='${sanitized}'"; \ + exit 1; \ +} + +# Build the application +RUN cargo build --release --bin "${APP_NAME}" + +# Copy the built application to working directory. +RUN cp ./target/release/"${APP_NAME}" /sdv/service + +################################################################################ +# Create a new stage for running the application that contains the minimal +# runtime dependencies for the application. This often uses a different base +# image from the build stage where the necessary files are copied from the build +# stage. +# +# The example below uses the debian bullseye image as the foundation for running the app. +# By specifying the "bullseye-slim" tag, it will also use whatever happens to be the +# most recent version of that tag when you build your Dockerfile. If +# reproducability is important, consider using a digest +# (e.g., debian@sha256:ac707220fbd7b67fc19b112cee8170b41a9e97f703f588b2cdbbcdcecdd8af57). +FROM docker.io/library/debian:bullseye-slim AS final + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser +USER appuser + +WORKDIR /sdv + +# Copy the executable from the "build" stage. +COPY --from=build /sdv/service /sdv/ + +# Expose the port that the application listens on. +EXPOSE 4020 + +# What the container should run when it is started. CMD ["/sdv/service"] \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_off_provider/src/car_off_provider_impl.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_off_provider/src/car_off_provider_impl.rs new file mode 100644 index 0000000..b8adf6f --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_off_provider/src/car_off_provider_impl.rs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +//! Module containing gRPC service implementation based on [`interfaces::digital_twin_get_provider.proto`]. +//! +//! Provides a gRPC endpoint for getting if the cars ignition is off +use wheelchair_assistant_interfaces::digital_twin_get_provider::v1::digital_twin_get_provider_server::DigitalTwinGetProvider; +use wheelchair_assistant_interfaces::digital_twin_get_provider::v1::{GetRequest, GetResponse}; +use tonic::{Request, Response, Status}; + +/// Base structure for the Car ignition off Provider gRPC service. +#[derive(Default)] +pub struct CarOffProviderImpl {} + +#[tonic::async_trait] +impl DigitalTwinGetProvider for CarOffProviderImpl { + /// This function returns the value of "is_car_off" property + async fn get(&self, _request: Request) -> Result, Status> { + // For now, we assume that if this provider is active, if the cars ignition is off + let get_response = GetResponse { + property_value: false, + }; + Ok(Response::new(get_response)) + } +} diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_off_provider/src/main.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_off_provider/src/main.rs new file mode 100644 index 0000000..323d253 --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_off_provider/src/main.rs @@ -0,0 +1,110 @@ +// Copyright (c) IAV GmbH. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +use wheelchair_digital_twin_model::car_v1; + +use wheelchair_digital_twin_providers_common::constants::chariott::{ + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_KIND, + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_REFERENCE, INVEHICLE_DIGITAL_TWIN_SERVICE_NAME, + INVEHICLE_DIGITAL_TWIN_SERVICE_NAMESPACE, INVEHICLE_DIGITAL_TWIN_SERVICE_VERSION, +}; +use wheelchair_digital_twin_providers_common::constants::{digital_twin_operation, digital_twin_protocol}; +use wheelchair_digital_twin_providers_common::utils::discover_service_using_chariott; +use env_logger::{Builder, Target}; +use interfaces::invehicle_digital_twin::v1::invehicle_digital_twin_client::InvehicleDigitalTwinClient; +use interfaces::invehicle_digital_twin::v1::{EndpointInfo, EntityAccessInfo, RegisterRequest}; +use log::{debug, info, LevelFilter}; +use wheelchair_assistant_interfaces::digital_twin_get_provider::v1::digital_twin_get_provider_server::DigitalTwinGetProviderServer; +use std::net::SocketAddr; +use tokio::signal; +use tonic::transport::Server; +use tonic::Status; +use car_off_provider_impl::CarOffProviderImpl; + +mod car_off_provider_impl; + +// TODO: These could be added in configuration +const CHARIOTT_SERVICE_DISCOVERY_URI: &str = "http://0.0.0.0:50000"; +const PROVIDER_AUTHORITY: &str = "0.0.0.0:4040"; + +/// Register the "is_car_off" property's endpoint. +/// +/// # Arguments +/// * `invehicle_digital_twin_uri` - The In-Vehicle Digital Twin URI. +/// * `provider_uri` - The provider's URI. +async fn register_entity( + invehicle_digital_twin_uri: &str, + provider_uri: &str, +) -> Result<(), Status> { + let is_car_off_endpoint_info = EndpointInfo { + protocol: digital_twin_protocol::GRPC.to_string(), + operations: vec![digital_twin_operation::GET.to_string()], + uri: provider_uri.to_string(), + context: car_v1::car::is_car_running::ID.to_string(), + }; + let entity_access_info = EntityAccessInfo { + name: car_v1::car::is_car_running::NAME.to_string(), + id: car_v1::car::is_car_running::ID.to_string(), + description: car_v1::car::is_car_running::DESCRIPTION.to_string(), + endpoint_info_list: vec![is_car_off_endpoint_info], + }; + + let mut client = InvehicleDigitalTwinClient::connect(invehicle_digital_twin_uri.to_string()) + .await + .map_err(|e| Status::internal(e.to_string()))?; + let request = tonic::Request::new(RegisterRequest { + entity_access_info_list: vec![entity_access_info], + }); + let _response = client.register(request).await?; + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Set up logging. + Builder::new() + .filter(None, LevelFilter::Debug) + .target(Target::Stdout) + .init(); + + info!("The Provider CarOff has started."); + + let provider_uri = format!("http://{PROVIDER_AUTHORITY}"); + debug!("The Provider URI is {}", &provider_uri); + + // Setup the HTTP server. + let addr: SocketAddr = PROVIDER_AUTHORITY.parse()?; + let provider_impl = CarOffProviderImpl::default(); + let server_future = Server::builder() + .add_service(DigitalTwinGetProviderServer::new(provider_impl)) + .serve(addr); + info!("The HTTP server is listening on address '{PROVIDER_AUTHORITY}'"); + + // Get the In-vehicle Digital Twin Uri from the service discovery system + // This could be enhanced to add retries for robustness + let invehicle_digital_twin_uri = discover_service_using_chariott( + CHARIOTT_SERVICE_DISCOVERY_URI, + INVEHICLE_DIGITAL_TWIN_SERVICE_NAMESPACE, + INVEHICLE_DIGITAL_TWIN_SERVICE_NAME, + INVEHICLE_DIGITAL_TWIN_SERVICE_VERSION, + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_KIND, + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_REFERENCE, + ) + .await?; + + debug!("Sending a register request to the In-Vehicle Digital Twin Service URI {invehicle_digital_twin_uri}"); + + // This could be enhanced to add retries for robustness + register_entity(&invehicle_digital_twin_uri, &provider_uri).await?; + server_future.await?; + + signal::ctrl_c() + .await + .expect("Failed to listen for control-c event"); + + info!("The Provider has completed."); + + Ok(()) +} diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/Cargo.toml b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_on_provider/Cargo.toml similarity index 63% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/Cargo.toml rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_on_provider/Cargo.toml index ed42315..b648457 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/Cargo.toml +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_on_provider/Cargo.toml @@ -1,18 +1,18 @@ [package] -name = "trailer_connected_provider" +name = "car_on_provider" version = "0.1.0" edition = "2021" license = "MIT" [dependencies] -digital-twin-model = { path = "../../digital-twin-model" } -digital_twin_providers_common = { path = "../common" } +wheelchair_digital_twin_model= { path = "../../digital-twin-model" } +wheelchair_digital_twin_providers_common = { path = "../common" } env_logger = { workspace = true } interfaces = { path = "../../../../proto_build"} log = { workspace = true } serde = { workspace = true } serde_derive = { workspace = true } serde_json = { workspace = true } -smart_trailer_interfaces = { path = "../../proto_build" } +wheelchair_assistant_interfaces = { path = "../../proto_build" } tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] } tonic = { workspace = true } diff --git a/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/Dockerfile b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_on_provider/Dockerfile similarity index 95% rename from scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/Dockerfile rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_on_provider/Dockerfile index d553ece..bacef1f 100644 --- a/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/Dockerfile +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_on_provider/Dockerfile @@ -1,74 +1,74 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. -# SPDX-License-Identifier: MIT - -# Comments are provided throughout this file to help you get started. -# If you need more help, visit the Dockerfile reference guide at -# https://docs.docker.com/engine/reference/builder/ - -################################################################################ -# Create a stage for building the application. - -ARG RUST_VERSION=1.72.1 -FROM docker.io/library/rust:${RUST_VERSION}-slim-bullseye AS build -ARG APP_NAME=trailer_connected_provider -WORKDIR /sdv - -COPY ./ . - -# Add Build dependencies. -RUN apt update -y && apt upgrade -y && apt install -y \ - cmake \ - libssl-dev \ - pkg-config \ - protobuf-compiler - -# Check that APP_NAME argument is valid. -RUN sanitized=$(echo "${APP_NAME}" | tr -dc '^[a-zA-Z_0-9-]+$'); \ -[ "$sanitized" = "${APP_NAME}" ] || { \ - echo "ARG 'APP_NAME' is invalid. APP_NAME='${APP_NAME}' sanitized='${sanitized}'"; \ - exit 1; \ -} - -# Build the application -RUN cargo build --release --bin "${APP_NAME}" - -# Copy the built application to working directory. -RUN cp ./target/release/"${APP_NAME}" /sdv/service - -################################################################################ -# Create a new stage for running the application that contains the minimal -# runtime dependencies for the application. This often uses a different base -# image from the build stage where the necessary files are copied from the build -# stage. -# -# The example below uses the debian bullseye image as the foundation for running the app. -# By specifying the "bullseye-slim" tag, it will also use whatever happens to be the -# most recent version of that tag when you build your Dockerfile. If -# reproducability is important, consider using a digest -# (e.g., debian@sha256:ac707220fbd7b67fc19b112cee8170b41a9e97f703f588b2cdbbcdcecdd8af57). -FROM docker.io/library/debian:bullseye-slim AS final - -# Create a non-privileged user that the app will run under. -# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user -ARG UID=10001 -RUN adduser \ - --disabled-password \ - --gecos "" \ - --home "/nonexistent" \ - --shell "/sbin/nologin" \ - --no-create-home \ - --uid "${UID}" \ - appuser -USER appuser - -WORKDIR /sdv - -# Copy the executable from the "build" stage. -COPY --from=build /sdv/service /sdv/ - -# Expose the port that the application listens on. -EXPOSE 4020 - -# What the container should run when it is started. +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Dockerfile reference guide at +# https://docs.docker.com/engine/reference/builder/ + +################################################################################ +# Create a stage for building the application. + +ARG RUST_VERSION=1.72.1 +FROM docker.io/library/rust:${RUST_VERSION}-slim-bullseye AS build +ARG APP_NAME=car_on_provider +WORKDIR /sdv + +COPY ./ . + +# Add Build dependencies. +RUN apt update -y && apt upgrade -y && apt install -y \ + cmake \ + libssl-dev \ + pkg-config \ + protobuf-compiler + +# Check that APP_NAME argument is valid. +RUN sanitized=$(echo "${APP_NAME}" | tr -dc '^[a-zA-Z_0-9-]+$'); \ +[ "$sanitized" = "${APP_NAME}" ] || { \ + echo "ARG 'APP_NAME' is invalid. APP_NAME='${APP_NAME}' sanitized='${sanitized}'"; \ + exit 1; \ +} + +# Build the application +RUN cargo build --release --bin "${APP_NAME}" + +# Copy the built application to working directory. +RUN cp ./target/release/"${APP_NAME}" /sdv/service + +################################################################################ +# Create a new stage for running the application that contains the minimal +# runtime dependencies for the application. This often uses a different base +# image from the build stage where the necessary files are copied from the build +# stage. +# +# The example below uses the debian bullseye image as the foundation for running the app. +# By specifying the "bullseye-slim" tag, it will also use whatever happens to be the +# most recent version of that tag when you build your Dockerfile. If +# reproducability is important, consider using a digest +# (e.g., debian@sha256:ac707220fbd7b67fc19b112cee8170b41a9e97f703f588b2cdbbcdcecdd8af57). +FROM docker.io/library/debian:bullseye-slim AS final + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser +USER appuser + +WORKDIR /sdv + +# Copy the executable from the "build" stage. +COPY --from=build /sdv/service /sdv/ + +# Expose the port that the application listens on. +EXPOSE 4020 + +# What the container should run when it is started. CMD ["/sdv/service"] \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_on_provider/src/car_on_provider_impl.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_on_provider/src/car_on_provider_impl.rs new file mode 100644 index 0000000..3fb32e0 --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_on_provider/src/car_on_provider_impl.rs @@ -0,0 +1,26 @@ +// Copyright (c) IAV GmbH. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +//! Module containing gRPC service implementation based on [`interfaces::digital_twin_get_provider.proto`]. +//! +//! Provides a gRPC endpoint for getting if the cars ignition is on +use wheelchair_assistant_interfaces::digital_twin_get_provider::v1::digital_twin_get_provider_server::DigitalTwinGetProvider; +use wheelchair_assistant_interfaces::digital_twin_get_provider::v1::{GetRequest, GetResponse}; +use tonic::{Request, Response, Status}; + +/// Base structure for the Car ignition on Provider gRPC service. +#[derive(Default)] +pub struct CarOnProviderImpl {} + +#[tonic::async_trait] +impl DigitalTwinGetProvider for CarOnProviderImpl { + /// This function returns the value of "is_car_on" property + async fn get(&self, _request: Request) -> Result, Status> { + // For now, we assume that if this provider is active, if the cars ignition is on + let get_response = GetResponse { + property_value: true, + }; + Ok(Response::new(get_response)) + } +} diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_on_provider/src/main.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_on_provider/src/main.rs new file mode 100644 index 0000000..8c29fbb --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/car_on_provider/src/main.rs @@ -0,0 +1,110 @@ +// Copyright (c) IAV GmbH. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +use wheelchair_digital_twin_model::car_v1; + +use wheelchair_digital_twin_providers_common::constants::chariott::{ + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_KIND, + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_REFERENCE, INVEHICLE_DIGITAL_TWIN_SERVICE_NAME, + INVEHICLE_DIGITAL_TWIN_SERVICE_NAMESPACE, INVEHICLE_DIGITAL_TWIN_SERVICE_VERSION, +}; +use wheelchair_digital_twin_providers_common::constants::{digital_twin_operation, digital_twin_protocol}; +use wheelchair_digital_twin_providers_common::utils::discover_service_using_chariott; +use env_logger::{Builder, Target}; +use interfaces::invehicle_digital_twin::v1::invehicle_digital_twin_client::InvehicleDigitalTwinClient; +use interfaces::invehicle_digital_twin::v1::{EndpointInfo, EntityAccessInfo, RegisterRequest}; +use log::{debug, info, LevelFilter}; +use wheelchair_assistant_interfaces::digital_twin_get_provider::v1::digital_twin_get_provider_server::DigitalTwinGetProviderServer; +use std::net::SocketAddr; +use tokio::signal; +use tonic::transport::Server; +use tonic::Status; +use car_on_provider_impl::CarOnProviderImpl; + +mod car_on_provider_impl; + +// TODO: These could be added in configuration +const CHARIOTT_SERVICE_DISCOVERY_URI: &str = "http://0.0.0.0:50000"; +const PROVIDER_AUTHORITY: &str = "0.0.0.0:4030"; + +/// Register the "is_car_on" property's endpoint. +/// +/// # Arguments +/// * `invehicle_digital_twin_uri` - The In-Vehicle Digital Twin URI. +/// * `provider_uri` - The provider's URI. +async fn register_entity( + invehicle_digital_twin_uri: &str, + provider_uri: &str, +) -> Result<(), Status> { + let is_car_on_endpoint_info = EndpointInfo { + protocol: digital_twin_protocol::GRPC.to_string(), + operations: vec![digital_twin_operation::GET.to_string()], + uri: provider_uri.to_string(), + context: car_v1::car::is_car_running::ID.to_string(), + }; + let entity_access_info = EntityAccessInfo { + name: car_v1::car::is_car_running::NAME.to_string(), + id: car_v1::car::is_car_running::ID.to_string(), + description: car_v1::car::is_car_running::DESCRIPTION.to_string(), + endpoint_info_list: vec![is_car_on_endpoint_info], + }; + + let mut client = InvehicleDigitalTwinClient::connect(invehicle_digital_twin_uri.to_string()) + .await + .map_err(|e| Status::internal(e.to_string()))?; + let request = tonic::Request::new(RegisterRequest { + entity_access_info_list: vec![entity_access_info], + }); + let _response = client.register(request).await?; + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Set up logging. + Builder::new() + .filter(None, LevelFilter::Debug) + .target(Target::Stdout) + .init(); + + info!("The Provider CarOn has started."); + + let provider_uri = format!("http://{PROVIDER_AUTHORITY}"); + debug!("The Provider URI is {}", &provider_uri); + + // Setup the HTTP server. + let addr: SocketAddr = PROVIDER_AUTHORITY.parse()?; + let provider_impl = CarOnProviderImpl::default(); + let server_future = Server::builder() + .add_service(DigitalTwinGetProviderServer::new(provider_impl)) + .serve(addr); + info!("The HTTP server is listening on address '{PROVIDER_AUTHORITY}'"); + + // Get the In-vehicle Digital Twin Uri from the service discovery system + // This could be enhanced to add retries for robustness + let invehicle_digital_twin_uri = discover_service_using_chariott( + CHARIOTT_SERVICE_DISCOVERY_URI, + INVEHICLE_DIGITAL_TWIN_SERVICE_NAMESPACE, + INVEHICLE_DIGITAL_TWIN_SERVICE_NAME, + INVEHICLE_DIGITAL_TWIN_SERVICE_VERSION, + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_KIND, + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_REFERENCE, + ) + .await?; + + debug!("Sending a register request to the In-Vehicle Digital Twin Service URI {invehicle_digital_twin_uri}"); + + // This could be enhanced to add retries for robustness + register_entity(&invehicle_digital_twin_uri, &provider_uri).await?; + server_future.await?; + + signal::ctrl_c() + .await + .expect("Failed to listen for control-c event"); + + info!("The Provider has completed."); + + Ok(()) +} diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_lock_provider/Cargo.toml b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_lock_provider/Cargo.toml new file mode 100644 index 0000000..2927a62 --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_lock_provider/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "carkey_lock_provider" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[dependencies] +wheelchair_digital_twin_model= { path = "../../digital-twin-model" } +wheelchair_digital_twin_providers_common = { path = "../common" } +env_logger = { workspace = true } +interfaces = { path = "../../../../proto_build"} +log = { workspace = true } +serde = { workspace = true } +serde_derive = { workspace = true } +serde_json = { workspace = true } +wheelchair_assistant_interfaces = { path = "../../proto_build" } +tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] } +tonic = { workspace = true } diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_lock_provider/Dockerfile b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_lock_provider/Dockerfile new file mode 100644 index 0000000..1cd0a8f --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_lock_provider/Dockerfile @@ -0,0 +1,74 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Dockerfile reference guide at +# https://docs.docker.com/engine/reference/builder/ + +################################################################################ +# Create a stage for building the application. + +ARG RUST_VERSION=1.72.1 +FROM docker.io/library/rust:${RUST_VERSION}-slim-bullseye AS build +ARG APP_NAME=carkey_lock_provider +WORKDIR /sdv + +COPY ./ . + +# Add Build dependencies. +RUN apt update -y && apt upgrade -y && apt install -y \ + cmake \ + libssl-dev \ + pkg-config \ + protobuf-compiler + +# Check that APP_NAME argument is valid. +RUN sanitized=$(echo "${APP_NAME}" | tr -dc '^[a-zA-Z_0-9-]+$'); \ +[ "$sanitized" = "${APP_NAME}" ] || { \ + echo "ARG 'APP_NAME' is invalid. APP_NAME='${APP_NAME}' sanitized='${sanitized}'"; \ + exit 1; \ +} + +# Build the application +RUN cargo build --release --bin "${APP_NAME}" + +# Copy the built application to working directory. +RUN cp ./target/release/"${APP_NAME}" /sdv/service + +################################################################################ +# Create a new stage for running the application that contains the minimal +# runtime dependencies for the application. This often uses a different base +# image from the build stage where the necessary files are copied from the build +# stage. +# +# The example below uses the debian bullseye image as the foundation for running the app. +# By specifying the "bullseye-slim" tag, it will also use whatever happens to be the +# most recent version of that tag when you build your Dockerfile. If +# reproducability is important, consider using a digest +# (e.g., debian@sha256:ac707220fbd7b67fc19b112cee8170b41a9e97f703f588b2cdbbcdcecdd8af57). +FROM docker.io/library/debian:bullseye-slim AS final + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser +USER appuser + +WORKDIR /sdv + +# Copy the executable from the "build" stage. +COPY --from=build /sdv/service /sdv/ + +# Expose the port that the application listens on. +EXPOSE 4020 + +# What the container should run when it is started. +CMD ["/sdv/service"] \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_lock_provider/src/carkey_lock_provider_impl.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_lock_provider/src/carkey_lock_provider_impl.rs new file mode 100644 index 0000000..6515942 --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_lock_provider/src/carkey_lock_provider_impl.rs @@ -0,0 +1,27 @@ +// Copyright (c) IAV GmbH. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +//! Module containing gRPC service implementation based on [`interfaces::digital_twin_get_provider.proto`]. +//! +//! Provides a gRPC endpoint for getting if the car key is locked +use wheelchair_assistant_interfaces::digital_twin_get_provider::v1::digital_twin_get_provider_server::DigitalTwinGetProvider; +use wheelchair_assistant_interfaces::digital_twin_get_provider::v1::{GetRequest, GetResponse}; +use tonic::{Request, Response, Status}; + +/// Base structure for the Carkey lock Provider gRPC service. +#[derive(Default)] +pub struct CarkeyLockProviderImpl {} + +#[tonic::async_trait] +impl DigitalTwinGetProvider for CarkeyLockProviderImpl { + /// This function returns the value of "is_car_locked" property + async fn get(&self, _request: Request) -> Result, Status> { + // For now, we assume that if this provider is active, the car is locked + + let get_response = GetResponse { + property_value: false, + }; + Ok(Response::new(get_response)) + } +} diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/src/main.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_lock_provider/src/main.rs similarity index 74% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/src/main.rs rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_lock_provider/src/main.rs index 219f2d2..fa22b36 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_connected_provider/src/main.rs +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_lock_provider/src/main.rs @@ -1,34 +1,34 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) IAV GmbH. // Licensed under the MIT license. // SPDX-License-Identifier: MIT -use digital_twin_model::trailer_v1; +use wheelchair_digital_twin_model::car_v1; -use digital_twin_providers_common::constants::chariott::{ +use wheelchair_digital_twin_providers_common::constants::chariott::{ INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_KIND, INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_REFERENCE, INVEHICLE_DIGITAL_TWIN_SERVICE_NAME, INVEHICLE_DIGITAL_TWIN_SERVICE_NAMESPACE, INVEHICLE_DIGITAL_TWIN_SERVICE_VERSION, }; -use digital_twin_providers_common::constants::{digital_twin_operation, digital_twin_protocol}; -use digital_twin_providers_common::utils::discover_service_using_chariott; +use wheelchair_digital_twin_providers_common::constants::{digital_twin_operation, digital_twin_protocol}; +use wheelchair_digital_twin_providers_common::utils::discover_service_using_chariott; use env_logger::{Builder, Target}; use interfaces::invehicle_digital_twin::v1::invehicle_digital_twin_client::InvehicleDigitalTwinClient; use interfaces::invehicle_digital_twin::v1::{EndpointInfo, EntityAccessInfo, RegisterRequest}; use log::{debug, info, LevelFilter}; -use smart_trailer_interfaces::digital_twin_get_provider::v1::digital_twin_get_provider_server::DigitalTwinGetProviderServer; +use wheelchair_assistant_interfaces::digital_twin_get_provider::v1::digital_twin_get_provider_server::DigitalTwinGetProviderServer; use std::net::SocketAddr; use tokio::signal; use tonic::transport::Server; use tonic::Status; -use trailer_connected_provider_impl::TrailerConnectedProviderImpl; +use carkey_lock_provider_impl::CarkeyLockProviderImpl; -mod trailer_connected_provider_impl; +mod carkey_lock_provider_impl; // TODO: These could be added in configuration const CHARIOTT_SERVICE_DISCOVERY_URI: &str = "http://0.0.0.0:50000"; const PROVIDER_AUTHORITY: &str = "0.0.0.0:4020"; -/// Register the "is trailer connected" property's endpoint. +/// Register the "is_car_locked" property's endpoint. /// /// # Arguments /// * `invehicle_digital_twin_uri` - The In-Vehicle Digital Twin URI. @@ -37,17 +37,17 @@ async fn register_entity( invehicle_digital_twin_uri: &str, provider_uri: &str, ) -> Result<(), Status> { - let is_trailer_connected_endpoint_info = EndpointInfo { + let is_car_locked_endpoint_info = EndpointInfo { protocol: digital_twin_protocol::GRPC.to_string(), operations: vec![digital_twin_operation::GET.to_string()], uri: provider_uri.to_string(), - context: trailer_v1::trailer::is_trailer_connected::ID.to_string(), + context: car_v1::car::is_car_unlocked::ID.to_string(), }; let entity_access_info = EntityAccessInfo { - name: trailer_v1::trailer::is_trailer_connected::NAME.to_string(), - id: trailer_v1::trailer::is_trailer_connected::ID.to_string(), - description: trailer_v1::trailer::is_trailer_connected::DESCRIPTION.to_string(), - endpoint_info_list: vec![is_trailer_connected_endpoint_info], + name: car_v1::car::is_car_unlocked::NAME.to_string(), + id: car_v1::car::is_car_unlocked::ID.to_string(), + description: car_v1::car::is_car_unlocked::DESCRIPTION.to_string(), + endpoint_info_list: vec![is_car_locked_endpoint_info], }; let mut client = InvehicleDigitalTwinClient::connect(invehicle_digital_twin_uri.to_string()) @@ -69,14 +69,14 @@ async fn main() -> Result<(), Box> { .target(Target::Stdout) .init(); - info!("The Provider has started."); + info!("The Provider CarKeyLock has started."); let provider_uri = format!("http://{PROVIDER_AUTHORITY}"); debug!("The Provider URI is {}", &provider_uri); // Setup the HTTP server. let addr: SocketAddr = PROVIDER_AUTHORITY.parse()?; - let provider_impl = TrailerConnectedProviderImpl::default(); + let provider_impl = CarkeyLockProviderImpl::default(); let server_future = Server::builder() .add_service(DigitalTwinGetProviderServer::new(provider_impl)) .serve(addr); diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_unlock_provider/Cargo.toml b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_unlock_provider/Cargo.toml new file mode 100644 index 0000000..ef7ad1c --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_unlock_provider/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "carkey_unlock_provider" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[dependencies] +wheelchair_digital_twin_model= { path = "../../digital-twin-model" } +wheelchair_digital_twin_providers_common = { path = "../common" } +env_logger = { workspace = true } +interfaces = { path = "../../../../proto_build"} +log = { workspace = true } +serde = { workspace = true } +serde_derive = { workspace = true } +serde_json = { workspace = true } +wheelchair_assistant_interfaces = { path = "../../proto_build" } +tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] } +tonic = { workspace = true } diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_unlock_provider/Dockerfile b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_unlock_provider/Dockerfile new file mode 100644 index 0000000..9183d04 --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_unlock_provider/Dockerfile @@ -0,0 +1,74 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Dockerfile reference guide at +# https://docs.docker.com/engine/reference/builder/ + +################################################################################ +# Create a stage for building the application. + +ARG RUST_VERSION=1.72.1 +FROM docker.io/library/rust:${RUST_VERSION}-slim-bullseye AS build +ARG APP_NAME=carkey_unlock_provider +WORKDIR /sdv + +COPY ./ . + +# Add Build dependencies. +RUN apt update -y && apt upgrade -y && apt install -y \ + cmake \ + libssl-dev \ + pkg-config \ + protobuf-compiler + +# Check that APP_NAME argument is valid. +RUN sanitized=$(echo "${APP_NAME}" | tr -dc '^[a-zA-Z_0-9-]+$'); \ +[ "$sanitized" = "${APP_NAME}" ] || { \ + echo "ARG 'APP_NAME' is invalid. APP_NAME='${APP_NAME}' sanitized='${sanitized}'"; \ + exit 1; \ +} + +# Build the application +RUN cargo build --release --bin "${APP_NAME}" + +# Copy the built application to working directory. +RUN cp ./target/release/"${APP_NAME}" /sdv/service + +################################################################################ +# Create a new stage for running the application that contains the minimal +# runtime dependencies for the application. This often uses a different base +# image from the build stage where the necessary files are copied from the build +# stage. +# +# The example below uses the debian bullseye image as the foundation for running the app. +# By specifying the "bullseye-slim" tag, it will also use whatever happens to be the +# most recent version of that tag when you build your Dockerfile. If +# reproducability is important, consider using a digest +# (e.g., debian@sha256:ac707220fbd7b67fc19b112cee8170b41a9e97f703f588b2cdbbcdcecdd8af57). +FROM docker.io/library/debian:bullseye-slim AS final + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser +USER appuser + +WORKDIR /sdv + +# Copy the executable from the "build" stage. +COPY --from=build /sdv/service /sdv/ + +# Expose the port that the application listens on. +EXPOSE 4020 + +# What the container should run when it is started. +CMD ["/sdv/service"] \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_unlock_provider/src/carkey_unlock_provider_impl.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_unlock_provider/src/carkey_unlock_provider_impl.rs new file mode 100644 index 0000000..b21732f --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_unlock_provider/src/carkey_unlock_provider_impl.rs @@ -0,0 +1,26 @@ +// Copyright (c) IAV GmbH. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +//! Module containing gRPC service implementation based on [`interfaces::digital_twin_get_provider.proto`]. +//! +//! Provides a gRPC endpoint for getting if the car key is unlocked +use wheelchair_assistant_interfaces::digital_twin_get_provider::v1::digital_twin_get_provider_server::DigitalTwinGetProvider; +use wheelchair_assistant_interfaces::digital_twin_get_provider::v1::{GetRequest, GetResponse}; +use tonic::{Request, Response, Status}; + +/// Base structure for the Carkey Unlock Provider gRPC service. +#[derive(Default)] +pub struct CarkeyUnlockProviderImpl {} + +#[tonic::async_trait] +impl DigitalTwinGetProvider for CarkeyUnlockProviderImpl { + /// This function returns the value of "is_car_unlocked" property + async fn get(&self, _request: Request) -> Result, Status> { + // For now, we assume that if this provider is active, the car is unlocked + let get_response = GetResponse { + property_value: true, + }; + Ok(Response::new(get_response)) + } +} diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_unlock_provider/src/main.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_unlock_provider/src/main.rs new file mode 100644 index 0000000..806eb2c --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/carkey_unlock_provider/src/main.rs @@ -0,0 +1,110 @@ +// Copyright (c) IAV GmbH. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +use wheelchair_digital_twin_model::car_v1; + +use wheelchair_digital_twin_providers_common::constants::chariott::{ + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_KIND, + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_REFERENCE, INVEHICLE_DIGITAL_TWIN_SERVICE_NAME, + INVEHICLE_DIGITAL_TWIN_SERVICE_NAMESPACE, INVEHICLE_DIGITAL_TWIN_SERVICE_VERSION, +}; +use wheelchair_digital_twin_providers_common::constants::{digital_twin_operation, digital_twin_protocol}; +use wheelchair_digital_twin_providers_common::utils::discover_service_using_chariott; +use env_logger::{Builder, Target}; +use interfaces::invehicle_digital_twin::v1::invehicle_digital_twin_client::InvehicleDigitalTwinClient; +use interfaces::invehicle_digital_twin::v1::{EndpointInfo, EntityAccessInfo, RegisterRequest}; +use log::{debug, info, LevelFilter}; +use wheelchair_assistant_interfaces::digital_twin_get_provider::v1::digital_twin_get_provider_server::DigitalTwinGetProviderServer; +use std::net::SocketAddr; +use tokio::signal; +use tonic::transport::Server; +use tonic::Status; +use carkey_unlock_provider_impl::CarkeyUnlockProviderImpl; + +mod carkey_unlock_provider_impl; + +// TODO: These could be added in configuration +const CHARIOTT_SERVICE_DISCOVERY_URI: &str = "http://0.0.0.0:50000"; +const PROVIDER_AUTHORITY: &str = "0.0.0.0:4010"; + +/// Register the "is_car_unlocked" property's endpoint. +/// +/// # Arguments +/// * `invehicle_digital_twin_uri` - The In-Vehicle Digital Twin URI. +/// * `provider_uri` - The provider's URI. +async fn register_entity( + invehicle_digital_twin_uri: &str, + provider_uri: &str, +) -> Result<(), Status> { + let is_car_unlocked_endpoint_info = EndpointInfo { + protocol: digital_twin_protocol::GRPC.to_string(), + operations: vec![digital_twin_operation::GET.to_string()], + uri: provider_uri.to_string(), + context: car_v1::car::is_car_unlocked::ID.to_string(), + }; + let entity_access_info = EntityAccessInfo { + name: car_v1::car::is_car_unlocked::NAME.to_string(), + id: car_v1::car::is_car_unlocked::ID.to_string(), + description: car_v1::car::is_car_unlocked::DESCRIPTION.to_string(), + endpoint_info_list: vec![is_car_unlocked_endpoint_info], + }; + + let mut client = InvehicleDigitalTwinClient::connect(invehicle_digital_twin_uri.to_string()) + .await + .map_err(|e| Status::internal(e.to_string()))?; + let request = tonic::Request::new(RegisterRequest { + entity_access_info_list: vec![entity_access_info], + }); + let _response = client.register(request).await?; + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Set up logging. + Builder::new() + .filter(None, LevelFilter::Debug) + .target(Target::Stdout) + .init(); + + info!("The Provider CarKeyUnlock has started."); + + let provider_uri = format!("http://{PROVIDER_AUTHORITY}"); + debug!("The Provider URI is {}", &provider_uri); + + // Setup the HTTP server. + let addr: SocketAddr = PROVIDER_AUTHORITY.parse()?; + let provider_impl = CarkeyUnlockProviderImpl::default(); + let server_future = Server::builder() + .add_service(DigitalTwinGetProviderServer::new(provider_impl)) + .serve(addr); + info!("The HTTP server is listening on address '{PROVIDER_AUTHORITY}'"); + + // Get the In-vehicle Digital Twin Uri from the service discovery system + // This could be enhanced to add retries for robustness + let invehicle_digital_twin_uri = discover_service_using_chariott( + CHARIOTT_SERVICE_DISCOVERY_URI, + INVEHICLE_DIGITAL_TWIN_SERVICE_NAMESPACE, + INVEHICLE_DIGITAL_TWIN_SERVICE_NAME, + INVEHICLE_DIGITAL_TWIN_SERVICE_VERSION, + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_KIND, + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_REFERENCE, + ) + .await?; + + debug!("Sending a register request to the In-Vehicle Digital Twin Service URI {invehicle_digital_twin_uri}"); + + // This could be enhanced to add retries for robustness + register_entity(&invehicle_digital_twin_uri, &provider_uri).await?; + server_future.await?; + + signal::ctrl_c() + .await + .expect("Failed to listen for control-c event"); + + info!("The Provider has completed."); + + Ok(()) +} diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/common/Cargo.toml b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/common/Cargo.toml similarity index 80% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/common/Cargo.toml rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/common/Cargo.toml index e9e9012..2365bc0 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/common/Cargo.toml +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/common/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "digital_twin_providers_common" +name = "wheelchair_digital_twin_providers_common" version = "0.1.0" edition = "2021" license = "MIT" diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/common/src/constants.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/common/src/constants.rs similarity index 94% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/common/src/constants.rs rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/common/src/constants.rs index 99c5508..d65dcff 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/common/src/constants.rs +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/common/src/constants.rs @@ -1,33 +1,33 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -/// 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"; - pub const STREAM: &str = "Stream"; - pub const MANAGEDSUBSCRIBE: &str = "ManagedSubscribe"; -} - -// Supported digital twin protocols. -pub mod digital_twin_protocol { - pub const GRPC: &str = "grpc"; - pub const MQTT: &str = "mqtt"; -} - -pub mod chariott { - pub const INVEHICLE_DIGITAL_TWIN_SERVICE_NAMESPACE: &str = "sdv.ibeji"; - pub const INVEHICLE_DIGITAL_TWIN_SERVICE_NAME: &str = "invehicle_digital_twin"; - pub const INVEHICLE_DIGITAL_TWIN_SERVICE_VERSION: &str = "1.0"; - pub const INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_KIND: &str = "grpc+proto"; - pub const INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_REFERENCE: &str = "https://github.com/eclipse-ibeji/ibeji/blob/main/interfaces/digital_twin/v1/digital_twin.proto"; -} - -/// Recognized constraint types for subscribe requests. -pub mod constraint_type { - pub const FREQUENCY_MS: &str = "frequency_ms"; -} +// Copyright (c) IAV GmbH. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +/// 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"; + pub const STREAM: &str = "Stream"; + pub const MANAGEDSUBSCRIBE: &str = "ManagedSubscribe"; +} + +// Supported digital twin protocols. +pub mod digital_twin_protocol { + pub const GRPC: &str = "grpc"; + pub const MQTT: &str = "mqtt"; +} + +pub mod chariott { + pub const INVEHICLE_DIGITAL_TWIN_SERVICE_NAMESPACE: &str = "sdv.ibeji"; + pub const INVEHICLE_DIGITAL_TWIN_SERVICE_NAME: &str = "invehicle_digital_twin"; + pub const INVEHICLE_DIGITAL_TWIN_SERVICE_VERSION: &str = "1.0"; + pub const INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_KIND: &str = "grpc+proto"; + pub const INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_REFERENCE: &str = "https://github.com/eclipse-ibeji/ibeji/blob/main/interfaces/digital_twin/v1/digital_twin.proto"; +} + +/// Recognized constraint types for subscribe requests. +pub mod constraint_type { + pub const FREQUENCY_MS: &str = "frequency_ms"; +} diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/common/src/lib.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/common/src/lib.rs similarity index 68% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/common/src/lib.rs rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/common/src/lib.rs index 00cd99c..8a3df1c 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/common/src/lib.rs +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/common/src/lib.rs @@ -1,6 +1,6 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -pub mod constants; -pub mod utils; +// Copyright (c) IAV GmbH. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +pub mod constants; +pub mod utils; diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/common/src/utils.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/common/src/utils.rs similarity index 99% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/common/src/utils.rs rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/common/src/utils.rs index 04d7e2f..c2b431d 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/common/src/utils.rs +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/common/src/utils.rs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) IAV GmbH. // Licensed under the MIT license. // SPDX-License-Identifier: MIT diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider/Cargo.toml b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider/Cargo.toml similarity index 66% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider/Cargo.toml rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider/Cargo.toml index 90747e7..f509519 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider/Cargo.toml +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider/Cargo.toml @@ -3,14 +3,14 @@ # SPDX-License-Identifier: MIT [package] -name = "trailer_properties_provider" +name = "wheelchair_distance_decreasing_provider" version = "0.1.0" edition = "2021" license = "MIT" [dependencies] -digital-twin-model = { path = "../../digital-twin-model" } -digital_twin_providers_common = { path = "../common" } +wheelchair_digital_twin_model= { path = "../../digital-twin-model" } +wheelchair_digital_twin_providers_common = { path = "../common" } env_logger = { workspace = true } interfaces = { path = "../../../../proto_build"} log = { workspace = true } @@ -19,11 +19,11 @@ parking_lot = { workspace = true } serde = { workspace = true } serde_derive = { workspace = true } serde_json = { workspace = true } -smart_trailer_interfaces = { path = "../../proto_build" } +wheelchair_assistant_interfaces = { path = "../../proto_build" } strum = { workspace = true } strum_macros = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] } tonic = { workspace = true } [features] -containerize = ["digital_twin_providers_common/containerize"] +containerize = ["wheelchair_digital_twin_providers_common/containerize"] diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider/Dockerfile b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider/Dockerfile similarity index 95% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider/Dockerfile rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider/Dockerfile index ea2059c..ee8ebe0 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider/Dockerfile +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider/Dockerfile @@ -1,74 +1,74 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. -# SPDX-License-Identifier: MIT - -# Comments are provided throughout this file to help you get started. -# If you need more help, visit the Dockerfile reference guide at -# https://docs.docker.com/engine/reference/builder/ - -################################################################################ -# Create a stage for building the application. - -ARG RUST_VERSION=1.72.1 -FROM docker.io/library/rust:${RUST_VERSION}-slim-bullseye AS build -ARG APP_NAME=trailer_properties_provider -WORKDIR /sdv - -COPY ./ . - -# Add Build dependencies. -RUN apt update && apt upgrade -y && apt install -y \ - cmake \ - libssl-dev \ - pkg-config \ - protobuf-compiler - -# Check that APP_NAME argument is valid. -RUN sanitized=$(echo "${APP_NAME}" | tr -dc '^[a-zA-Z_0-9-]+$'); \ -[ "$sanitized" = "${APP_NAME}" ] || { \ - echo "ARG 'APP_NAME' is invalid. APP_NAME='${APP_NAME}' sanitized='${sanitized}'"; \ - exit 1; \ -} - -# Build the application -RUN cargo build --release --bin "${APP_NAME}" - -# Copy the built application to working directory. -RUN cp ./target/release/"${APP_NAME}" /sdv/service - -################################################################################ -# Create a new stage for running the application that contains the minimal -# runtime dependencies for the application. This often uses a different base -# image from the build stage where the necessary files are copied from the build -# stage. -# -# The example below uses the debian bullseye image as the foundation for running the app. -# By specifying the "bullseye-slim" tag, it will also use whatever happens to be the -# most recent version of that tag when you build your Dockerfile. If -# reproducability is important, consider using a digest -# (e.g., debian@sha256:ac707220fbd7b67fc19b112cee8170b41a9e97f703f588b2cdbbcdcecdd8af57). -FROM docker.io/library/debian:bullseye-slim AS final - -# Create a non-privileged user that the app will run under. -# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user -ARG UID=10001 -RUN adduser \ - --disabled-password \ - --gecos "" \ - --home "/nonexistent" \ - --shell "/sbin/nologin" \ - --no-create-home \ - --uid "${UID}" \ - appuser -USER appuser - -WORKDIR /sdv - -# Copy the executable from the "build" stage. -COPY --from=build /sdv/service /sdv/ - -# Expose the port that the application listens on. -EXPOSE 4030 - -# What the container should run when it is started. +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Dockerfile reference guide at +# https://docs.docker.com/engine/reference/builder/ + +################################################################################ +# Create a stage for building the application. + +ARG RUST_VERSION=1.72.1 +FROM docker.io/library/rust:${RUST_VERSION}-slim-bullseye AS build +ARG APP_NAME=wheelchair_distance_decreasing_provider +WORKDIR /sdv + +COPY ./ . + +# Add Build dependencies. +RUN apt update && apt upgrade -y && apt install -y \ + cmake \ + libssl-dev \ + pkg-config \ + protobuf-compiler + +# Check that APP_NAME argument is valid. +RUN sanitized=$(echo "${APP_NAME}" | tr -dc '^[a-zA-Z_0-9-]+$'); \ +[ "$sanitized" = "${APP_NAME}" ] || { \ + echo "ARG 'APP_NAME' is invalid. APP_NAME='${APP_NAME}' sanitized='${sanitized}'"; \ + exit 1; \ +} + +# Build the application +RUN cargo build --release --bin "${APP_NAME}" + +# Copy the built application to working directory. +RUN cp ./target/release/"${APP_NAME}" /sdv/service + +################################################################################ +# Create a new stage for running the application that contains the minimal +# runtime dependencies for the application. This often uses a different base +# image from the build stage where the necessary files are copied from the build +# stage. +# +# The example below uses the debian bullseye image as the foundation for running the app. +# By specifying the "bullseye-slim" tag, it will also use whatever happens to be the +# most recent version of that tag when you build your Dockerfile. If +# reproducability is important, consider using a digest +# (e.g., debian@sha256:ac707220fbd7b67fc19b112cee8170b41a9e97f703f588b2cdbbcdcecdd8af57). +FROM docker.io/library/debian:bullseye-slim AS final + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser +USER appuser + +WORKDIR /sdv + +# Copy the executable from the "build" stage. +COPY --from=build /sdv/service /sdv/ + +# Expose the port that the application listens on. +EXPOSE 4030 + +# What the container should run when it is started. CMD ["/sdv/service"] \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider/src/main.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider/src/main.rs similarity index 62% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider/src/main.rs rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider/src/main.rs index f8178b8..301c668 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider/src/main.rs +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider/src/main.rs @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) IAV GmbH. // Licensed under the MIT license. // SPDX-License-Identifier: MIT -mod trailer_properties_provider_impl; +mod wheelchair_distance_decreasing_provider_impl; use std::net::SocketAddr; -use digital_twin_model::trailer_v1; -use digital_twin_providers_common::constants::chariott::{ +use wheelchair_digital_twin_model::car_v1; +use wheelchair_digital_twin_providers_common::constants::chariott::{ INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_KIND, INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_REFERENCE, INVEHICLE_DIGITAL_TWIN_SERVICE_NAME, INVEHICLE_DIGITAL_TWIN_SERVICE_NAMESPACE, INVEHICLE_DIGITAL_TWIN_SERVICE_VERSION, }; -use digital_twin_providers_common::constants::{digital_twin_operation, digital_twin_protocol}; -use digital_twin_providers_common::utils::discover_service_using_chariott; +use wheelchair_digital_twin_providers_common::constants::{digital_twin_operation, digital_twin_protocol}; +use wheelchair_digital_twin_providers_common::utils::discover_service_using_chariott; use env_logger::{Builder, Target}; use interfaces::invehicle_digital_twin::v1::invehicle_digital_twin_client::InvehicleDigitalTwinClient; use interfaces::invehicle_digital_twin::v1::{EndpointInfo, EntityAccessInfo, RegisterRequest}; @@ -25,20 +25,20 @@ use tokio::time::{sleep, Duration}; use tonic::transport::Server; use tonic::Status; -use crate::trailer_properties_provider_impl::TrailerPropertiesProviderImpl; +use crate::wheelchair_distance_decreasing_provider_impl::WheelchairDistanceDecreasingProviderImpl; // TODO: These could be added in configuration const CHARIOTT_SERVICE_DISCOVERY_URI: &str = "http://0.0.0.0:50000"; -const PROVIDER_AUTHORITY: &str = "0.0.0.0:4030"; +const PROVIDER_AUTHORITY: &str = "0.0.0.0:4050"; -const DEFAULT_MIN_INTERVAL_MS: u64 = 10000; // 10 seconds +const DEFAULT_MIN_INTERVAL_MS: u64 = 10; -/// Register the trailer weight property's endpoint. +/// Register the wheelchair distance property's endpoint. /// /// # Arguments /// * `invehicle_digital_twin_uri` - The In-Vehicle Digital Twin URI. /// * `provider_uri` - The provider's URI. -async fn register_trailer_weight( +async fn register_wheelchair_distance_decreasing( invehicle_digital_twin_uri: &str, provider_uri: &str, ) -> Result<(), Status> { @@ -50,9 +50,9 @@ async fn register_trailer_weight( }; let entity_access_info = EntityAccessInfo { - name: trailer_v1::trailer::trailer_weight::NAME.to_string(), - id: trailer_v1::trailer::trailer_weight::ID.to_string(), - description: trailer_v1::trailer::trailer_weight::DESCRIPTION.to_string(), + name: car_v1::car::car_wheelchair_distance::NAME.to_string(), + id: car_v1::car::car_wheelchair_distance::ID.to_string(), + description: car_v1::car::car_wheelchair_distance::DESCRIPTION.to_string(), endpoint_info_list: vec![endpoint_info], }; @@ -67,52 +67,40 @@ async fn register_trailer_weight( Ok(()) } -/// Start the trailer weight data stream. +/// Start the wheelchair distance decreasing data stream. /// /// # Arguments /// `min_interval_ms` - minimum frequency for data stream. -fn start_trailer_weight_data_stream(min_interval_ms: u64) -> watch::Receiver { - debug!("Starting the Provider's trailer weight data stream."); - let mut weight: i32 = 1000; - let (sender, reciever) = watch::channel(weight); +fn start_wheelchair_distance_decreasing_data_stream(min_interval_ms: u64) -> watch::Receiver { + debug!("Starting the Provider's wheelchair distance data stream."); + let mut distance: i32 = 700; + let (sender, receiver) = watch::channel(distance); tokio::spawn(async move { - let mut is_weight_increasing: bool = true; loop { debug!( - "Recording new value for {} of {weight}", - trailer_v1::trailer::trailer_weight::ID + "Recording new value for {} of {distance}", + car_v1::car::car_wheelchair_distance::ID ); - if let Err(err) = sender.send(weight) { + if let Err(err) = sender.send(distance) { warn!("Failed to get new value due to '{err:?}'"); break; } debug!("Completed the publish request"); - // Calculate the new weight. - // It bounces back and forth between 1000 and 2000 kilograms. - // It increases in increments of 500 to simulate a large amount of cargo being loaded - // And decreases in increments of 50 to simulate smaller deliveries being made - if is_weight_increasing { - if weight == 2000 { - is_weight_increasing = false; - weight -= 50; - } else { - weight += 500; - } - } else if weight == 1000 { - is_weight_increasing = true; - weight += 500; - } else { - weight -= 50; + // Calculate the new distance. + // it decreases in increments of 1 to simulate smaller distances + // Start from 700 cm away from the car and come closer by 1 cm every 10 ms -> 1 m/second. + if distance>0 { + distance-=1 } sleep(Duration::from_millis(min_interval_ms)).await; } }); - reciever + receiver } #[tokio::main] @@ -142,11 +130,11 @@ async fn main() -> Result<(), Box> { debug!("The Provider retrieved Chariott's Service Discovery URI."); // Start mock data stream. - let data_stream = start_trailer_weight_data_stream(DEFAULT_MIN_INTERVAL_MS); - debug!("The Provider has started the trailer weight data stream."); + let data_stream = start_wheelchair_distance_decreasing_data_stream(DEFAULT_MIN_INTERVAL_MS); + debug!("The Provider has started the wheelchair distance decreasing data stream."); // Setup provider management cb endpoint. - let provider = TrailerPropertiesProviderImpl::new(data_stream, DEFAULT_MIN_INTERVAL_MS); + let provider = WheelchairDistanceDecreasingProviderImpl::new(data_stream, DEFAULT_MIN_INTERVAL_MS); // Start service. let addr: SocketAddr = PROVIDER_AUTHORITY.parse()?; @@ -155,7 +143,7 @@ async fn main() -> Result<(), Box> { .serve(addr); // This could be enhanced with retries for robustness - register_trailer_weight(&invehicle_digital_twin_uri, &provider_uri).await?; + register_wheelchair_distance_decreasing(&invehicle_digital_twin_uri, &provider_uri).await?; debug!("The Provider has registered with Ibeji."); server_future.await?; diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider/src/trailer_properties_provider_impl.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider/src/wheelchair_distance_decreasing_impl_provider.rs similarity index 88% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider/src/trailer_properties_provider_impl.rs rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider/src/wheelchair_distance_decreasing_impl_provider.rs index 5ea60a1..bc0391f 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/digital_twin_providers/trailer_properties_provider/src/trailer_properties_provider_impl.rs +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider/src/wheelchair_distance_decreasing_impl_provider.rs @@ -11,7 +11,7 @@ use interfaces::module::managed_subscribe::v1::{ CallbackPayload, TopicManagementRequest, TopicManagementResponse, }; -use digital_twin_model::{trailer_v1, Metadata}; +use digital_twin_model::{car_v1, Metadata}; use digital_twin_providers_common::utils; use log::{debug, info, warn}; use paho_mqtt as mqtt; @@ -22,13 +22,14 @@ use tokio::sync::{mpsc, watch}; use tokio::time::{sleep, Duration}; use tonic::{Request, Response, Status}; -const MQTT_CLIENT_ID: &str = "trailer-properties-publisher"; +const MQTT_CLIENT_ID: &str = "wheelchair-distance-decreasing-publisher"; const FREQUENCY_MS: &str = "frequency_ms"; #[derive(Debug, Serialize, Deserialize)] -struct TrailerWeightProperty { - #[serde(rename = "TrailerWeight")] - trailer_weight: trailer_v1::trailer::trailer_weight::TYPE, +#[serde(tag="type")] +struct WheelchairDistanceProperty { + #[serde(rename = "WheelchairDistance")] + wheelchair_distance: car_v1::car::car_wheelchair_distance::TYPE, #[serde(rename = "$metadata")] metadata: Metadata, } @@ -50,23 +51,23 @@ pub struct TopicInfo { } #[derive(Debug)] -pub struct TrailerPropertiesProviderImpl { +pub struct WheelchairDistanceDecreasingProviderImpl { pub data_stream: watch::Receiver, pub min_interval_ms: u64, entity_map: Arc>>>, } -/// Create the JSON for the trailer weight property. +/// Create the JSON for the wheelchair distance property. /// /// # Arguments -/// * `trailer_weight` - The trailer weight value. -fn create_property_json(trailer_weight: i32) -> String { +/// * `wheelchair_distance` - The wheelchair distance value. +fn create_property_json(wheelchair_distance: i32) -> String { let metadata = Metadata { - model: trailer_v1::trailer::trailer_weight::ID.to_string(), + model: car_v1::car::car_wheelchair_distance::ID.to_string(), }; - let property: TrailerWeightProperty = TrailerWeightProperty { - trailer_weight, + let property: WheelchairDistanceProperty = WheelchairDistanceProperty { + wheelchair_distance, metadata, }; @@ -109,7 +110,7 @@ fn publish_message(broker_uri: &str, topic: &str, content: &str) -> Result<(), S Ok(()) } -impl TrailerPropertiesProviderImpl { +impl WheelchairDistanceDecreasingProviderImpl { /// Initializes provider with entities relevant to itself. /// /// # Arguments @@ -121,12 +122,12 @@ impl TrailerPropertiesProviderImpl { // Insert entry for entity id's associated with provider. entity_map.insert( - trailer_v1::trailer::trailer_weight::ID.to_string(), + car_v1::car::car_wheelchair_distance::ID.to_string(), Vec::new(), ); // Create new instance. - TrailerPropertiesProviderImpl { + WheelchairDistanceDecreasingProviderImpl { data_stream, min_interval_ms, entity_map: Arc::new(RwLock::new(entity_map)), @@ -192,7 +193,7 @@ impl TrailerPropertiesProviderImpl { // Publish message to broker. info!( "Publish to {topic} for {} with value {data}", - trailer_v1::trailer::trailer_weight::NAME + car_v1::car::car_wheelchair_distance::NAME ); if let Err(err) = publish_message(&broker_uri, &topic, &content) { @@ -234,7 +235,7 @@ impl TrailerPropertiesProviderImpl { } #[tonic::async_trait] -impl ManagedSubscribeCallback for TrailerPropertiesProviderImpl { +impl ManagedSubscribeCallback for WheelchairDistanceDecreasingProviderImpl { /// Callback for a provider, will process a provider action. /// /// # Arguments diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider/src/wheelchair_distance_decreasing_provider_impl.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider/src/wheelchair_distance_decreasing_provider_impl.rs new file mode 100644 index 0000000..92ae6b1 --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_decreasing_provider/src/wheelchair_distance_decreasing_provider_impl.rs @@ -0,0 +1,260 @@ +// Copyright (c) IAV GmbH. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +use std::collections::HashMap; +use std::str::FromStr; +use std::sync::Arc; + +use interfaces::module::managed_subscribe::v1::managed_subscribe_callback_server::ManagedSubscribeCallback; +use interfaces::module::managed_subscribe::v1::{ + CallbackPayload, TopicManagementRequest, TopicManagementResponse, +}; + +use wheelchair_digital_twin_model::{car_v1, Metadata}; +use wheelchair_digital_twin_providers_common::utils; +use log::{debug, info, warn}; +use paho_mqtt as mqtt; +use parking_lot::RwLock; +use serde_derive::{Deserialize, Serialize}; +use strum_macros::{Display, EnumString}; +use tokio::sync::{mpsc, watch}; +use tokio::time::{sleep, Duration}; +use tonic::{Request, Response, Status}; + +const MQTT_CLIENT_ID: &str = "wheelchair-distance-decreasing-publisher"; +const FREQUENCY_MS: &str = "frequency_ms"; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag="type")] +struct WheelchairDistanceProperty { + #[serde(rename = "WheelchairDistance")] + wheelchair_distance: car_v1::car::car_wheelchair_distance::TYPE, + #[serde(rename = "$metadata")] + metadata: Metadata, +} + +/// Actions that are returned from the Pub Sub Service. +#[derive(Clone, EnumString, Eq, Display, Debug, PartialEq)] +pub enum ProviderAction { + #[strum(serialize = "PUBLISH")] + Publish, + + #[strum(serialize = "STOP_PUBLISH")] + StopPublish, +} + +#[derive(Debug)] +pub struct TopicInfo { + topic: String, + stop_channel: mpsc::Sender, +} + +#[derive(Debug)] +pub struct WheelchairDistanceDecreasingProviderImpl { + pub data_stream: watch::Receiver, + pub min_interval_ms: u64, + entity_map: Arc>>>, +} + +/// Create the JSON for the wheelchair distance property. +/// +/// # Arguments +/// * `wheelchair_distance` - The wheelchair distance value. +fn create_property_json(wheelchair_distance: i32) -> String { + let metadata = Metadata { + model: car_v1::car::car_wheelchair_distance::ID.to_string(), + }; + + let property: WheelchairDistanceProperty = WheelchairDistanceProperty { + wheelchair_distance, + metadata, + }; + + serde_json::to_string(&property).unwrap() +} + +/// Publish a message to a MQTT broker located. +/// +/// # Arguments +/// `broker_uri` - The MQTT broker's URI. +/// `topic` - The topic to publish to. +/// `content` - The message to publish. +fn publish_message(broker_uri: &str, topic: &str, content: &str) -> Result<(), String> { + let create_opts = mqtt::CreateOptionsBuilder::new() + .server_uri(broker_uri) + .client_id(MQTT_CLIENT_ID.to_string()) + .finalize(); + + let client = mqtt::Client::new(create_opts) + .map_err(|err| format!("Failed to create the client due to '{err:?}'"))?; + + let conn_opts = mqtt::ConnectOptionsBuilder::new() + .keep_alive_interval(Duration::from_secs(30)) + .clean_session(true) + .finalize(); + + let _connect_response = client + .connect(conn_opts) + .map_err(|err| format!("Failed to connect due to '{err:?}")); + + let msg = mqtt::Message::new(topic, content, mqtt::types::QOS_1); + if let Err(err) = client.publish(msg) { + return Err(format!("Failed to publish message due to '{err:?}")); + } + + if let Err(err) = client.disconnect(None) { + warn!("Failed to disconnect from topic '{topic}' on broker {broker_uri} due to {err:?}"); + } + + Ok(()) +} + +impl WheelchairDistanceDecreasingProviderImpl { + /// Initializes provider with entities relevant to itself. + /// + /// # Arguments + /// * `data_stream` - Receiver for data stream for entity. + /// * `min_interval_ms` - The frequency of the data coming over the data stream. + pub fn new(data_stream: watch::Receiver, min_interval_ms: u64) -> Self { + // Initialize entity map. + let mut entity_map = HashMap::new(); + + // Insert entry for entity id's associated with provider. + entity_map.insert( + car_v1::car::car_wheelchair_distance::ID.to_string(), + Vec::new(), + ); + + // Create new instance. + WheelchairDistanceDecreasingProviderImpl { + data_stream, + min_interval_ms, + entity_map: Arc::new(RwLock::new(entity_map)), + } + } + + /// Handles the 'PUBLISH' action from the callback. + /// + /// # Arguments + /// `payload` - Payload sent with the 'PUBLISH' action. + pub fn handle_publish_action(&self, payload: CallbackPayload) { + // Get payload information. + let topic = payload.topic; + let constraints = payload.constraints; + let min_interval_ms = self.min_interval_ms; + + // This should not be empty. + let mut subscription_info = payload.subscription_info.unwrap(); + + subscription_info.uri = utils::get_uri(&subscription_info.uri).unwrap(); + + // Create stop publish channel. + let (sender, mut reciever) = mpsc::channel(10); + + // Create topic info. + let topic_info = TopicInfo { + topic: topic.clone(), + stop_channel: sender, + }; + + // Record new topic in entity map. + { + let mut entity_lock = self.entity_map.write(); + let get_result = entity_lock.get_mut(&payload.entity_id); + get_result.unwrap().push(topic_info); + } + + let data_stream = self.data_stream.clone(); + + // Start thread for new topic. + tokio::spawn(async move { + // Get constraints information. + let mut frequency_ms = min_interval_ms; + + for constraint in constraints { + if constraint.r#type == *FREQUENCY_MS { + frequency_ms = u64::from_str(&constraint.value).unwrap(); + }; + } + + loop { + // See if we need to shutdown. + if reciever.try_recv() == Err(mpsc::error::TryRecvError::Disconnected) { + info!("Shutdown thread for {topic}."); + return; + } + + // Get data from stream at the current instant. + let data = *data_stream.borrow(); + let content = create_property_json(data); + let broker_uri = subscription_info.uri.clone(); + + // Publish message to broker. + info!( + "Publish to {topic} for {} with value {data}", + car_v1::car::car_wheelchair_distance::NAME + ); + + if let Err(err) = publish_message(&broker_uri, &topic, &content) { + warn!("Publish failed due to '{err:?}'"); + break; + } + + debug!("Completed publish to {topic}."); + + // Sleep for requested amount of time. + sleep(Duration::from_millis(frequency_ms)).await; + } + }); + } + + /// Handles the 'STOP_PUBLISH' action from the callback. + /// + /// # Arguments + /// `payload` - Payload sent with the 'STOP_PUBLISH' action. + pub fn handle_stop_publish_action(&self, payload: CallbackPayload) { + let topic_info: TopicInfo; + + let mut entity_lock = self.entity_map.write(); + let get_result = entity_lock.get_mut(&payload.entity_id); + + let topics = get_result.unwrap(); + + // Check to see if topic exists. + if let Some(index) = topics.iter_mut().position(|t| t.topic == payload.topic) { + // Remove topic. + topic_info = topics.swap_remove(index); + + // Stop publishing to removed topic. + drop(topic_info.stop_channel); + } else { + warn!("No topic found matching {}", payload.topic); + } + } +} + +#[tonic::async_trait] +impl ManagedSubscribeCallback for WheelchairDistanceDecreasingProviderImpl { + /// Callback for a provider, will process a provider action. + /// + /// # Arguments + /// * `request` - The request with the action and associated payload. + async fn topic_management_cb( + &self, + request: Request, + ) -> Result, Status> { + let inner = request.into_inner(); + let action = inner.action; + let payload = inner.payload.unwrap(); + + let provider_action = ProviderAction::from_str(&action).unwrap(); + + match provider_action { + ProviderAction::Publish => Self::handle_publish_action(self, payload), + ProviderAction::StopPublish => Self::handle_stop_publish_action(self, payload), + } + + Ok(Response::new(TopicManagementResponse {})) + } +} diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_increasing_provider/Cargo.toml b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_increasing_provider/Cargo.toml new file mode 100644 index 0000000..c9c0f49 --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_increasing_provider/Cargo.toml @@ -0,0 +1,29 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +[package] +name = "wheelchair_distance_increasing_provider" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[dependencies] +wheelchair_digital_twin_model= { path = "../../digital-twin-model" } +wheelchair_digital_twin_providers_common = { path = "../common" } +env_logger = { workspace = true } +interfaces = { path = "../../../../proto_build"} +log = { workspace = true } +paho-mqtt = { workspace = true } +parking_lot = { workspace = true } +serde = { workspace = true } +serde_derive = { workspace = true } +serde_json = { workspace = true } +wheelchair_assistant_interfaces = { path = "../../proto_build" } +strum = { workspace = true } +strum_macros = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] } +tonic = { workspace = true } + +[features] +containerize = ["wheelchair_digital_twin_providers_common/containerize"] diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_increasing_provider/Dockerfile b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_increasing_provider/Dockerfile new file mode 100644 index 0000000..0875016 --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_increasing_provider/Dockerfile @@ -0,0 +1,74 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Dockerfile reference guide at +# https://docs.docker.com/engine/reference/builder/ + +################################################################################ +# Create a stage for building the application. + +ARG RUST_VERSION=1.72.1 +FROM docker.io/library/rust:${RUST_VERSION}-slim-bullseye AS build +ARG APP_NAME=wheelchair_distance_increasing_provider +WORKDIR /sdv + +COPY ./ . + +# Add Build dependencies. +RUN apt update && apt upgrade -y && apt install -y \ + cmake \ + libssl-dev \ + pkg-config \ + protobuf-compiler + +# Check that APP_NAME argument is valid. +RUN sanitized=$(echo "${APP_NAME}" | tr -dc '^[a-zA-Z_0-9-]+$'); \ +[ "$sanitized" = "${APP_NAME}" ] || { \ + echo "ARG 'APP_NAME' is invalid. APP_NAME='${APP_NAME}' sanitized='${sanitized}'"; \ + exit 1; \ +} + +# Build the application +RUN cargo build --release --bin "${APP_NAME}" + +# Copy the built application to working directory. +RUN cp ./target/release/"${APP_NAME}" /sdv/service + +################################################################################ +# Create a new stage for running the application that contains the minimal +# runtime dependencies for the application. This often uses a different base +# image from the build stage where the necessary files are copied from the build +# stage. +# +# The example below uses the debian bullseye image as the foundation for running the app. +# By specifying the "bullseye-slim" tag, it will also use whatever happens to be the +# most recent version of that tag when you build your Dockerfile. If +# reproducability is important, consider using a digest +# (e.g., debian@sha256:ac707220fbd7b67fc19b112cee8170b41a9e97f703f588b2cdbbcdcecdd8af57). +FROM docker.io/library/debian:bullseye-slim AS final + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser +USER appuser + +WORKDIR /sdv + +# Copy the executable from the "build" stage. +COPY --from=build /sdv/service /sdv/ + +# Expose the port that the application listens on. +EXPOSE 4030 + +# What the container should run when it is started. +CMD ["/sdv/service"] \ No newline at end of file diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_increasing_provider/src/main.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_increasing_provider/src/main.rs new file mode 100644 index 0000000..4c6c45c --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_increasing_provider/src/main.rs @@ -0,0 +1,157 @@ +// Copyright (c) IAV GmbH. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +mod wheelchair_distance_increasing_provider_impl; + +use std::net::SocketAddr; + +use wheelchair_digital_twin_model::car_v1; +use wheelchair_digital_twin_providers_common::constants::chariott::{ + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_KIND, + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_REFERENCE, INVEHICLE_DIGITAL_TWIN_SERVICE_NAME, + INVEHICLE_DIGITAL_TWIN_SERVICE_NAMESPACE, INVEHICLE_DIGITAL_TWIN_SERVICE_VERSION, +}; +use wheelchair_digital_twin_providers_common::constants::{digital_twin_operation, digital_twin_protocol}; +use wheelchair_digital_twin_providers_common::utils::discover_service_using_chariott; +use env_logger::{Builder, Target}; +use interfaces::invehicle_digital_twin::v1::invehicle_digital_twin_client::InvehicleDigitalTwinClient; +use interfaces::invehicle_digital_twin::v1::{EndpointInfo, EntityAccessInfo, RegisterRequest}; +use interfaces::module::managed_subscribe::v1::managed_subscribe_callback_server::ManagedSubscribeCallbackServer; +use log::{debug, info, warn, LevelFilter}; +use tokio::signal; +use tokio::sync::watch; +use tokio::time::{sleep, Duration}; +use tonic::transport::Server; +use tonic::Status; + +use crate::wheelchair_distance_increasing_provider_impl::WheelchairDistanceIncreasingProviderImpl; + +// TODO: These could be added in configuration +const CHARIOTT_SERVICE_DISCOVERY_URI: &str = "http://0.0.0.0:50000"; +const PROVIDER_AUTHORITY: &str = "0.0.0.0:4060"; + +const DEFAULT_MIN_INTERVAL_MS: u64 = 10; + +/// Register the wheelchair distance property's endpoint. +/// +/// # Arguments +/// * `invehicle_digital_twin_uri` - The In-Vehicle Digital Twin URI. +/// * `provider_uri` - The provider's URI. +async fn register_wheelchair_distance( + invehicle_digital_twin_uri: &str, + provider_uri: &str, +) -> Result<(), Status> { + let endpoint_info = EndpointInfo { + protocol: digital_twin_protocol::GRPC.to_string(), + operations: vec![digital_twin_operation::MANAGEDSUBSCRIBE.to_string()], + uri: provider_uri.to_string(), + context: "GetSubscriptionInfo".to_string(), + }; + + let entity_access_info = EntityAccessInfo { + name: car_v1::car::car_wheelchair_distance::NAME.to_string(), + id: car_v1::car::car_wheelchair_distance::ID.to_string(), + description: car_v1::car::car_wheelchair_distance::DESCRIPTION.to_string(), + endpoint_info_list: vec![endpoint_info], + }; + + let mut client = InvehicleDigitalTwinClient::connect(invehicle_digital_twin_uri.to_string()) + .await + .map_err(|e| Status::internal(e.to_string()))?; + let request = tonic::Request::new(RegisterRequest { + entity_access_info_list: vec![entity_access_info], + }); + let _response = client.register(request).await?; + + Ok(()) +} + +/// Start the wheelchair distance data stream. Add 1 cm every 10 ms, so make 1 m per second. +/// +/// # Arguments +/// `min_interval_ms` - minimum frequency for data stream. +fn start_wheelchair_distance_increasing_data_stream(min_interval_ms: u64) -> watch::Receiver { + debug!("Starting the Provider's wheelchair distance increasing data stream."); + let mut distance: i32 = 0; + let (sender, receiver) = watch::channel(distance); + tokio::spawn(async move { + loop { + debug!( + "Recording new value for {} of {distance}", + car_v1::car::car_wheelchair_distance::ID + ); + + if let Err(err) = sender.send(distance) { + warn!("Failed to get new value due to '{err:?}'"); + break; + } + + debug!("Completed the publish request"); + + // Calculate the new distance, it increases by 1 m every second, until it is 10 m. + // This function simulates the person going away from the car. + if distance < 1000 { + distance += 1; + } + + sleep(Duration::from_millis(min_interval_ms)).await; + } + }); + + receiver +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup logging. + Builder::new() + .filter(None, LevelFilter::Info) + .target(Target::Stdout) + .init(); + + info!("The Provider has started."); + + let provider_uri = format!("http://{PROVIDER_AUTHORITY}"); // Devskim: ignore DS137138 + + // Get the In-vehicle Digital Twin Uri from the service discovery system + // This could be enhanced to add retries for robustness + let invehicle_digital_twin_uri = discover_service_using_chariott( + CHARIOTT_SERVICE_DISCOVERY_URI, + INVEHICLE_DIGITAL_TWIN_SERVICE_NAMESPACE, + INVEHICLE_DIGITAL_TWIN_SERVICE_NAME, + INVEHICLE_DIGITAL_TWIN_SERVICE_VERSION, + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_KIND, + INVEHICLE_DIGITAL_TWIN_SERVICE_COMMUNICATION_REFERENCE, + ) + .await?; + + debug!("The Provider retrieved Chariott's Service Discovery URI."); + + // Start mock data stream. + let data_stream = start_wheelchair_distance_increasing_data_stream(DEFAULT_MIN_INTERVAL_MS); + debug!("The Provider has started the wheelchair distance increasing data stream."); + + // Setup provider management cb endpoint. + let provider = WheelchairDistanceIncreasingProviderImpl::new(data_stream, DEFAULT_MIN_INTERVAL_MS); + + // Start service. + let addr: SocketAddr = PROVIDER_AUTHORITY.parse()?; + let server_future = Server::builder() + .add_service(ManagedSubscribeCallbackServer::new(provider)) + .serve(addr); + + // This could be enhanced with retries for robustness + register_wheelchair_distance(&invehicle_digital_twin_uri, &provider_uri).await?; + debug!("The Provider has registered with Ibeji."); + + server_future.await?; + + signal::ctrl_c() + .await + .expect("Failed to listen for control-c event"); + + info!("The Provider has completed."); + + Ok(()) +} diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_increasing_provider/src/wheelchair_distance_increasing_provider_impl.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_increasing_provider/src/wheelchair_distance_increasing_provider_impl.rs new file mode 100644 index 0000000..2ecfa3e --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/digital_twin_providers/wheelchair_distance_increasing_provider/src/wheelchair_distance_increasing_provider_impl.rs @@ -0,0 +1,259 @@ +// Copyright (c) IAV GmbH. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +use std::collections::HashMap; +use std::str::FromStr; +use std::sync::Arc; + +use interfaces::module::managed_subscribe::v1::managed_subscribe_callback_server::ManagedSubscribeCallback; +use interfaces::module::managed_subscribe::v1::{ + CallbackPayload, TopicManagementRequest, TopicManagementResponse, +}; + +use wheelchair_digital_twin_model::{car_v1, Metadata}; +use wheelchair_digital_twin_providers_common::utils; +use log::{debug, info, warn}; +use paho_mqtt as mqtt; +use parking_lot::RwLock; +use serde_derive::{Deserialize, Serialize}; +use strum_macros::{Display, EnumString}; +use tokio::sync::{mpsc, watch}; +use tokio::time::{sleep, Duration}; +use tonic::{Request, Response, Status}; + +const MQTT_CLIENT_ID: &str = "wheelchair-distance-increasing-publisher"; +const FREQUENCY_MS: &str = "frequency_ms"; + +#[derive(Debug, Serialize, Deserialize)] +struct WheelchairDistanceProperty { + #[serde(rename = "WheelchairDistance")] + wheelchair_distance: car_v1::car::car_wheelchair_distance::TYPE, + #[serde(rename = "$metadata")] + metadata: Metadata, +} + +/// Actions that are returned from the Pub Sub Service. +#[derive(Clone, EnumString, Eq, Display, Debug, PartialEq)] +pub enum ProviderAction { + #[strum(serialize = "PUBLISH")] + Publish, + + #[strum(serialize = "STOP_PUBLISH")] + StopPublish, +} + +#[derive(Debug)] +pub struct TopicInfo { + topic: String, + stop_channel: mpsc::Sender, +} + +#[derive(Debug)] +pub struct WheelchairDistanceIncreasingProviderImpl { + pub data_stream: watch::Receiver, + pub min_interval_ms: u64, + entity_map: Arc>>>, +} + +/// Create the JSON for the wheelchair distance property. +/// +/// # Arguments +/// * `wheelchair_distance` - The wheelchair distance value. +fn create_property_json(wheelchair_distance: i32) -> String { + let metadata = Metadata { + model: car_v1::car::car_wheelchair_distance::ID.to_string(), + }; + + let property: WheelchairDistanceProperty = WheelchairDistanceProperty { + wheelchair_distance, + metadata, + }; + + serde_json::to_string(&property).unwrap() +} + +/// Publish a message to a MQTT broker located. +/// +/// # Arguments +/// `broker_uri` - The MQTT broker's URI. +/// `topic` - The topic to publish to. +/// `content` - The message to publish. +fn publish_message(broker_uri: &str, topic: &str, content: &str) -> Result<(), String> { + let create_opts = mqtt::CreateOptionsBuilder::new() + .server_uri(broker_uri) + .client_id(MQTT_CLIENT_ID.to_string()) + .finalize(); + + let client = mqtt::Client::new(create_opts) + .map_err(|err| format!("Failed to create the client due to '{err:?}'"))?; + + let conn_opts = mqtt::ConnectOptionsBuilder::new() + .keep_alive_interval(Duration::from_secs(30)) + .clean_session(true) + .finalize(); + + let _connect_response = client + .connect(conn_opts) + .map_err(|err| format!("Failed to connect due to '{err:?}")); + + let msg = mqtt::Message::new(topic, content, mqtt::types::QOS_1); + if let Err(err) = client.publish(msg) { + return Err(format!("Failed to publish message due to '{err:?}")); + } + + if let Err(err) = client.disconnect(None) { + warn!("Failed to disconnect from topic '{topic}' on broker {broker_uri} due to {err:?}"); + } + + Ok(()) +} + +impl WheelchairDistanceIncreasingProviderImpl { + /// Initializes provider with entities relevant to itself. + /// + /// # Arguments + /// * `data_stream` - Receiver for data stream for entity. + /// * `min_interval_ms` - The frequency of the data coming over the data stream. + pub fn new(data_stream: watch::Receiver, min_interval_ms: u64) -> Self { + // Initialize entity map. + let mut entity_map = HashMap::new(); + + // Insert entry for entity id's associated with provider. + entity_map.insert( + car_v1::car::car_wheelchair_distance::ID.to_string(), + Vec::new(), + ); + + // Create new instance. + WheelchairDistanceIncreasingProviderImpl { + data_stream, + min_interval_ms, + entity_map: Arc::new(RwLock::new(entity_map)), + } + } + + /// Handles the 'PUBLISH' action from the callback. + /// + /// # Arguments + /// `payload` - Payload sent with the 'PUBLISH' action. + pub fn handle_publish_action(&self, payload: CallbackPayload) { + // Get payload information. + let topic = payload.topic; + let constraints = payload.constraints; + let min_interval_ms = self.min_interval_ms; + + // This should not be empty. + let mut subscription_info = payload.subscription_info.unwrap(); + + subscription_info.uri = utils::get_uri(&subscription_info.uri).unwrap(); + + // Create stop publish channel. + let (sender, mut reciever) = mpsc::channel(10); + + // Create topic info. + let topic_info = TopicInfo { + topic: topic.clone(), + stop_channel: sender, + }; + + // Record new topic in entity map. + { + let mut entity_lock = self.entity_map.write(); + let get_result = entity_lock.get_mut(&payload.entity_id); + get_result.unwrap().push(topic_info); + } + + let data_stream = self.data_stream.clone(); + + // Start thread for new topic. + tokio::spawn(async move { + // Get constraints information. + let mut frequency_ms = min_interval_ms; + + for constraint in constraints { + if constraint.r#type == *FREQUENCY_MS { + frequency_ms = u64::from_str(&constraint.value).unwrap(); + }; + } + + loop { + // See if we need to shutdown. + if reciever.try_recv() == Err(mpsc::error::TryRecvError::Disconnected) { + info!("Shutdown thread for {topic}."); + return; + } + + // Get data from stream at the current instant. + let data = *data_stream.borrow(); + let content = create_property_json(data); + let broker_uri = subscription_info.uri.clone(); + + // Publish message to broker. + info!( + "Publish to {topic} for {} with value {data}", + car_v1::car::car_wheelchair_distance::NAME + ); + + if let Err(err) = publish_message(&broker_uri, &topic, &content) { + warn!("Publish failed due to '{err:?}'"); + break; + } + + debug!("Completed publish to {topic}."); + + // Sleep for requested amount of time. + sleep(Duration::from_millis(frequency_ms)).await; + } + }); + } + + /// Handles the 'STOP_PUBLISH' action from the callback. + /// + /// # Arguments + /// `payload` - Payload sent with the 'STOP_PUBLISH' action. + pub fn handle_stop_publish_action(&self, payload: CallbackPayload) { + let topic_info: TopicInfo; + + let mut entity_lock = self.entity_map.write(); + let get_result = entity_lock.get_mut(&payload.entity_id); + + let topics = get_result.unwrap(); + + // Check to see if topic exists. + if let Some(index) = topics.iter_mut().position(|t| t.topic == payload.topic) { + // Remove topic. + topic_info = topics.swap_remove(index); + + // Stop publishing to removed topic. + drop(topic_info.stop_channel); + } else { + warn!("No topic found matching {}", payload.topic); + } + } +} + +#[tonic::async_trait] +impl ManagedSubscribeCallback for WheelchairDistanceIncreasingProviderImpl { + /// Callback for a provider, will process a provider action. + /// + /// # Arguments + /// * `request` - The request with the action and associated payload. + async fn topic_management_cb( + &self, + request: Request, + ) -> Result, Status> { + let inner = request.into_inner(); + let action = inner.action; + let payload = inner.payload.unwrap(); + + let provider_action = ProviderAction::from_str(&action).unwrap(); + + match provider_action { + ProviderAction::Publish => Self::handle_publish_action(self, payload), + ProviderAction::StopPublish => Self::handle_stop_publish_action(self, payload), + } + + Ok(Response::new(TopicManagementResponse {})) + } +} diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/interfaces/digital_twin_get_provider.proto b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/interfaces/digital_twin_get_provider.proto similarity index 90% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/interfaces/digital_twin_get_provider.proto rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/interfaces/digital_twin_get_provider.proto index b11b28e..5cd026a 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/interfaces/digital_twin_get_provider.proto +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/interfaces/digital_twin_get_provider.proto @@ -1,26 +1,26 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// SPDX-License-Identifier: MIT - -// Digital Twin "Get" Provider definition -// -// The protobuf definitions for a Digital Twin Provider which only supports synchronous -// "Get" operation - -syntax = "proto3"; -package digital_twin_get_provider; - -// The service entry point to the Digital Twin Get Provider. This simple provider has one method -// to get the property -service DigitalTwinGetProvider { - // Method which gets the value of the specified property - rpc Get (GetRequest) returns (GetResponse); -} - -message GetRequest { - string entity_id = 1; -} - -message GetResponse { - bool property_value = 1; -} +// Copyright (c) IAV GmbH. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +// Digital Twin "Get" Provider definition +// +// The protobuf definitions for a Digital Twin Provider which only supports synchronous +// "Get" operation + +syntax = "proto3"; +package digital_twin_get_provider; + +// The service entry point to the Digital Twin Get Provider. This simple provider has one method +// to get the property +service DigitalTwinGetProvider { + // Method which gets the value of the specified property + rpc Get (GetRequest) returns (GetResponse); +} + +message GetRequest { + string entity_id = 1; +} + +message GetResponse { + bool property_value = 1; +} diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/proto_build/Cargo.toml b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/proto_build/Cargo.toml similarity index 89% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/proto_build/Cargo.toml rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/proto_build/Cargo.toml index 716307c..534d288 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/proto_build/Cargo.toml +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/proto_build/Cargo.toml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT [package] -name = "smart_trailer_interfaces" +name = "wheelchair_assistant_interfaces" version = "0.1.0" edition = "2021" license = "MIT" diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/proto_build/build.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/proto_build/build.rs similarity index 84% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/proto_build/build.rs rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/proto_build/build.rs index 771678c..042b23b 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/proto_build/build.rs +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/proto_build/build.rs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) IAV GmbH. // Licensed under the MIT license. // SPDX-License-Identifier: MIT diff --git a/in-vehicle-stack/scenarios/smart_trailer_use_case/proto_build/src/lib.rs b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/proto_build/src/lib.rs similarity index 85% rename from in-vehicle-stack/scenarios/smart_trailer_use_case/proto_build/src/lib.rs rename to in-vehicle-stack/scenarios/wheelchair_assistant_use_case/proto_build/src/lib.rs index a9f07e7..e486da2 100644 --- a/in-vehicle-stack/scenarios/smart_trailer_use_case/proto_build/src/lib.rs +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/proto_build/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) IAV GmbH. // Licensed under the MIT license. // SPDX-License-Identifier: MIT diff --git a/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/scripts/start_wheelchair_assistant_provider_ankaios.sh b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/scripts/start_wheelchair_assistant_provider_ankaios.sh new file mode 100755 index 0000000..63a0a8a --- /dev/null +++ b/in-vehicle-stack/scenarios/wheelchair_assistant_use_case/scripts/start_wheelchair_assistant_provider_ankaios.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +set -e + +# Deklariere das Array +#PROVIDER_CONTAINERS=( +# "car_off_provider" +# "car_on_provider" +# "carkey_lock_provider" +# "carkey_unlock_provider" +# "wheelchair_distance_decreasing_provider" +# "wheelchair_distance_increasing_provider" +#) + +PROVIDER_CONTAINERS=( + "car_off_provider" +) + +# Get the directory of where the script is located +# All relative paths will be in relation to this +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +# Iterate over provider containers to build +for CONTAINER in "${PROVIDER_CONTAINERS[@]}"; do + CFG_PROVIDER="image: sdvblueprint.azurecr.io/sdvblueprint/in-vehicle-stack/--${CONTAINER}:0.1.0\ncommandOptions: [\"--network\", \"host\", \"--name\", \"${CONTAINER}\"]" + ank run workload ${CONTAINER} --runtime podman --config "$CFG_PROVIDER" --agent agent_A +done diff --git a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/.gitignore b/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/.gitignore deleted file mode 100644 index 2f7896d..0000000 --- a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target/ diff --git a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/Cargo.toml b/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/Cargo.toml deleted file mode 100644 index 698c842..0000000 --- a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "resource_statistics" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -anyhow = "1" -actix-web = "4.4.0" -tokio = { version = "1.24.2", features = ["sync", "io-util", "rt-multi-thread", "macros"] } -tokio-util = "0.7.4" -tokio-stream = "0.1.1" -async-process = "2.0.0" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -env_logger = "0.10" -log = "0.4" -lazy_static = "1.4.0" diff --git a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/Dockerfile b/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/Dockerfile deleted file mode 100644 index d410565..0000000 --- a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -FROM docker.io/alpine:3.18.4 as compile -ENV PATH="/root/.cargo/bin:${PATH}" - -RUN apk update && apk add --update-cache \ - curl \ - # Development tools - protobuf \ - protobuf-dev \ - protoc \ - build-base \ - && curl --proto '=https' --tlsv1.2 -sS https://sh.rustup.rs | sh -s -- -y > /dev/null \ - && rm -rf /var/cache/apk/* - -COPY . /workspaces/build -WORKDIR /workspaces/build -RUN cargo build --release - -# stage prod -FROM docker.io/alpine:3.18.4 -COPY --from=compile /workspaces/build/target/release/resource_statistics /usr/local/bin/resource_statistics -RUN chmod +x /usr/local/bin/resource_statistics -ENTRYPOINT ["/usr/local/bin/resource_statistics"] \ No newline at end of file diff --git a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/README.md b/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/README.md deleted file mode 100644 index 611e092..0000000 --- a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Resource Monitoring Workload - -For challenges requiring resource usage statistics a workload application is provided that provides you some initial resource statistic values of the host the workload is running on. The statistics can be accessed via a REST API as JSON. - -This image is public available: - -```shell -docker pull ghcr.io/eclipse-ankaios/maestro_resource_monitor:0.1.0 -``` - -The image url is already pre-configured in the [initial startup state](../../../eclipse-ankaios/config/startupState.yaml) of Ankaios. -So, if you select a challenge requiring the statistics please uncomment the config part in the initial startup state to enable this workload and start Ankaios again. - -## Request the resource usage statistic - -To fetch the resource usage statistics execute the following command: - -```shell -curl -sL localhost:25555 -``` - -## Extend with custom resource usage values - -The workload app is designed for easily adding new resource statistic values. Depending on your input for the OpenAI model you can adapt the commands fetching the resource statistic values from the host namespace. - -Open the file `src/commands.rs` and adapt existing commands or add a new command: - -```rust - pub static ref COMMANDS: HashMap<&'static str, &'static str> = { - HashMap::from([ - // ... - ("new-resource-statistic", "command for new resource statistic value") - ]) - }; -``` - -The commands are automatically executed and the values inserted into the json response. - -After you have added commands for fetching resource usage statistics you must build a new version of the container image and -publish it to a container registry of your choice. Afterwards you must replace the initial image url in the [initial startup state](../../../eclipse-ankaios/config/startupState.yaml) of Ankaios. - -## Build - -Replace `` in the following command with a registry you have access to and build the resource monitor workload: - -```shell -docker run --rm --privileged multiarch/qemu-user-static --reset -p yes -docker buildx create --name mybuilder --driver docker-container --bootstrap -docker buildx use mybuilder -docker buildx build -t /maestro_resource_monitor: --platform linux/amd64,linux/arm64 . -``` - -**Note:** Make sure that the image is public available (without authentication) or log in into the registry before starting the workload with Ankaios. - -## Run - -You can also run the workload without Ankaios for testing, by running it with the following command and settings: - -```shell -docker run --rm -d --name resource_monitor --privileged --network host --pid host --name resource_monitor -p 25555:25555 /maestro_resource_monitor: -``` \ No newline at end of file diff --git a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/src/commands.rs b/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/src/commands.rs deleted file mode 100644 index 95cf727..0000000 --- a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/src/commands.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::collections::HashMap; - -lazy_static::lazy_static! { - pub static ref COMMANDS: HashMap<&'static str, &'static str> = { - HashMap::from([ - ("cpu-usage", "awk '/cpu / {CPU=($2+$4)*100/($2+$4+$5)} END {printf(\"%.1f\", CPU)}' /proc/stat"), // CPU usage in % - ("cpu-usage-user", "awk '/cpu / {CPU=($2)*100/($2+$4+$5)} END {printf(\"%.1f\", CPU)}' /proc/stat"), // CPU usage user in % - ("cpu-usage-system", "awk '/cpu / {CPU=($4)*100/($2+$4+$5)} END {printf(\"%.1f\", CPU)}' /proc/stat"), // CPU usage system in % - ("memory-usage", "awk '/MemTotal/ {TOT=$2} /MemFree/ {FREE=$2} END {printf(\"%.1f\", FREE/TOT * 100)}' /proc/meminfo"), // memory usage in % - ("cores", "awk '/siblings/ {printf(\"%u\", $3); exit}' /proc/cpuinfo"), // amount of cores - ("memory-total", "awk '/MemTotal/ {printf(\"%u\", $2/1024)}' /proc/meminfo"), // total memory in KiB - ]) - }; -} diff --git a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/src/main.rs b/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/src/main.rs deleted file mode 100644 index c162089..0000000 --- a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/src/main.rs +++ /dev/null @@ -1,32 +0,0 @@ -use actix_web::{web, App, HttpServer}; -use std::net::SocketAddr; - -mod resource_statistic_job; -mod routes; -mod commands; - -use resource_statistic_job::init_resource_statistic_cache; - -#[actix_web::main] -async fn main() -> anyhow::Result<()> { - env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); - - let socket_address = - std::env::var("RESOURCE_STATISTICS_API_SOCKET").unwrap_or("0.0.0.0:25555".to_string()); - let socket_address: SocketAddr = socket_address.parse()?; - - let (item_cache, cache_sweep_handle, cache_sweep_cancel) = init_resource_statistic_cache(); - HttpServer::new(move || { - App::new() - .service(routes::statistics) - .app_data(web::Data::from(item_cache.clone())) - }) - .workers(2) - .bind((socket_address.ip(), socket_address.port()))? - .run() - .await?; - - cache_sweep_cancel.cancel(); - cache_sweep_handle.await.unwrap(); - Ok(()) -} diff --git a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/src/resource_statistic_job.rs b/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/src/resource_statistic_job.rs deleted file mode 100644 index e13e871..0000000 --- a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/src/resource_statistic_job.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::commands::COMMANDS; -use async_process::Command; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; -use std::time::Duration; -use tokio::{task::JoinHandle, time::sleep}; -use tokio_util::sync::CancellationToken; - -pub type ItemCache = Mutex>>; - -pub fn init_resource_statistic_cache() -> (Arc, JoinHandle<()>, CancellationToken) { - let cache = Arc::new(ItemCache::default()); - - let cache_sweep_cancel = CancellationToken::new(); - ( - cache.clone(), - tokio::spawn(fetch_statistics(cache, cache_sweep_cancel.clone())), - cache_sweep_cancel, - ) -} - -async fn exec_command(cmd: &str) -> anyhow::Result { - let result = Command::new("sh").args(["-c", cmd]).output().await?; - Ok(String::from_utf8(result.stdout)?) -} - -async fn fetch_statistics(cache: Arc, stop_signal: CancellationToken) { - let host = exec_command("hostname") - .await - .unwrap_or("unknown".to_string()) - .strip_suffix('\n') - .unwrap() - .to_owned(); - - loop { - for (typ, cmd) in COMMANDS.iter() { - match Command::new("sh").args(["-c", cmd]).output().await { - Ok(result) => { - let output_stdout = String::from_utf8(result.stdout).unwrap(); - - match cache.lock().unwrap().entry(host.clone()) { - std::collections::hash_map::Entry::Vacant(new_value) => { - log::debug!("Inserting new resource statistic value '{}' with value '{}' for new host '{}'", typ, output_stdout, host); - let mut new_map = HashMap::new(); - new_map.insert(typ.to_string(), output_stdout); - new_value.insert(new_map); - } - std::collections::hash_map::Entry::Occupied(existing_value) => { - log::debug!("Inserting new resource statistic value '{}' with value '{}' for existing host '{}'", typ, output_stdout, host); - existing_value - .into_mut() - .insert(typ.to_string(), output_stdout); - } - } - } - Err(err) => log::error!("{}", err), - } - } - - tokio::select! { - _ = sleep(Duration::from_secs(5)) => { - continue; - } - - _ = stop_signal.cancelled() => { - log::info!("gracefully shutting down resource statistics job"); - break; - } - }; - } -} diff --git a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/src/routes.rs b/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/src/routes.rs deleted file mode 100644 index fa4a7b2..0000000 --- a/scenarios/intelligent_orchestrator_use_case/ankaios_resource_statistics_app/src/routes.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::resource_statistic_job::ItemCache; -use actix_web::{get, web, HttpResponse, Responder}; - -#[get("/")] -pub(crate) async fn statistics(cache: web::Data) -> impl Responder { - match cache.lock() { - Ok(entry) => HttpResponse::Ok().json(&*entry), - Err(_) => HttpResponse::InternalServerError() - .body("failed to request the resource statistics on the host."), - } -} diff --git a/scenarios/smart_trailer_use_case/scripts/start_trailer_applications_ankaios.sh b/scenarios/smart_trailer_use_case/scripts/start_trailer_applications_ankaios.sh deleted file mode 100755 index 82d65f6..0000000 --- a/scenarios/smart_trailer_use_case/scripts/start_trailer_applications_ankaios.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/bash -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. -# SPDX-License-Identifier: MIT - -set -e -# This script requires jq and grpcurl to be installed -# The commands for this can be added here - -# Get the directory of where the script is located -# All relative paths will be in relation to this -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) - -# The Ibeji gRPC server address -SERVER="0.0.0.0:5010" - -# The Ibeji FindById gRPC service and method -SERVICE="invehicle_digital_twin.InvehicleDigitalTwin" -METHOD="FindById" - -# The request body: The IsTrailerConnected signal -BODY='{"id":"dtmi:sdv:Trailer:IsTrailerConnected;1"}' - -PROTO_PATH="${SCRIPT_DIR}/../../../in-vehicle-stack/interfaces/invehicle_digital_twin/v1" -PROTO="invehicle_digital_twin.proto" - -EXPECTED_PROTOCOL="grpc" -EXPECTED_OPERATION="get" - -# Call FindById in a loop until something is returned -while true; do - OUTPUT=$(grpcurl -import-path $PROTO_PATH -proto $PROTO -plaintext -d "$BODY" $SERVER $SERVICE/$METHOD 2>&1) - - # Check if the output contains entityAccessInfo (the response from Ibeji when a provider is found) - if echo "$OUTPUT" | grep -iq "EntityAccessInfo" - then - echo "The FindById call was successful. Output:" - echo "$OUTPUT" - break - else - echo "Provider not found: $OUTPUT" - echo "The trailer is not connected. Retrying..." - sleep 5 - fi -done - -# Parse the output as a JSON object using jq and extract the endpoints -ENDPOINTS=$(echo $OUTPUT | jq -c '.entityAccessInfo.endpointInfoList[]') - -# Loop through each endpoint -for ENDPOINT in $ENDPOINTS -do - # Check if protocol is what we expect - if [[ $(echo $ENDPOINT | jq -r '.protocol' | tr '[:upper:]' '[:lower:]') == $EXPECTED_PROTOCOL ]] - then - OPERATIONS=$(echo $ENDPOINT | jq -r '.operations[]') - # Loop through each operation and check if this endpoint supports the expected operation - for OPERATION in $OPERATIONS - do - if [[ $(echo $OPERATION | tr '[:upper:]' '[:lower:]') == $EXPECTED_OPERATION ]] - then - URI=$(echo $ENDPOINT | jq -r '.uri') - CONTEXT=$(echo $ENDPOINT | jq -r '.context') - - # We need the authority for the server, so remove the http:// - get_server=$(echo "$URI" | sed 's/http:\/\///g') - - # Call get for the "trailer connected provider" to check if it's connected - GET_PROTO_PATH="${SCRIPT_DIR}/../interfaces" - GET_PROTO="digital_twin_get_provider.proto" - GET_SERVER=$get_server - GET_SERVICE="digital_twin_get_provider.DigitalTwinGetProvider" - GET_METHOD="Get" - GET_OUTPUT=$(grpcurl -import-path $GET_PROTO_PATH -proto $GET_PROTO -plaintext $GET_SERVER $GET_SERVICE/$GET_METHOD 2>&1) - - # For now, this always returns true, this can be expanded to simulate connecting and disconnecting the trailer - if [[ $(echo $GET_OUTPUT | jq -r '.propertyValue') ]] - then - echo "Trailer is connected! Starting workloads to manage it" - - # Start up the other workloads using podman - CFG_PROVIDER=$'image: localhost/trailer_properties_provider_ank:latest\ncommandOptions: ["--network", "host", "--name", "trailer_properties_provider"]' - CFG_APP=$'image: localhost/smart_trailer_application_ank:latest\ncommandOptions: ["--network", "host", "--name", "smart_trailer_application"]' - - ank run workload trailer_properties_provider --runtime podman --config "$CFG_PROVIDER" --agent agent_A - ank run workload smart_trailer_application --runtime podman --config "$CFG_APP" --agent agent_A - - echo "Called Ankaios to start the Trailer Properties Digital Twin Provider and Smart Trailer Application" - echo "Check Ankaios status with 'ank get workloads'" - exit 0 - fi - fi - done - fi -done -# We didn't find an endpoint which satisfied our conditions -exit 1 \ No newline at end of file diff --git a/scenarios/wheelchair_assistant_use_case/start_wheelchair_assistant_provider_ankaios.sh b/scenarios/wheelchair_assistant_use_case/start_wheelchair_assistant_provider_ankaios.sh new file mode 100755 index 0000000..1593b98 --- /dev/null +++ b/scenarios/wheelchair_assistant_use_case/start_wheelchair_assistant_provider_ankaios.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +set -e + +# Deklariere das Array +PROVIDER_CONTAINERS=( + "car_off_provider" + "car_on_provider" + "carkey_lock_provider" + "carkey_unlock_provider" + "wheelchair_distance_decreasing_provider" + "wheelchair_distance_increasing_provider" +) + +PROVIDER_CONTAINERS=( + "car_off_provider" +) + +# Get the directory of where the script is located +# All relative paths will be in relation to this +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +# Iterate over provider containers to build +for CONTAINER in "${PROVIDER_CONTAINERS[@]}"; do + CFG_PROVIDER="image: sdvblueprint.azurecr.io/sdvblueprint/in-vehicle-stack/--${CONTAINER}:0.1.0\ncommandOptions: [\"--network\", \"host\", \"--name\", \"${CONTAINER}\"]" + echo "${CFG_PROVIDER}" + ank run workload ${CONTAINER} --runtime podman --config "$CFG_PROVIDER" --agent agent_A +done