diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d499d77000223514ac4d47d5200a27b35f00b034 --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +## Ignore .openapi-generator Folder +## +.openapi-generator/ +server/worldstorage/src/ETSI.ARF.OpenAPI.WorldStorage/.gitignore + +# Visual Studio cache/options directory +.vs/ + +# Visual Studio solution +*.sln + +# generated project-files +*.csproj + +# build-jobs +build.* + +# database data directory +server/data/ + +# database directory +server/programs/MongoDB/ +!server/programs/MongoDB/kill_MongoDB.bat +!server/programs/MongoDB/startMongoDB.bat +!server/programs/MongoDB/readme.md + +#generated readme +server/worldstorage/README.md + +#!ETSI-ARF/Models/ +#!ETSI-ARF/Controllers/ +#!ETSI-ARF/Services/ + +# all generated directories +wwwroot/ +!wwwroot/ws.html +Attributes/ +Authentication/ +Converters/ +Filters/ +Formatters/ +OpenAPI/ +Properties/ +Models +Controllers/ + +# generated Program.cs +#Program.cs + +# backup-files +*.bak diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..455ab71b55998fb287f525e89da7ba82c44b6034 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "openapi"] + path = openapi + url = https://forge.etsi.org/rep/arf/openapi.git + branch = master \ No newline at end of file diff --git a/.openapi-generator-ignore b/.openapi-generator-ignore new file mode 100644 index 0000000000000000000000000000000000000000..745cfa9c3f6ff86e020b61516a35dce6c7dfb896 --- /dev/null +++ b/.openapi-generator-ignore @@ -0,0 +1,41 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md + +# +# ETSI - ISG - ARF +# +**\ETSI.ARF.OpenAPI.WorldAnalysis.csproj + +# Modules to initialize World Storage database, server.... +**/Program.cs +**/Startup.cs +**/Dockerfile +**/appsettings.json + +# Implementation of REST request and database functionalities +**/ETSI-ARF + +# Design of some web pages +**/wwwroot/portal +**/wwwroot/index.html \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..369a2512139f698ce91876fe56d973ad63690d21 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 ETSI + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 08c8fa851d9e13d8f678b2718fc889b109829b8a..bbf773d0e2288ec4d158c4e6d81e47d79f2ca068 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,213 @@ -# World Analysis ASP.NET Server +*This repository is part of the outcomes of the Specialist Task Force 620 focusing on the authoring of a World Representation as part of the ETSI ISG Augmented Reality Framework architecture (https://www.etsi.org/deliver/etsi_gs/ARF/001_099/003/01.01.01_60/gs_ARF003v010101p.pdf).* +*The set of the World Representation authoring components includes:* +*• The C++ and C# source code for servers and clients generated from OpenAPI available here (https://forge.etsi.org/rep/arf/arf005)* +*• A Unity plugin and a Unity editor for authoring and accessing a World Representation hosted on a World Storage server.* -## Getting started +*All these components are available under the ETSI Labs group “World Storage API Helpers”: https://labs.etsi.org/rep/arf/world-storage-api-helpers* -To make it easy for you to get started with GitLab, here's a list of recommended next steps. +*If you wish to contribute to this project or any other projects in the context of the [ETSI ISG Augmented Reality Framework architecture](https://www.etsi.org/committee/1420-arf), please refer to the ["How to get involved in an ISG" section on the ETSI website](https://www.etsi.org/how-to-get-involved-in-an-isg)* -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! +--- -## Add your files +# Description - World Analysis Server for STF 669 -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: +This repo should be used to construct a complete ASP-Net REST server compliant to the ARF World Analysis API. It uses auto-generated ASP.NET server code. We propose to use the open source OpenAPI-Generator for this. +It includes description and code for a fully functional server with MongoDB integration. + +## Repo Content + +| | Files / Folders | Description | +|:-:|:--------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| +| 📂 | openapi | A git submodule (ForgeETSI ) pointing to the repo containing the API specification file | +| 📂 | server | The folder where the library code will be generated, the openapi generator is set to not overwrite some files used to generate and initialiue the ASP.Net server system | +| 📂 | server/worldstorage/src/ETSI.ARF.OpenAPI.WorldAnalysis | This is the location where the ASP.Net code will be generated | +| 📂 | server/worldstorage/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF | This folder contains codes implementating the REST end-paths for the different objects in API | +| 📂 | server/programs/MongoDB | This folder contains the MongoDB service. The World Storage database should be created or imported in a folder of your choice which path has to be editied in the .bat file | +| 📂 | server\worldanalysis\src\ETSI.ARF.OpenAPI.WorldAnalysis\appsettings.json | Parameter for accessing the MongoDB server from the ASP.NET services. This file contains the MongoDB server IP and optional port number, name of the World Storage database and their collections. | + +## Requirements + +What you need: + +1. Installed npm: https://phoenixnap.com/kb/install-node-js-npm-on-windows +2. Installed openapi generator with npm: https://openapi-generator.tech/docs/installation/ +3. Installed docker (recommanded): https://www.docker.com/get-started + +# Code Generation + +We provide the file `.openapi-generator-ignore` in `server`, which prevents openapi-generator to override some adapted files. + +## Auto-generate server code + +Use/define following setup for the config file `openapitools.json`: + +``` +{ + "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "5.3.0", + "generators":{ + "v1.1": { + "generatorName": "aspnetcore", + "output": "./server/worldanalysis", + "inputSpec": "./openapi/API/worldanalysis/worldanalysisopenapi.yaml", + "additionalProperties": { + "aspnetCoreVersion": "5.0", + "packageName": "ETSI.ARF.OpenAPI.WorldAnalysis", + "operationModifier": "abstract", + "classModifier": "abstract" + } + } + } + } +} +``` + +Open a command shell and execute: +``` + npx openapi-generator-cli generate +``` + +Open the solution `ETSI.ARF.OpenAPI.WorldAnalysis.sln` (folder `server/worldstorage`) in Visual Studio: + +## In Visual Studio +Open `NuGet Package Manager` and add `MongoDB.Driver`. + +### File adaptations +Change version number in all files if a new version is provided. + +## 📂 Custom folders (new) +All custom files are now in the folder 'ETSI-ARF'. Nothing to do adapt manually after generation. But you have to provide the implementation of new endpoints because they are now directly declared as abstract (default). + +The folder contains following subfolders: +`ControllersImpl` +`ModelsExt` +`Services` + +### 📂 Folder 'ControllersImp' + +The modules in this folder implement the endpoints. Compare files folder in "`ETSI-ARF/ControllersImpl`" with the corresponding files in "`Controllers`" and implement if necessary the new methods. + +Methods should be the same with "`override`" instead of "`abstract`". + +--- + +#### If some files are missing (and only then!) +Add ``` -cd existing_repo -git remote add origin https://labs.etsi.org/rep/arf/world-analysis-api-helpers/world-analysis-asp.net-server.git -git branch -M main -git push -uf origin main + using MongoDB.Driver; ``` -## Integrate with your tools +Add one or more private readonly services class variable like in the already existing files, e.g.: +``` +private readonly xxxService _xxxService; +``` -- [ ] [Set up project integrations](https://labs.etsi.org/rep/arf/world-analysis-api-helpers/world-analysis-asp.net-server/-/settings/integrations) +Add a constructor with this service class variable like in the already existing files. +``` +public XxxApiControllerImpl(TrackableService trackableService) +{ + _xxxService = xxxService; + // etc. +} +``` -## Collaborate with your team +Implement endpoint code using the appropriate MongoDB methods of the corresponding classes from the folder `Services` (which you may be have to create). -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) +### 📂 Folder `ModelsExt` +The modules inside this folder are for extensions of the generated API data structures. Add all the classes to be stored in the database (i.e. `XxxExt.cs`, ) inherited from `IModels`. (definition of the extra UUID): +``` + using MongoDB.Bson; + using MongoDB.Bson.Serialization.Attributes; +``` -## Test and Deploy +and at the value that is to become the MongoDB ID, add: +``` + [BsonId] + [BsonRepresentation(BsonType.String)] +``` -Use the built-in continuous integration in GitLab. +If some members won't be saved in the MongoDB then use this keyword: +``` +[BsonIgnore] +``` -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) +### 📂 Folder `Services` +The folder `Services` is for handling the data with the MongoDB. It should contain one common class with the DatabaseSettings (`DatabaseSettings.cs`) and one with the database-access-methods (create, get, update, remove) for each API. If some are missing create them like the ones you find there. Be aware to add the reference to these in the file `startup.cs` in this case. -*** +The naming in the DatabaseSettings is the same as defined in `appsettings.json`, which you have to extend when creating new classes in this folder. Change `appsettings.json` in the folder `docker` accordingly. Make sure that the ConnectionString for the database contains the correct IP address as specified in `docker-compose.yml`. -# Editing this README +## 📂 Extra folder `wwwroot` +Add in `openapi-original.json` in section `servers` the urls of the servers you want to use with swagger-ui -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. +# MongoDB +If you don't have a MongoDB, follow the instructions in `readme.md` in `server/programs/MongoDB` -## Suggestions for a good README -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. +...and put MongoDB in folder `server/programs/MongoDB` (download MongoDB as zip-file from https://www.mongodb.com/try/download/community and unzip the file into this directory, so that the bin-directory is in this folder). -## Name -Choose a self-explaining name for your project. +To setup MongoDB in the IIS webserver, adjust the parameters in the file `appsettings.json`. Default values for the databes an collections are: -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. + "DatabaseName": "WorldAnalysisAPI", + "CollectionNameWorldLinks": "XXXs", -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. +Set the correct server IP/adress and port number (if there is one). Use the network setup as you define in the Docker-Compose yaml file, e.g. +``` + ports: + - 27037:27017 // mapping, for accessing the MongoDB from outside + - 27038:27018 + networks: + vpcbr: + ipv4_address: 172.24.30.101 // or whatever you want +``` +Server settings: -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. + "MongoSrv": "172.24.30.101", // same as defined in the yaml file + "MongoPort": "27017", // internal docker port -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. +## How to dump database +Execute the following command in docker: +``` + mongodump --db **insert database_name** --out /data-dump/`date +"%Y-%m-%d"` +``` -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. +## How to import database +Execute the following command in docker: +``` + mongorestore --db **insert database_name** **insert path_to_bson_file** +``` -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. +# Use in Visual Studio +Make sure, that an instance of MongoDB is running. -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. +Start application with IIS Express. -## Contributing -State if you are open to contributions and what your requirements are for accepting them. -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. +# Use API within a Docker -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. +## Creating the IIS docker ## +Remove the substring `src/Org.OpenAPITools/` in Dockerfile (if not already done) -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. +open a command shell and generate docker by executing: +``` + build-iis-docker-ws.bat +``` -## License -For open source projects, say how it is licensed. +## How to start (with Docker-Compose) +The easiest way is to use docker-compose: -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. +Open a command shell and use docker-compose (if necessary adapt docker-compose.yml) by executing`: +``` + docker-compose.bat +``` + +Open http://localhost:8080/openapi/index.html in a web-browser, if you want to check the functionalities using SwaggerUI + +## How to stop +Open a command shell by executing in `server/worldstorage/src/ETSI.ARF.OpenAPI.WorldStorage`: +``` + docker-compose down +``` diff --git a/build-iis-docker-wa.bat b/build-iis-docker-wa.bat new file mode 100644 index 0000000000000000000000000000000000000000..2550b0ab445b24ddef1cb6297b0ea09216f6485b --- /dev/null +++ b/build-iis-docker-wa.bat @@ -0,0 +1,2 @@ +cd server\worldanalysis\src\ETSI.ARF.OpenAPI.WorldAnalysis +docker build -t etsi.arf.openapi.worldanalysis . \ No newline at end of file diff --git a/docker-compose.bat b/docker-compose.bat new file mode 100644 index 0000000000000000000000000000000000000000..81eda89a647ab99a76ade58d3594e5abba9f2b22 --- /dev/null +++ b/docker-compose.bat @@ -0,0 +1 @@ +docker-compose up -d --force-recreate --remove-orphans \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..83d878be3cb26cfb6d88ce21a37784a86c54852b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,45 @@ +# Please refer https://aka.ms/HTTPSinContainer on how to setup an https developer certificate for your ASP .NET Core service. + +name: etsi_arf_wa_server +version: '1.0' + +networks: + minionetwork: + driver: bridge + vpcbr: + ipam: + config: + - subnet: 172.24.30.0/24 + +services: + mongod: + container_name: etsi_mongodb + image: mongo:latest + environment: + - PUID=1000 + - PGID=1000 + volumes: + # - mongodbdata:/data/mongodb:rw + - C:/Fraunhofer/Servers/ETSI/MongoDB/data:/data/db:rw + - C:/Fraunhofer/Servers/ETSI/MongoDB/data-dump:/data/db-dump:rw + # - D:/Fraunhofer/Servers/ETSI/MongoDB/mongod.yaml:/etc/mongod.conf:rw + ports: + - 27037:27017 + - 27038:27018 + networks: + vpcbr: + ipv4_address: 172.24.30.101 + restart: unless-stopped + + etsiapi: + container_name: etsi_arf_iis_wa_server + image: etsi.arf.openapi.worldanalysis:latest + volumes: + - C:/Fraunhofer/Servers/ETSI/appsettings.json:/app/appsettings.json:rw + - C:/Fraunhofer/Servers/ETSI/WWWRoot:/app/wwwroot:rw + ports: + - 8082:44301 + networks: + vpcbr: + ipv4_address: 172.24.30.100 + restart: unless-stopped diff --git a/openapi b/openapi new file mode 160000 index 0000000000000000000000000000000000000000..073fd7213fd9e6ebc2f8a47d628a650de30c8bc4 --- /dev/null +++ b/openapi @@ -0,0 +1 @@ +Subproject commit 073fd7213fd9e6ebc2f8a47d628a650de30c8bc4 diff --git a/openapitools.json b/openapitools.json new file mode 100644 index 0000000000000000000000000000000000000000..47445f2073cde8906cf8db44e071e7ad6b1fb6e3 --- /dev/null +++ b/openapitools.json @@ -0,0 +1,20 @@ +{ + "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "7.6.0", + "generators":{ + "v1.1": { + "generatorName": "aspnetcore", + "output": "./server/worldanalysis", + "inputSpec": "./openapi/API/worldanalysis/worldanalysisopenapi.yaml", + "additionalProperties": { + "aspnetCoreVersion": "5.0", + "packageName": "ETSI.ARF.OpenAPI.WorldAnalysis", + "operationModifier": "abstract", + "classModifier": "abstract" + } + } + } + } +} diff --git a/server/worldanalysis/.openapi-generator-ignore b/server/worldanalysis/.openapi-generator-ignore new file mode 100644 index 0000000000000000000000000000000000000000..745cfa9c3f6ff86e020b61516a35dce6c7dfb896 --- /dev/null +++ b/server/worldanalysis/.openapi-generator-ignore @@ -0,0 +1,41 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md + +# +# ETSI - ISG - ARF +# +**\ETSI.ARF.OpenAPI.WorldAnalysis.csproj + +# Modules to initialize World Storage database, server.... +**/Program.cs +**/Startup.cs +**/Dockerfile +**/appsettings.json + +# Implementation of REST request and database functionalities +**/ETSI-ARF + +# Design of some web pages +**/wwwroot/portal +**/wwwroot/index.html \ No newline at end of file diff --git a/server/worldanalysis/README.md b/server/worldanalysis/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ac3ce3def60533a7bcacb5549e0767e71ac7ee5f --- /dev/null +++ b/server/worldanalysis/README.md @@ -0,0 +1,50 @@ +# ETSI.ARF.OpenAPI.WorldAnalysis - ASP.NET Core 5.0 Server + +API ensuring interoperability between Scene Management and a World Analysis service + +## Upgrade NuGet Packages + +NuGet packages get frequently updated. + +To upgrade this solution to the latest version of all NuGet packages, use the dotnet-outdated tool. + + +Install dotnet-outdated tool: + +``` +dotnet tool install --global dotnet-outdated-tool +``` + +Upgrade only to new minor versions of packages + +``` +dotnet outdated --upgrade --version-lock Major +``` + +Upgrade to all new versions of packages (more likely to include breaking API changes) + +``` +dotnet outdated --upgrade +``` + + +## Run + +Linux/OS X: + +``` +sh build.sh +``` + +Windows: + +``` +build.bat +``` +## Run in Docker + +``` +cd src/ETSI.ARF.OpenAPI.WorldAnalysis +docker build -t etsi.arf.openapi.worldanalysis . +docker run -p 5000:8080 etsi.arf.openapi.worldanalysis +``` diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/.gitignore b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..1ee53850b84cd478da00264a919c8180a746056e --- /dev/null +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/.gitignore @@ -0,0 +1,362 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/Dockerfile b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..76f44e25130c34acda31ffe342b389729b530da1 --- /dev/null +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/Dockerfile @@ -0,0 +1,32 @@ +#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. + +# Container we use for final publish +FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +# Build container +FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build + +# Copy the code into the container +WORKDIR /src +COPY ["ETSI.ARF.OpenAPI.WorldAnalysis.csproj", "ETSI.ARF.OpenAPI.WorldAnalysis/"] + +# NuGet restore +RUN dotnet restore "ETSI.ARF.OpenAPI.WorldAnalysis/ETSI.ARF.OpenAPI.WorldAnalysis.csproj" +COPY [".", "ETSI.ARF.OpenAPI.WorldAnalysis/"] + +# Build the API +WORKDIR "ETSI.ARF.OpenAPI.WorldAnalysis" +RUN dotnet build "ETSI.ARF.OpenAPI.WorldAnalysis.csproj" -c Release -o /app/build + +# Publish it +FROM build AS publish +RUN dotnet publish "ETSI.ARF.OpenAPI.WorldAnalysis.csproj" -c Release -o /app/publish + +# Make the final image for publishing +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "ETSI.ARF.OpenAPI.WorldAnalysis.dll"] diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/CapabilitiesImpl.cs b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/CapabilitiesImpl.cs new file mode 100644 index 0000000000000000000000000000000000000000..4638d5c7df96786128abcc49291f699338163b95 --- /dev/null +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/CapabilitiesImpl.cs @@ -0,0 +1,94 @@ +// +// ARF - Augmented Reality Framework (ETSI ISG ARF) +// +// Copyright 2024 ETSI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Last change: June 2024 +// + +/* + * World Analysis API + * + * API ensuring interoperability between Scene Management and a World Analysis service + * + * The version of the OpenAPI document: 2.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.SwaggerGen; +using Newtonsoft.Json; +using ETSI.ARF.OpenAPI.WorldAnalysis.Attributes; +using ETSI.ARF.OpenAPI.WorldAnalysis.Models; + +namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers +{ + /// + /// + /// + [ApiController] + public class CapabilitiesApiControllerImpl : CapabilitiesApiController + { + /// + /// Get the supported capabilities of the World Analysis + /// + public override IActionResult GetCapabilities([FromHeader (Name = "token")]string token, [FromHeader (Name = "sessionID")]string sessionID) + { + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID + + // Get all capabilities from all anchors/trackables + // Info: Capabilities are collected after a module is connected via websockets + + // Create list + List capabilitiesList = new List(); + capabilitiesList.AddRange(WorldAnalysisConnections.Singleton.GetCapabilities()); + + // Create response object + GetCapabilities200Response response = new GetCapabilities200Response(); + response.Capabilities = capabilitiesList; + return new ObjectResult(response); + //return StatusCode(405, "Not supported yet!"); + } + + /// + /// For a given trackable or anchor, get its support information + /// + public override IActionResult GetSupport([FromRoute (Name = "trackableOrAnchorUUID")][Required]Guid trackableOrAnchorUUID, [FromHeader (Name = "token")]string token, [FromHeader (Name = "sessionID")]string sessionID) + { + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID + + // Create list + List capabilitiesList = new List(); + capabilitiesList.AddRange(WorldAnalysisConnections.Singleton.GetCapabilitiesFromUuid(trackableOrAnchorUUID)); + + // Create response object + GetSupport200Response response = new GetSupport200Response(); + response.Capabilities = capabilitiesList; + return new ObjectResult(response); + //return StatusCode(405, "Not supported yet!"); + } + } +} diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/DefaultImpl.cs b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/DefaultImpl.cs new file mode 100644 index 0000000000000000000000000000000000000000..2b206cdafe116b0f72ba1d48eb99627814464692 --- /dev/null +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/DefaultImpl.cs @@ -0,0 +1,173 @@ +// +// ARF - Augmented Reality Framework (ETSI ISG ARF) +// +// Copyright 2024 ETSI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Last change: June 2024 +// + +/* + * ETSI ARF Core System API + * + * API ensuring interoperability between the various services, tools and apps in ARF + * + * The version of the OpenAPI document: 2.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +using System; +using System.Net; +using System.IO; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.SwaggerGen; +using Newtonsoft.Json; +using ETSI.ARF.OpenAPI.WorldAnalysis.Attributes; +using ETSI.ARF.OpenAPI.WorldAnalysis.Models; +//using ETSI.ARF.OpenAPI.WorldAnalysis.Services; + +namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers +{ +#pragma warning disable CS1591 // Fehlendes XML-Kommentar fr ffentlich sichtbaren Typ oder Element + /// + /// + /// + public class DefaultApiControllerImpl : DefaultApiController + { + public override IActionResult GetPing() + { + string answer = "Pong"; + return new ObjectResult(answer); + } + + public override IActionResult GetAdmin() + { + string answer = "ETSI ARF World Analysis Server is running."; + return new ObjectResult(answer); + } + + public override IActionResult GetVersion() + { + string answer = "ETSI ARF RESTful API v" + Startup.apiVersion; + return new ObjectResult(answer); + } + + [NonAction] + private string writeMsg(string title, string msg) + { + return $"

{title}: \t {msg}

"; + } + + [NonAction] + public /*override*/ IActionResult GetReport() + { + string projectName = "ETSI ARF - STF 669"; + string machinename = System.Environment.MachineName; + string hostname = Dns.GetHostName(); + var ips = Dns.GetHostAddresses(hostname); + + string status = "Running"; + string mongoStatus = "Running"; + //string mongoCollectionUsers = "Number = " + UserService.Singleton?.NumOfDocuments(); + //string mongoCollectionMachines = "Number = " + MachineService.Singleton?.NumOfDocuments(); + //string mongoCollectionAssets = "Number = " + AssetService.Singleton?.NumOfDocuments(); + //string mongoCollectionJobs = "Number = " + JobService.Singleton?.NumOfDocuments(); + + //List users = UserService.Singleton?.Get(); + //int activeU = 0; + //int nonActiveU = 0; + //foreach (var u in users) + // if (u.IsLogged) activeU++; + // else nonActiveU++; + + //List jobs = JobService.Singleton?.Get(); + //int jobNum = 0; + //foreach (var j in jobs) + // if (j.Progress != 100) jobNum++; + + // + // Simple output + // + string html = " " + + "" + + " SmartSiteCloud" + + "" + + "" + + "

SmartSite Cloud

" + + "

Server based on IIS/ASP.NET

" + + writeMsg("Local Date & Time:", DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString()) + + writeMsg("Machine Name", machinename) + writeMsg("Host Name", hostname) + writeMsg("IPv6", ips[0].ToString()) + + "

Services

" + + writeMsg("WebServer", status) + + writeMsg("MongoDB", mongoStatus) + + //writeMsg(" > Users", mongoCollectionUsers) + + //writeMsg(" > Machines", mongoCollectionMachines) + + //writeMsg(" > Assets", mongoCollectionAssets) + + //writeMsg(" > Jobs", mongoCollectionJobs) + + "

Database

" + + //"

Users

" + + // writeMsg(" - Active", activeU.ToString()) + + // writeMsg(" - Non active", nonActiveU.ToString()) + + //"

Jobs

" + + // writeMsg(" - Running jobs", jobNum.ToString()) + + " "; + + // + // Complex output (for RocketCake) + // + // always use https + //string domainName = (HttpContext.Request.IsHttps ? "https://" : "https://") + HttpContext.Request.Host.ToUriComponent() + "/portal"; // Ex: "https://localhost:44301/portal" + //string root = Path.Combine(Startup.wPath, "portal"); + //string htmlFname = Path.Combine(root, "status.html"); + //string[] template = System.IO.File.ReadAllLines(htmlFname); + //string html2 = ""; + + //string msg1 = + // writeMsg("Domain", domainName) + + // writeMsg("Project", projectName) + + // writeMsg("Local Date", DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString()) + + // writeMsg("Machine Name", machinename) + writeMsg("Host Name", hostname) + writeMsg("IPv6", ips[0].ToString()); + //string msg2 = + // writeMsg("WebServer", status) + + // writeMsg("MongoDB", mongoStatus) + + // //writeMsg(" > Users", mongoCollectionUsers) + + // //writeMsg(" > Machines", mongoCollectionMachines) + + // //writeMsg(" > Assets", mongoCollectionAssets) + + // //writeMsg(" > Jobs", mongoCollectionJobs) + + // writeMsg("MinIO", "-"); + + + //foreach (var line in template) + //{ + // string res = line; + // res = res.Replace("src=\"", "src=\"" + domainName + "/"); + // res = res.Replace("href=\"", "href=\"" + domainName + "/"); + // res = res.Replace("$name1$", projectName); + // res = res.Replace("$msg1$", msg1); + // res = res.Replace("$msg2$", msg2); + // html2 += res; + //} + + return Content(html, "text/html"); + //return Content(html2, "text/html"); + } + } +#pragma warning restore CS1591 // Fehlendes XML-Kommentar fr ffentlich sichtbaren Typ oder Element +} diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/PoseImpl.cs b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/PoseImpl.cs new file mode 100644 index 0000000000000000000000000000000000000000..44606416aab46dfd8512ffebd5068f2ada13de84 --- /dev/null +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/PoseImpl.cs @@ -0,0 +1,253 @@ +// +// ARF - Augmented Reality Framework (ETSI ISG ARF) +// +// Copyright 2024 ETSI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Last change: June 2024 +// + +/* + * World Analysis API + * + * API ensuring interoperability between Scene Management and a World Analysis service + * + * The version of the OpenAPI document: 2.0.1 + * + * Generated by: https://openapi-generator.tech + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.SwaggerGen; +using Newtonsoft.Json; +using ETSI.ARF.OpenAPI.WorldAnalysis.Attributes; +using ETSI.ARF.OpenAPI.WorldAnalysis.Models; + +namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers +{ + /// + /// + /// + [ApiController] + public class PoseApiControlleImpl : PoseApiController + { + /// + /// Specify the a minimum frame rate for pose estimation for Trackable types + /// + public override IActionResult ConfigureFramerate([FromBody] PoseConfiguration poseConfiguration, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) + { + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID + + // Notify the modules that the client need a new framerate + bool result = WorldAnalysisConnections.Singleton.ConfigureFramerate(poseConfiguration); + return result ? StatusCode(200, "Ok.") : StatusCode(405, "Not supported."); + } + + /// + /// Request the last pose of a single Anchor or Trackable + /// + public override IActionResult GetPose([FromRoute(Name = "trackableOrAnchorUUID")][Required] Guid trackableOrAnchorUUID, [FromQuery(Name = "mode")][Required()] ModeWorldAnalysis mode, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) + { + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID + + // Request from the modules a new pose of a single UUID + Pose result; + result = WorldAnalysisConnections.Singleton.GetPose(trackableOrAnchorUUID, mode); + return new ObjectResult(result); + } + + /// + /// Request the last pose of a batch of Anchor or Trackable + /// + public override IActionResult GetPoses([FromQuery(Name = "uuid")][Required()] List uuid, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) + { + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID + + // Request from the modules new poses from all UUIDs + GetPoses200Response result = new GetPoses200Response(); + foreach (var item in uuid) + { + result.Poses.Add(WorldAnalysisConnections.Singleton.GetPose(item.Uuid, item.Mode)); + } + return new ObjectResult(result); + } + +#pragma warning disable CS1591 // Fehlendes XML-Kommentar fr ffentlich sichtbaren Typ oder Element + + // + // Management of subscriptions + // + /// + /// Dictionnary of susbscription informations for poses, for each item, stored using the UUID of the item (anchor/trackable) + /// + private Dictionary m_subscriptionsPoses = new Dictionary(); + + public struct SubscriptionInfo + { + public Guid uuidSub; // id of subscription (id is defined by the WA server) + + public SubscribeToPoseRequest subscription; + public string webSocket; + public Pose pose; + public DateTime timeValidity; + + //public PoseCallback callback; + } +#pragma warning restore CS1591 // Fehlendes XML-Kommentar fr ffentlich sichtbaren Typ oder Element + + /// + /// Get information about a subscription + /// + public override IActionResult GetSubscription([FromRoute(Name = "subscriptionUUID")][Required] Guid subscriptionUUID, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) + { + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID + + // todo: search for a subscription and send it back + foreach (var item in m_subscriptionsPoses) + { + if (item.Key == subscriptionUUID) + { + SubscriptionSingle response = new SubscriptionSingle(); + response.Uuid = subscriptionUUID; + response.Target = item.Value.subscription.Target; + response.Mode = item.Value.pose.Mode; + response.Validity = item.Value.timeValidity.Millisecond; + response.WebhookUrl = item.Value.subscription.WebhookUrl; + response.WebsocketUrl = item.Value.webSocket; + return new ObjectResult(response); + } + } + return StatusCode(404, "Not found."); + } + + /// + /// Subscribe to collect the pose of an AR device, an Anchor or a Trackable + /// + public override IActionResult SubscribeToPose([FromBody] SubscribeToPoseRequest subscribeToPoseRequest, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) + { + // Simulation ON for STF669 pose calcualtion + string _token = token; + Guid parentUUID = Guid.Empty; + if (token.Contains(",")) + { + _token = token.Split(',')[0]; + parentUUID = new Guid(token.Split(',')[1]); + } + + if (!Startup.IsAccessGranted(_token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID + + if (subscribeToPoseRequest.Targets != null && subscribeToPoseRequest.Targets.Count > 0) + { + return StatusCode(404, "Multiple subscriptions are not implemented (targets)."); + } + else if (subscribeToPoseRequest.Modes != null && subscribeToPoseRequest.Modes.Count > 0) + { + return StatusCode(404, "Multiple subscriptions are not implemented (modes)."); + } + + int validity = subscribeToPoseRequest.Validity; // todo: is to handle here or by the client? + + SubscribeToPose200Response response = new SubscribeToPose200Response(); + response.Validity = validity; + response.Uuid = Guid.NewGuid(); + response.Target = subscribeToPoseRequest.Target; + response.Mode = subscribeToPoseRequest.Mode; + response.WebhookUrl = subscribeToPoseRequest.WebhookUrl; + response.WebsocketUrl = ""; + + // Send the websocket connection URL + if (string.IsNullOrEmpty(response.WebhookUrl)) + { + // Notice: starting websocket server is done autom. by the client, when calling "URL:xxx/ws" + // Registering the client is done in the analysis module, so the websocket can send to it pose updates + response.WebsocketUrl = "wss://" + Request.Host.ToString() + "/ws"; + } + + // We add the subscription + SubscriptionInfo info = new SubscriptionInfo(); + info.uuidSub = response.Uuid; + info.webSocket = response.WebsocketUrl; + info.timeValidity = DateTime.Now.AddMilliseconds(validity / 1000.0f); + + info.pose = new Pose(); + info.pose.Mode = response.Mode; + + info.subscription = new SubscribeToPoseRequest(); + info.subscription.Target = response.Target; + info.subscription.Mode = response.Mode; + info.subscription.Validity = response.Validity; + info.subscription.WebhookUrl = response.WebhookUrl; + + m_subscriptionsPoses.Add(info.uuidSub, info); + + // todo: inform the module(s) that the client will track an anchor/trackable and need the pose + // todo: has the module to call GetRelocalizationInformation() then?!? + WorldAnalysisConnections.Singleton.SubscribeToPose(info, parentUUID); + + return new ObjectResult(response); + } + + /// + /// Remove a subscription to a given pose + /// + public override IActionResult UnsubscribeFromPose([FromRoute(Name = "subscriptionUUID")][Required] Guid subscriptionUUID, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) + { + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID + + // Remove the subscription from the list? + if (m_subscriptionsPoses.ContainsKey(subscriptionUUID)) m_subscriptionsPoses.Remove(subscriptionUUID); + else + { + //return StatusCode(404, new Error() { Message = "Unsubscribe UUID not found!" }); + } + + // Inform the module(s) that the subscription ended + WorldAnalysisConnections.Singleton.UnsubscribeFromPose(subscriptionUUID); + + return StatusCode(200, new Success() { Message = "Unsubscription successfull." }); + } + + /// + /// Update a subscription + /// + public override IActionResult UpdateSubscription([FromRoute(Name = "subscriptionUUID")][Required] Guid subscriptionUUID, [FromBody] UpdateSubscriptionRequest updateSubscriptionRequest, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) + { + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID + + // todo: inform the module(s) that the subscription changed + + return StatusCode(405, "Not supported yet!"); + } + } +} diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/WebSocketController.cs b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/WebSocketController.cs new file mode 100644 index 0000000000000000000000000000000000000000..47315344237a9b087410b0bca2610e1fdb2a7444 --- /dev/null +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/WebSocketController.cs @@ -0,0 +1,378 @@ +// +// ARF - Augmented Reality Framework (ETSI ISG ARF) +// +// Copyright 2024 ETSI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Last change: June 2024 +// + +using System; +using System.Text; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Net.WebSockets; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.SwaggerGen; +using Newtonsoft.Json; + +using ETSI.ARF.OpenAPI.WorldAnalysis.Models; + +#pragma warning disable CS1591 // Fehlendes XML-Kommentar fr ffentlich sichtbaren Typ oder Element +namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers +{ + // + // ETSI-ARF World Analysis WebSocket implementation + // see also: https://learn.microsoft.com/de-de/aspnet/core/fundamentals/websockets?view=aspnetcore-5.0 + // + public class WebSocketController : ControllerBase + { + private WebSocket websocket; + static public int WebSocketControllerInstanceCount = 0; + + private string currentName = ""; + private bool registered = false; + private bool firstTime = true; + private int timeCnt = 3; + + [HttpGet("/ws")] + public async Task Get() + { + if (HttpContext.WebSockets.IsWebSocketRequest) + { + using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); + + if (webSocket.State == WebSocketState.Connecting) + { + // Response an OK message? + } + else if (webSocket.State == WebSocketState.Open) + { + // Go to the loop... + await WebSocketServer_Loop(HttpContext, webSocket); + } + } + else + { + await HttpContext.Response.WriteAsync("ETSI-ARF World Analysis: Not a valid WebSocket request."); + HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest; + } + } + + // + // Send a line of text + // + static private async Task SendText(WebSocket ws, string msg) + { + var message = msg; + var bytes = Encoding.UTF8.GetBytes(message); + var arraySegment = new ArraySegment(bytes, 0, bytes.Length); + await ws.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None); + } + + public void SendText(string msg) + { + var message = msg; + var bytes = Encoding.UTF8.GetBytes(message); + var arraySegment = new ArraySegment(bytes, 0, bytes.Length); + websocket.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None); + } + + // + // Send a demo pose all seconds + // + float rotInc = 0; + private async Task SendDemoPose(WebSocket ws) + { + while (true) + { + if (ws.State == WebSocketState.Open) + { + timeCnt--; + if (timeCnt < 0) + { + await SendText(ws, "PoseStop"); + break; + } + else + { + PoseValue val = new PoseValue(); + val.Unit = UnitSystem.MEnum; + val.Position = new List() { 1, 2, 3 }; + val.Rotation = new List() { rotInc, 0, 0, 0 }; + rotInc += 0.01f; + + Pose pose = new Pose(); + pose.Uuid = new Guid(); + pose.EstimationState = Pose.EstimationStateEnum.OKEnum; + //pose.SubscriptionUrl = ""; + pose.Value = val; + pose.Timestamp = (int)DateTime.Now.ToFileTime(); + pose.Confidence = 1; + pose.Mode = ModeWorldAnalysis.DEVICETOTRACKABLESEnum; + + string json = pose.ToJson(); + + await SendText(ws, "NewPose=" + json); + } + } + else if (ws.State == WebSocketState.Closed || ws.State == WebSocketState.Aborted) + { + if (WorldAnalysisConnections.Singleton.modules.Contains(currentModule)) WorldAnalysisConnections.Singleton.modules.Remove(currentModule); + if (WorldAnalysisConnections.Singleton.clients.Contains(currentClient)) WorldAnalysisConnections.Singleton.clients.Remove(currentClient); + currentClient = null; + currentModule = null; + break; + } + Thread.Sleep(250); + } + } + + private Module currentModule; + private Client currentClient; + + private async void OnReceiveText(string msg) + { + #region Register the client/module + if (firstTime) + { + if (msg.StartsWith("RegisterModule=")) + { + registered = true; + firstTime = false; + currentName = msg.Split('=')[1]; + + // If module exist already reuse it + Module module = null; + foreach (var item in WorldAnalysisConnections.Singleton.modules) if (item.name == currentName) module = item; + if (module != null) + { + currentModule = module; + currentModule.name = currentName; + currentModule.websockets = this; + currentModule.capabilities.Clear(); + } + + if (currentModule == null && currentClient == null) + { + currentModule = new Module(); + currentModule.name = currentName; + currentModule.websockets = this; + WorldAnalysisConnections.Singleton.modules.Add(currentModule); + } + SendText($"ARF World Analysis Server: #{ WebSocketControllerInstanceCount } You are now registered as module: { currentName }"); + } + else if (msg.StartsWith("RegisterClient=")) + { + registered = true; + firstTime = false; + currentName = msg.Split('=')[1]; + if (currentModule == null && currentClient == null) + { + currentClient = new Client(); + currentClient.name = currentName; + currentClient.websockets = this; + WorldAnalysisConnections.Singleton.clients.Add(currentClient); + SendText($"ARF World Analysis Server: #{ WebSocketControllerInstanceCount } You are now registered as client: { currentName }"); + } + } + else + { + registered = false; + SendText("ARF World Analysis Server: Cannot register " + msg); + } + return; + } + #endregion + + if (registered) + { + // + // Some admin stuffs + // + if (msg == "Idle") + { + SendText("Idle"); + } + else if (msg == "Busy") + { + SendText("Busy"); + } + else if (msg.StartsWith("UnregisterModule=")) + { + string name = msg.Split('=')[1]; + + // Unregister a client (e.g. Unity client) + Module module = null; + foreach (var item in WorldAnalysisConnections.Singleton.modules) if (item.name == name) module = item; + if (module != null) + { + WorldAnalysisConnections.Singleton.modules.Remove(module); + SendText("UnregisterModuleOK"); + } + currentName = ""; + firstTime = true; + registered = false; + } + else if (msg.StartsWith("UnregisterClient=")) + { + string name = msg.Split('=')[1]; + + // Unregister a client (e.g. Unity client) + Client client = null; + foreach (var item in WorldAnalysisConnections.Singleton.clients) if (item.name == name) client = item; + if (client != null) + { + WorldAnalysisConnections.Singleton.clients.Remove(client); + SendText("UnregisterClientOK"); + } + currentName = ""; + firstTime = true; + registered = false; + } + // + // OpenAPI + // + else if (msg == "PoseIsNowSubscribed") + { + SendText("RequestNextPose"); + } + + // + // Messages from a module (Analysis) + // + else if (msg.StartsWith("Capabilities=")) // Receive capab. from a module + { + // Module is sending their capabilities + string[] str_cap = msg.Split('='); + string moduleName = str_cap[1]; + Capability _c = JsonConvert.DeserializeObject(str_cap[2]); + + // Has the module already send their capabilities? + Module module = null; + foreach (var item in WorldAnalysisConnections.Singleton.modules) if (item.name == moduleName) module = item; + if (module != null) module.capabilities.Add(_c); + } + else if (msg.StartsWith("NewPose=")) + { + string[] str_pose = msg.Split('='); + + // Send the pose to the client(s) + Pose pose = JsonConvert.DeserializeObject(str_pose[1]); + + //WorldAnalysisConnections.Singleton.SendPoseToClients(pose); + WorldAnalysisConnections.Singleton.SendPoseToClients(str_pose[1]); + + // todo: if there are some subscription ask modules for new pose + SendText("RequestNextPose"); + } + else if (msg.StartsWith("Error=")) + { + string errorMsg = msg.Split('=')[1]; + foreach (var item in WorldAnalysisConnections.Singleton.clients) + { + await SendText(item.websockets.websocket, errorMsg); + } + } + + // + // Messages from a client (Unity) + // + else if (msg.StartsWith("PoseStart")) // send some fake poses to Unity clients + { + await SendDemoPose(websocket); + } + else if (msg.StartsWith("CurrentUserPose=")) + { + // A client (Unity) is sending the current user posistion + + // Send new user pose to all modules + foreach (var item in WorldAnalysisConnections.Singleton.modules) + { + await SendText(item.websockets.websocket, msg); + } + + //// Test: send user pose back to clients + //string[] str_pose = msg.Split('='); + //Pose pose = JsonConvert.DeserializeObject(str_pose[1]); + //pose.Value.Position[2] += 1f; + //WorldAnalysisConnections.Singleton.SendPoseToClients(pose); + } + + // + // Messages from modules and clients + // + else + { + // Send a response + SendText("ARF World Analysis Server: I got this unknown message: " + msg); + } + } + } + + private async Task WebSocketServer_Loop(HttpContext context, WebSocket ws) + { + websocket = ws; + WebSocketControllerInstanceCount++; + + var buffer = new byte[1024 * 4]; + + // Read/get the first data block + WebSocketReceiveResult result = await ws.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + if (result.MessageType == WebSocketMessageType.Text) + { + string getMsg = System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count); + OnReceiveText(getMsg); + } + + // Entering the loop + while (!result.CloseStatus.HasValue) + { + // Read/get the next block + result = await ws.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + if (result.MessageType == WebSocketMessageType.Text) + { + string getMsg = System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count); + OnReceiveText(getMsg); + } + } + await ws.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + WebSocketControllerInstanceCount--; + } + + + // + // Echo incoming messages + // + private async Task Echo(WebSocketReceiveResult context, WebSocket webSocket) + { + var buffer = new byte[1024 * 4]; + + // Read/get the first data block + WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + + // Send/echo the message back to the client + await SendText(webSocket, "ARF World Analysis Server: I got this (raw) message: "); + await webSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); + } + } +} +#pragma warning restore CS1591 // Fehlendes XML-Kommentar fr ffentlich sichtbaren Typ oder Element + + diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ModelsExt/NotNeeded.txt b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ModelsExt/NotNeeded.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/NoMongoDBneeded.txt b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/NoMongoDBneeded.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/Services/NotNeeded.txt b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/Services/NotNeeded.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/WorldAnalysisConnections.cs b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/WorldAnalysisConnections.cs new file mode 100644 index 0000000000000000000000000000000000000000..c864547b3750de69a96ae41035c7a6f3c4788622 --- /dev/null +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/WorldAnalysisConnections.cs @@ -0,0 +1,134 @@ +using ETSI.ARF.OpenAPI.WorldAnalysis.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using static ETSI.ARF.OpenAPI.WorldAnalysis.Controllers.PoseApiControlleImpl; +using ETSI.ARF.OpenAPI.WorldAnalysis.Controllers; + +#pragma warning disable CS1591 // Fehlendes XML-Kommentar für öffentlich sichtbaren Typ oder Element +namespace ETSI.ARF.OpenAPI.WorldAnalysis +{ + // For management of WA modules + public class Module + { + public string name; + public List capabilities = new List(); + public WebSocketController websockets; + } + + // For management of WA client (e.g. Unity client) + public class Client + { + public string name; + public string session; + public VectorQuaternionPoseValue currentUserPose; + public WebSocketController websockets; + } + + public class WorldAnalysisConnections + { + static public WorldAnalysisConnections Singleton = new WorldAnalysisConnections(); + + public List modules = new List(); + public List clients = new List(); // e.g. Unity clients + + // todo + // Manage the sessions and modules + // get capabilities of all modules + public List GetCapabilities() + { + List list = new List(); + foreach (var item in modules) + { + list.AddRange(item.capabilities); + } + return list; + } + + public List GetCapabilitiesFromUuid(Guid trackableOrAnchorUUID) + { + List list = new List(); + foreach (var item in modules) + { + // todo: Check if uuid has the capability? + // Get the world object from the world storage via the module? + // Use GetRelocalisation() ? + list.AddRange(item.capabilities); + } + return list; + } + + public bool ConfigureFramerate(PoseConfiguration poseConfiguration) + { + bool ok = false; + foreach (var m in modules) + { + bool moduleHasCap = false; + foreach (var c in m.capabilities) + { + if ((int)poseConfiguration.TrackableType == (int)c.TrackableType) + { + moduleHasCap = true; + break; + } + } + // Configure the module via websocket + if (moduleHasCap) m.websockets.SendText("ConfigureFramerate=" + poseConfiguration.ToJson()); + } + return ok; + } + + public Pose GetPose(Guid trackableOrAnchorUUID, ModeWorldAnalysis mode) + { + Pose result = new Pose(); + foreach (var item in modules) + { + // todo: get the pose via websocket + item.websockets.SendText("GetPose=" + trackableOrAnchorUUID + "=" + mode.ToString()); + + // todo: find the pose with the best confidence? + // How to get the results !?!?! (List with results, per Modules) + } + return result; + } + + public void SubscribeToPose(SubscriptionInfo info, Guid parentTrackableUUID) + { + // Send to all modules a request of subscription + foreach (var item in modules) + { + if (parentTrackableUUID == Guid.Empty) item.websockets.SendText("SubscribePose=" + info.uuidSub + "=" + info.subscription.ToJson()); + else item.websockets.SendText("SubscribeSimulatedPose=" + info.uuidSub + "=" + info.subscription.ToJson() + "=" + parentTrackableUUID.ToString()); + } + } + + public void UnsubscribeFromPose(Guid subscriptionUUID) + { + // Send to all modules a request of subscription + foreach (var item in modules) + { + item.websockets.SendText("UnsubscribePose=" + subscriptionUUID.ToString()); + } + } + + public void SendPoseToClients(Pose pose) + { + // Send to all clients with valid subscription the new pose + foreach (var item in clients) + { + item.websockets.SendText("NewPose=" + pose.ToJson()); + } + } + + public void SendPoseToClients(string str_pose) + { + // Send to all clients with valid subscription the new pose + foreach (var item in clients) + { + item.websockets.SendText("NewPose=" + str_pose); + } + } + } +} +#pragma warning restore CS1591 // Fehlendes XML-Kommentar für öffentlich sichtbaren Typ oder Element diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/Program.cs b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/Program.cs new file mode 100644 index 0000000000000000000000000000000000000000..1fba73140c56dfc242fd22c8b795c3bf7a51801f --- /dev/null +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/Program.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace ETSI.ARF.OpenAPI.WorldAnalysis +{ + /// + /// Program + /// + public class Program + { + /// + /// Main + /// + /// + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + /// + /// Create the host builder. + /// + /// + /// IHostBuilder + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup() + .UseUrls("http://0.0.0.0:44301/"); // SylR: Wichtig!!! + }); + } +} diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/Startup.cs b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/Startup.cs new file mode 100644 index 0000000000000000000000000000000000000000..2a3f67c8c81faf23840f8f5e81c90a997d5cb9ba --- /dev/null +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/Startup.cs @@ -0,0 +1,213 @@ +// +// ARF - Augmented Reality Framework (ETSI ISG ARF) +// +// Copyright 2024 ETSI +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Last change: September 2024 +// + +/* +*World Analysis API + * + * API ensuring interoperability between Scene Management and a World Analysis service + * + * The version of the OpenAPI document: 2.0.2 + * + * Generated by: https://openapi-generator.tech + */ + +using System; +using System.IO; +using System.Reflection; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.OpenApi.Models; + +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; + +using ETSI.ARF.OpenAPI.WorldAnalysis.Authentication; +using ETSI.ARF.OpenAPI.WorldAnalysis.Filters; +using ETSI.ARF.OpenAPI.WorldAnalysis.OpenApi; +using ETSI.ARF.OpenAPI.WorldAnalysis.Formatters; + +//using MongoDB.Bson.Serialization.Serializers; +//using MongoDB.Bson.Serialization; +//using MongoDB.Bson; + +namespace ETSI.ARF.OpenAPI.WorldAnalysis +{ + /// + /// Startup + /// + public class Startup + { + // + // SylR, Fraunhofer HHI + // + /// + /// The API version. (how to read it from the yaml?) + /// + static public string apiVersion = "2.0.0"; + + /// + /// Demo access key + /// + static public string accessKey = "ARF"; + + /// + /// Demo secret key + /// + //static public string secretKey = "GW0Wae1t4Cs5rAqEbPYFWO9J5nSbpJXxp1F3uv0J"; + static public string secretKey = "hhi"; + + /// + /// Constructor + /// + /// + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + /// + /// The application configuration. + /// + public IConfiguration Configuration { get; } + + /// + /// SylR: Check if the request is authorized + /// + /// + /// + static public bool IsAccessGranted(string token) + { + if (token == null) return false; + else if (token == "dev") return true; // developermode + + string[] t = token.Split('&'); + return t[0] == accessKey && t[1] == secretKey; + } + + /// + /// This method gets called by the runtime. Use this method to add services to the container. + /// + /// + public void ConfigureServices(IServiceCollection services) + { + + // Add framework services. + services + // Don't need the full MVC stack for an API, see https://andrewlock.net/comparing-startup-between-the-asp-net-core-3-templates/ + .AddControllers(options => { + options.InputFormatters.Insert(0, new InputFormatterStream()); + }) + .AddNewtonsoftJson(opts => + { + opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + opts.SerializerSettings.Converters.Add(new StringEnumConverter + { + NamingStrategy = new CamelCaseNamingStrategy() + }); + }); + services + .AddSwaggerGen(c => + { + c.EnableAnnotations(enableAnnotationsForInheritance: true, enableAnnotationsForPolymorphism: true); + + c.SwaggerDoc(apiVersion, new OpenApiInfo + { + Title = "World Analysis API", + Description = "World Analysis API (ASP.NET Core 5.0)", + TermsOfService = new Uri("https://github.com/openapitools/openapi-generator"), + Contact = new OpenApiContact + { + Name = "OpenAPI-Generator Contributors", + Url = new Uri("https://github.com/openapitools/openapi-generator"), + Email = "" + }, + License = new OpenApiLicense + { + Name = "NoLicense", + Url = new Uri("https://opensource.org/licenses/BSD-3-Clause") + }, + Version = apiVersion, + }); + c.CustomSchemaIds(type => type.FriendlyId(true)); + c.IncludeXmlComments($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}{Assembly.GetEntryAssembly().GetName().Name}.xml"); + + // Include DataAnnotation attributes on Controller Action parameters as OpenAPI validation rules (e.g required, pattern, ..) + // Use [ValidateModelState] on Actions to actually validate it in C# as well! + c.OperationFilter(); + }); + services + .AddSwaggerGenNewtonsoftSupport(); + } + + /// + /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + /// + /// + /// + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + // ETSI-ARF Websockets implementation + var webSocketOptions = new WebSocketOptions() + { + //KeepAliveInterval = TimeSpan.FromSeconds(120), + //AllowedOrigins.Add("https://etsi.hhi.fraunhofer.de"), + //AllowedOrigins.Add("https://www.client.com") + }; + app.UseWebSockets(webSocketOptions); + //app.UseWebSockets(); + + app.UseHttpsRedirection(); + app.UseDefaultFiles(); + app.UseStaticFiles(); + app.UseSwagger(c => + { + c.RouteTemplate = "openapi/{documentName}/openapi.json"; + }) + .UseSwaggerUI(c => + { + // set route prefix to openapi, e.g. http://localhost:8080/openapi/index.html + c.RoutePrefix = "openapi"; + //TODO: Either use the SwaggerGen generated OpenAPI contract (generated from C# classes) + c.SwaggerEndpoint("/openapi/" + apiVersion + "/openapi.json", "World Analysis API"); + + //TODO: Or alternatively use the original OpenAPI contract that's included in the static files + // c.SwaggerEndpoint("/openapi-original.json", "World Analysis API Original"); + }); + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/appsettings.Development.json b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/appsettings.Development.json new file mode 100644 index 0000000000000000000000000000000000000000..e203e9407e74a6b9662aab8fde5d73ae64665f18 --- /dev/null +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/appsettings.json b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/appsettings.json new file mode 100644 index 0000000000000000000000000000000000000000..def9159a7d9403c04a926f64e71ef3ee7c9e4c57 --- /dev/null +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/wwwroot/ws.html b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/wwwroot/ws.html new file mode 100644 index 0000000000000000000000000000000000000000..cbc7959284563fd214e85b696773559cc48d3902 --- /dev/null +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/wwwroot/ws.html @@ -0,0 +1,174 @@ + + + + + + + + +

ETSI ARF - WebSocket Sample Application

+

Developped for ISG STF 669

+

(C) ETSI 2024

+ +

Test Connection

+ +

Ready to connect...

+
+ + + +
+

+
+ + + + +
+ +

Communication Log

+ + + + + + + + + + +
FromToData
+ + + + \ No newline at end of file