├── Robot ├── src │ ├── orchestrated_robot │ │ ├── __init__.py │ │ ├── scripts │ │ │ ├── robot_status.py │ │ │ ├── job_status.py │ │ │ ├── jobs_handler.py │ │ │ ├── fleet_manager_client.py │ │ │ ├── move_base_client.py │ │ │ └── telemetry_controller.py │ │ ├── setup.py │ │ ├── relay │ │ │ ├── iot_relay.conf │ │ │ └── relay.launch │ │ ├── launch │ │ │ └── robot.launch │ │ └── package.xml │ ├── orchestrator_msgs │ │ ├── msg │ │ │ ├── Position.msg │ │ │ ├── Job.msg │ │ │ └── Telemetry.msg │ │ ├── srv │ │ │ └── RobotInfo.srv │ │ └── package.xml │ ├── argos_map │ │ └── map │ │ │ └── map-simple.png │ └── argos_bridge │ │ └── argos_worlds │ │ └── construct.argos ├── Dockerfile-simulator └── Dockerfile-robot ├── ui ├── tsconfig.prod.json ├── images.d.ts ├── public │ ├── favicon.ico │ ├── map-simple.png │ ├── configuration.json │ ├── manifest.json │ └── index.html ├── tsconfig.test.json ├── src │ ├── index.css │ ├── RobotStatus.tsx │ ├── JobStatus.tsx │ ├── OrderStatus.tsx │ ├── Robot.tsx │ ├── EmptyTable.tsx │ ├── Position2d.tsx │ ├── Pages.tsx │ ├── App.test.tsx │ ├── RobotTelemetry.tsx │ ├── Job.tsx │ ├── index.tsx │ ├── Order.tsx │ ├── RobotInfoCollapse.tsx │ ├── RobotInfoPanel.tsx │ ├── OrderManagerClient.tsx │ ├── RobotManagerClient.tsx │ ├── App.css │ ├── RobotView.tsx │ ├── Configuration.tsx │ ├── App.tsx │ └── OrderTable.tsx ├── tslint.json ├── .gitignore ├── tsconfig.json ├── web.config ├── azure-pipelines.yml └── package.json ├── images ├── UiMap.png ├── EditTasks.png ├── MainMap.png ├── UiOrders.png ├── DeployTask.PNG ├── CreatePipeline.png ├── JsonSubstitution.png ├── ReleaseVariables.PNG ├── ReferenceYamlFile.PNG └── OrchestratorArchitecture.png ├── .gitattributes ├── RobotOrchestrator.Dispatcher ├── appsettings.json ├── appsettings.Development.json ├── IDispatcher.cs ├── IotHubServiceClientOptions.cs ├── appsettings.Local.json ├── IIotHubServiceClient.cs ├── RobotOrchestrator.Dispatcher.csproj ├── IotHubServiceClient.cs ├── Program.cs ├── azure-pipelines.yml ├── Dispatcher.cs ├── Controllers │ └── JobsController.cs └── Startup.cs ├── RobotOrchestrator.FleetManager ├── appsettings.Development.json ├── IotHubRegistryClientOptions.cs ├── appsettings.local.json ├── IIotHubRegistryClient.cs ├── RobotConfig.cs ├── ITelemetryHandler.cs ├── appsettings.json ├── IFleetManager.cs ├── RobotOrchestrator.FleetManager.csproj ├── Program.cs ├── azure-pipelines.yml ├── TelemetryHandler.cs ├── Controllers │ └── TelemetryController.cs ├── IotHubRegistryClient.cs ├── TelemetryEventProcessor.cs └── FleetManager.cs ├── RobotOrchestrator.OrderManager ├── appsettings.Development.json ├── FleetManagerClientOptions.cs ├── IDispatcherClient.cs ├── appsettings.local.json ├── IFleetManagerClient.cs ├── IJobMessageHandler.cs ├── appsettings.json ├── IOrderManager.cs ├── OrderAssignment.cs ├── RobotOrchestrator.OrderManager.csproj ├── DispatcherClient.cs ├── Program.cs ├── azure-pipelines.yml ├── FleetManagerClient.cs ├── Controllers │ └── OrdersController.cs ├── JobEventProcessor.cs ├── JobMessageHandler.cs └── Startup.cs ├── RobotOrchestrator.OrderProducer ├── appsettings.Development.json ├── appsettings.json ├── IOrderHandler.cs ├── appsettings.local.json ├── IBatchManager.cs ├── IOrderFactory.cs ├── IOrderManagerClient.cs ├── BatchJobOptions.cs ├── RobotOrchestrator.OrderProducer.csproj ├── Controllers │ ├── OrdersController.cs │ └── OrdersProducerController.cs ├── azure-pipelines.yml ├── OrderHandler.cs ├── Program.cs ├── OrderManagerClient.cs ├── BatchManager.cs ├── OrderFactory.cs ├── Startup.cs └── BatchJob.cs ├── Provisioning ├── azuredeploy.parameters.json ├── keyvault.parameters.json └── keyvault.json ├── helm └── ros-orchestrator │ ├── Chart.yaml │ ├── templates │ ├── service.yaml │ └── deployment.yaml │ └── values.yaml ├── RobotOrchestrator ├── RosMessageType.cs ├── RobotStatus.cs ├── OrderStatus.cs ├── JobStatus.cs ├── Position.cs ├── Robot.cs ├── RosMessage.cs ├── IEventProcessorHostConfig.cs ├── RecordNotFoundException.cs ├── IQueryHelper.cs ├── EventProcessorHostConfig.cs ├── IotHubEventProcessorFactory.cs ├── RobotOrchestrator.csproj ├── RobotTelemetry.cs ├── CosmosDbOptions.cs ├── ICosmosDbClient.cs ├── Job.cs ├── Order.cs ├── QueryHelper.cs └── IotHubListener.cs ├── README.md ├── RobotOrchestrator.Tests └── RobotOrchestrator.Tests.csproj ├── RobotOrchestrator.Dispatcher.Tests └── RobotOrchestrator.Dispatcher.Tests.csproj ├── RobotOrchestrator.FleetManager.Tests ├── RobotOrchestrator.FleetManager.Tests.csproj ├── IotHubRegistryClientTest.cs └── FleetManagerTests.cs ├── RobotOrchestrator.OrderManager.Tests └── RobotOrchestrator.OrderManager.Tests.csproj ├── RobotOrchestrator.OrderProducer.Tests ├── RobotOrchestrator.OrderProducer.Tests.csproj ├── OrdersControllerTests.cs ├── OrdersProducerControllerTests.cs ├── OrderFactoryTests.cs └── BatchJobTests.cs ├── Docs ├── InstallRequirements.md ├── SendTestOrders.md ├── CICD.md ├── RunLocally.md └── Provisioning.md ├── LICENSE ├── cgmanifest.json ├── CONTRIBUTING.md ├── azure-pipelines.yml └── docker-compose.yml /Robot/src/orchestrated_robot/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } -------------------------------------------------------------------------------- /Robot/src/orchestrator_msgs/msg/Position.msg: -------------------------------------------------------------------------------- 1 | float64 X 2 | float64 Y -------------------------------------------------------------------------------- /images/UiMap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Ros-Orchestration/HEAD/images/UiMap.png -------------------------------------------------------------------------------- /ui/images.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' 2 | declare module '*.png' 3 | declare module '*.jpg' 4 | -------------------------------------------------------------------------------- /images/EditTasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Ros-Orchestration/HEAD/images/EditTasks.png -------------------------------------------------------------------------------- /images/MainMap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Ros-Orchestration/HEAD/images/MainMap.png -------------------------------------------------------------------------------- /images/UiOrders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Ros-Orchestration/HEAD/images/UiOrders.png -------------------------------------------------------------------------------- /images/DeployTask.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Ros-Orchestration/HEAD/images/DeployTask.PNG -------------------------------------------------------------------------------- /ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Ros-Orchestration/HEAD/ui/public/favicon.ico -------------------------------------------------------------------------------- /Robot/Dockerfile-simulator: -------------------------------------------------------------------------------- 1 | FROM simulator 2 | 3 | COPY src/argos_bridge/ /root/catkin_ws/src/argos_bridge 4 | -------------------------------------------------------------------------------- /images/CreatePipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Ros-Orchestration/HEAD/images/CreatePipeline.png -------------------------------------------------------------------------------- /ui/public/map-simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Ros-Orchestration/HEAD/ui/public/map-simple.png -------------------------------------------------------------------------------- /images/JsonSubstitution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Ros-Orchestration/HEAD/images/JsonSubstitution.png -------------------------------------------------------------------------------- /images/ReleaseVariables.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Ros-Orchestration/HEAD/images/ReleaseVariables.PNG -------------------------------------------------------------------------------- /images/ReferenceYamlFile.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Ros-Orchestration/HEAD/images/ReferenceYamlFile.PNG -------------------------------------------------------------------------------- /ui/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /images/OrchestratorArchitecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Ros-Orchestration/HEAD/images/OrchestratorArchitecture.png -------------------------------------------------------------------------------- /Robot/src/argos_map/map/map-simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/Ros-Orchestration/HEAD/Robot/src/argos_map/map/map-simple.png -------------------------------------------------------------------------------- /Robot/src/orchestrator_msgs/msg/Job.msg: -------------------------------------------------------------------------------- 1 | string id 2 | string RobotId 3 | string OrderId 4 | string Status 5 | Position StartPosition 6 | Position EndPosition 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Do not convert line endings for shell scripts 2 | *.sh -crlf 3 | 4 | # Do not convert line endings for ros /usr/bin/env python scripts 5 | *.py -crlf -------------------------------------------------------------------------------- /Robot/src/orchestrator_msgs/msg/Telemetry.msg: -------------------------------------------------------------------------------- 1 | string id 2 | string CreatedDateTime 3 | Position Position 4 | string Status 5 | string OrderId 6 | string RobotId 7 | -------------------------------------------------------------------------------- /Robot/src/orchestrator_msgs/srv/RobotInfo.srv: -------------------------------------------------------------------------------- 1 | #request constants 2 | #request fields 3 | --- 4 | #response fields 5 | string Status 6 | string OrderId 7 | string RobotId -------------------------------------------------------------------------------- /RobotOrchestrator.Dispatcher/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /ui/public/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "REACT_APP_FLEETMANAGER_URL": "robot-fleetmanager-demo.azurewebsites.net", 3 | "REACT_APP_ORDERMANAGER_URL": "robot-ordermanager-demo.azurewebsites.net" 4 | } -------------------------------------------------------------------------------- /ui/src/index.css: -------------------------------------------------------------------------------- 1 | /* Copyright (c) Microsoft Corporation. All rights reserved. 2 | Licensed under the MIT License. */ 3 | 4 | body { 5 | margin: 0; 6 | padding: 0; 7 | font-family: sans-serif; 8 | } 9 | -------------------------------------------------------------------------------- /Robot/src/orchestrated_robot/scripts/robot_status.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | class RobotStatus(Enum): 4 | Onboarding = "Onboarding" 5 | Idle = "Idle" 6 | Busy = "Busy" 7 | Failed = "Failed" -------------------------------------------------------------------------------- /Robot/src/orchestrated_robot/scripts/job_status.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | class JobStatus(Enum): 4 | Queued = "Queued" 5 | InProgress = "InProgress" 6 | Complete = "Complete" 7 | Failed = "Failed" -------------------------------------------------------------------------------- /RobotOrchestrator.Dispatcher/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*", 8 | "OrderManagerUrl": "https://localhost:44307/api/v1/Orders" 9 | } 10 | -------------------------------------------------------------------------------- /Robot/src/orchestrated_robot/setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | from catkin_pkg.python_setup import generate_distutils_setup 3 | 4 | d = generate_distutils_setup( 5 | packages=[''], 6 | package_dir={'': 'scripts'} 7 | ) 8 | 9 | setup(**d) -------------------------------------------------------------------------------- /ui/src/RobotStatus.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | enum RobotStatus { 5 | Onboarding, 6 | Idle, 7 | Busy, 8 | Failed 9 | } 10 | 11 | export default RobotStatus; -------------------------------------------------------------------------------- /Provisioning/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "envName": { 6 | "value": "dev" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /helm/ros-orchestrator/Chart.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT License. 3 | 4 | apiVersion: v1 5 | appVersion: "1.0" 6 | description: A Helm chart for Kubernetes 7 | name: ros-simulation 8 | version: 0.1.0 9 | -------------------------------------------------------------------------------- /ui/src/JobStatus.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | enum JobStatus 5 | { 6 | Queued, 7 | InProgress, 8 | Complete, 9 | Failed 10 | } 11 | 12 | export default JobStatus; -------------------------------------------------------------------------------- /Provisioning/keyvault.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "envName": { 6 | "value": "dev" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /ui/src/OrderStatus.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | enum OrderStatus { 5 | New, 6 | Received, 7 | InProgress, 8 | Complete, 9 | Failed 10 | } 11 | 12 | export default OrderStatus; -------------------------------------------------------------------------------- /RobotOrchestrator/RosMessageType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace RobotOrchestrator 5 | { 6 | public enum RosMessageType 7 | { 8 | Job, 9 | Telemetry 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ui/src/Robot.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import RobotTelemetry from './RobotTelemetry'; 5 | 6 | class Robot 7 | { 8 | public id: string; 9 | 10 | public telemetry : RobotTelemetry; 11 | } 12 | 13 | export default Robot; -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/IOrderHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace RobotOrchestrator.OrderProducer 5 | { 6 | public interface IOrderHandler 7 | { 8 | void HandleBatch(int batchSize); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /RobotOrchestrator/RobotStatus.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace RobotOrchestrator 5 | { 6 | public enum RobotStatus 7 | { 8 | Onboarding, 9 | Idle, 10 | Busy, 11 | Failed 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ui/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": false 4 | }, 5 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], 6 | "linterOptions": { 7 | "exclude": [ 8 | "config/**/*.js", 9 | "node_modules/**/*.ts", 10 | "coverage/lcov-report/*.js" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /RobotOrchestrator.Dispatcher/IDispatcher.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Threading.Tasks; 5 | 6 | namespace RobotOrchestrator.Dispatcher 7 | { 8 | public interface IDispatcher 9 | { 10 | Task SendJobAsync(Job job); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RobotOrchestrator/OrderStatus.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace RobotOrchestrator 5 | { 6 | public enum OrderStatus 7 | { 8 | New, 9 | Received, 10 | InProgress, 11 | Complete, 12 | Failed 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ui/src/EmptyTable.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as React from 'react'; 5 | 6 | class EmptyRowsView extends React.Component 7 | { 8 | public render() 9 | { 10 | return (
Empty Table
); 11 | } 12 | } 13 | export default EmptyRowsView; -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/FleetManagerClientOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace RobotOrchestrator.OrderManager 5 | { 6 | public class FleetManagerClientOptions 7 | { 8 | public string FleetManagerUrl { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /RobotOrchestrator.Dispatcher/IotHubServiceClientOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace RobotOrchestrator.Dispatcher 5 | { 6 | public class IotHubServiceClientOptions 7 | { 8 | public string IotHubServiceConnectionString { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/IotHubRegistryClientOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace RobotOrchestrator.FleetManager 5 | { 6 | public class IotHubRegistryClientOptions 7 | { 8 | public string IotHubRegistryConnectionString { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/IDispatcherClient.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Threading.Tasks; 5 | 6 | namespace RobotOrchestrator.OrderManager 7 | { 8 | public interface IDispatcherClient 9 | { 10 | Task SendJobAsync(Job job); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ui/src/Position2d.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | class Position2d 5 | { 6 | public x : number; 7 | public y : number; 8 | 9 | constructor(x : number, y : number) 10 | { 11 | this.x = x; 12 | this.y = y; 13 | } 14 | } 15 | 16 | export default Position2d; -------------------------------------------------------------------------------- /Robot/src/orchestrated_robot/relay/iot_relay.conf: -------------------------------------------------------------------------------- 1 | - { 2 | topic: 'jobs', 3 | msg_type: 'orchestrator_msgs.msg:Job', 4 | relay_mode: 1 5 | } 6 | - { 7 | topic: 'jobsstatus', 8 | msg_type: 'orchestrator_msgs.msg:Job', 9 | relay_mode: 2 10 | } 11 | - { 12 | topic: 'telemetry', 13 | msg_type: 'orchestrator_msgs.msg:Telemetry', 14 | relay_mode: 2 15 | } -------------------------------------------------------------------------------- /ui/src/Pages.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as React from 'react'; 5 | import OrderTable from './OrderTable'; 6 | import RobotView from './RobotView'; 7 | 8 | export const Visualization = () => ( 9 | 10 | ) 11 | 12 | export const Orders = () => ( 13 | 14 | ) 15 | -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /RobotOrchestrator.Dispatcher/appsettings.Local.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | }, 9 | "Vault": "robot-orch-kv-dev", 10 | "ClientId": "19a15ab9-1db4-4875-9663-22a0e0dfbd1e", 11 | "ClientSecret": "027eeb80-8339-4e26-8a36-3dfd8fb3838b" 12 | } 13 | -------------------------------------------------------------------------------- /ui/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /RobotOrchestrator.Dispatcher/IIotHubServiceClient.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Threading.Tasks; 5 | 6 | namespace RobotOrchestrator.Dispatcher 7 | { 8 | public interface IIotHubServiceClient 9 | { 10 | Task SendMessageFromCloudToDeviceAsync(string deviceId, string message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/appsettings.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | }, 9 | "Vault": "robot-orch-kv-dev", 10 | "ClientId": "19a15ab9-1db4-4875-9663-22a0e0dfbd1e", 11 | "ClientSecret": "027eeb80-8339-4e26-8a36-3dfd8fb3838b" 12 | } 13 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/appsettings.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | }, 9 | "Vault": "robot-orch-kv-dev", 10 | "ClientId": "19a15ab9-1db4-4875-9663-22a0e0dfbd1e", 11 | "ClientSecret": "027eeb80-8339-4e26-8a36-3dfd8fb3838b" 12 | } 13 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/appsettings.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | }, 9 | "Vault": "robot-orch-kv-dev", 10 | "ClientId": "19a15ab9-1db4-4875-9663-22a0e0dfbd1e", 11 | "ClientSecret": "027eeb80-8339-4e26-8a36-3dfd8fb3838b" 12 | } 13 | -------------------------------------------------------------------------------- /RobotOrchestrator/JobStatus.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace RobotOrchestrator 9 | { 10 | public enum JobStatus 11 | { 12 | Queued, 13 | InProgress, 14 | Complete, 15 | Failed 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/IIotHubRegistryClient.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Threading.Tasks; 5 | 6 | namespace RobotOrchestrator.FleetManager 7 | { 8 | public interface IIotHubRegistryClient 9 | { 10 | Task AddDeviceAsync(string deviceId); 11 | 12 | Task DeleteDeviceAsync(string deviceId); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /RobotOrchestrator/Position.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace RobotOrchestrator 5 | { 6 | public class Position 7 | { 8 | public Position(double x, double y) 9 | { 10 | X = x; 11 | Y = y; 12 | } 13 | 14 | public double X { get; set; } 15 | public double Y { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/IBatchManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | 6 | namespace RobotOrchestrator.OrderProducer 7 | { 8 | public interface IBatchManager 9 | { 10 | void StartBatchJob(Action handler, BatchJobOptions options); 11 | 12 | void StopBatchJob(); 13 | 14 | bool HasActiveBatchJob(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /RobotOrchestrator/Robot.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Converters; 6 | 7 | namespace RobotOrchestrator 8 | { 9 | public class Robot 10 | { 11 | [JsonProperty(PropertyName ="id")] 12 | public string DeviceId { get; set; } 13 | 14 | public RobotTelemetry Telemetry { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/IOrderFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace RobotOrchestrator.OrderProducer 7 | { 8 | public interface IOrderFactory 9 | { 10 | Order CreateOrder(string message = null); 11 | 12 | IEnumerable CreateOrders(int numOfOrders, string message = null); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/IFleetManagerClient.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | 7 | namespace RobotOrchestrator.OrderManager 8 | { 9 | public interface IFleetManagerClient 10 | { 11 | Task GetRobot(string id); 12 | 13 | Task> GetAvailableRobotsAsync(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/RobotConfig.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace RobotOrchestrator.FleetManager 10 | { 11 | public class RobotConfig 12 | { 13 | public string DeviceId { get; set; } 14 | 15 | public string IoTHubConnectionString { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/IJobMessageHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace RobotOrchestrator.OrderManager 10 | { 11 | public interface IJobMessageHandler 12 | { 13 | Task UpdateOrdersAsync(IEnumerable jobs); 14 | 15 | Task UpdateOrderAsync(Job job); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ui/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as React from 'react'; 5 | import * as ReactDOM from 'react-dom'; 6 | import { HashRouter } from 'react-router-dom'; 7 | import App from './App'; 8 | 9 | it('renders without crashing', () => { 10 | const div = document.createElement('div'); 11 | ReactDOM.render( 12 | 13 | , div); 14 | ReactDOM.unmountComponentAtNode(div); 15 | }); 16 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/IOrderManagerClient.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Net.Http; 7 | using System.Threading.Tasks; 8 | 9 | namespace RobotOrchestrator.OrderProducer 10 | { 11 | public interface IOrderManagerClient 12 | { 13 | Task SendOrderAsync(Order order); 14 | 15 | Task SendOrdersAsync(List orders); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ui/src/RobotTelemetry.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import Position2d from './Position2d'; 5 | import RobotStatus from './RobotStatus'; 6 | 7 | class RobotTelemetry{ 8 | public id : string; 9 | 10 | public createdDateTime : Date; 11 | 12 | public position : Position2d; 13 | 14 | public status : RobotStatus; 15 | 16 | public orderId : string; 17 | 18 | public robotId : string; 19 | } 20 | 21 | export default RobotTelemetry; -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/ITelemetryHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | 7 | namespace RobotOrchestrator.FleetManager 8 | { 9 | public interface ITelemetryHandler 10 | { 11 | Task InsertTelemetryAsync(IEnumerable robotTelemetry); 12 | 13 | Task> GetLatestTelemetriesAsync(string robotId, int? n); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ui/src/Job.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { Guid } from "guid-typescript"; 5 | import JobStatus from "./JobStatus"; 6 | import Position2d from "./Position2d"; 7 | 8 | class Job { 9 | public Id : Guid; 10 | 11 | public RobotId : string; 12 | 13 | public OrderId : Guid; 14 | 15 | public Status : JobStatus; 16 | 17 | public StartPosition : Position2d; 18 | public EndPosition : Position2d; 19 | } 20 | 21 | export default Job; 22 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*", 8 | "DispatcherUrl": "https://localhost:44321/api/v1/Jobs", 9 | "FleetManagerUrl": "https://localhost:44301/api/v1/Robots", 10 | "OrderManagerEventHubConsumerGroup": "ordermanagercg", 11 | "OrderManagerEventHubPath": "ordermanager", 12 | "Order": { 13 | "DbName": "Order", 14 | "DbCollectionName": "OrderCollection", 15 | "PartitionName": "/id" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ui/src/index.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as React from 'react'; 5 | import * as ReactDOM from 'react-dom'; 6 | import { HashRouter } from 'react-router-dom'; 7 | import App from './App'; 8 | import './index.css'; 9 | import registerServiceWorker from './registerServiceWorker'; 10 | 11 | 12 | ReactDOM.render( 13 | 14 | 15 | , 16 | document.getElementById('root') as HTMLElement 17 | ); 18 | registerServiceWorker(); 19 | -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*", 8 | "FleetManagerEventHubConsumerGroup": "fleetmanagercg", 9 | "FleetManagerEventHubPath": "fleetmanager", 10 | "Robot": { 11 | "DbName": "Robot", 12 | "DbCollectionName": "RobotCollection", 13 | "PartitionName": "/id" 14 | }, 15 | "Telemetry": { 16 | "DbName": "Telemetry", 17 | "DbCollectionName": "TelemetryCollection", 18 | "PartitionName": "/RobotId" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /RobotOrchestrator/RosMessage.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Newtonsoft.Json; 5 | 6 | namespace RobotOrchestrator 7 | { 8 | public class RosMessage 9 | { 10 | [JsonProperty(PropertyName = "topic")] 11 | public string Topic { get; set; } 12 | 13 | [JsonProperty(PropertyName = "msg_type")] 14 | public string MessageType { get; set; } 15 | 16 | [JsonProperty(PropertyName = "payload")] 17 | public T Payload { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Robot Orchestrator 2 | 3 | This project provides a sample robot orchestrator that can run in the cloud. 4 | 5 | MainMap 6 | 7 | For more information, see the following docs: 8 | 9 | - [Architecture](./Docs/Architecture.md) 10 | - [Install Requirements](./Docs/InstallRequirements.md) 11 | - [Provisioning](./Docs/Provisioning.md) 12 | - [Deploy Robot And Simulator](./Docs/DeployRobotAndSimulator.md) 13 | - [CI/CD](./Docs/CICD.md) 14 | - [Run Locally](./Docs/RunLocally.md) 15 | - [Send Test Orders](./Docs/SendTestOrders.md) 16 | -------------------------------------------------------------------------------- /RobotOrchestrator/IEventProcessorHostConfig.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace RobotOrchestrator 5 | { 6 | public interface IEventProcessorHostConfig 7 | { 8 | string ConsumerGroupName { get; set; } 9 | 10 | string EventHubConnectionString { get; set; } 11 | 12 | string EventHubPath { get; set; } 13 | 14 | string HostName { get; } 15 | 16 | string LeaseContainerName { get; set; } 17 | 18 | string StorageConnectionString { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ui/src/Order.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { Guid } from "guid-typescript"; 5 | import Job from "./Job"; 6 | import OrderStatus from "./OrderStatus"; 7 | import Position2d from "./Position2d"; 8 | 9 | class Order 10 | { 11 | public id: Guid; 12 | 13 | public startPosition : Position2d; 14 | public endPosition : Position2d; 15 | 16 | public status : OrderStatus; 17 | 18 | public jobs : Job[]; 19 | 20 | public createdDateTime : Date; 21 | 22 | public message : string; 23 | } 24 | 25 | export default Order; -------------------------------------------------------------------------------- /RobotOrchestrator/RecordNotFoundException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | 6 | namespace RobotOrchestrator 7 | { 8 | public class RecordNotFoundException : Exception 9 | { 10 | public RecordNotFoundException() 11 | { 12 | } 13 | 14 | public RecordNotFoundException(string message) 15 | : base(message) 16 | { 17 | } 18 | 19 | public RecordNotFoundException(string message, Exception inner) 20 | : base(message, inner) 21 | { 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /RobotOrchestrator/IQueryHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq.Expressions; 7 | using System.Threading.Tasks; 8 | 9 | namespace RobotOrchestrator 10 | { 11 | public interface IQueryHelper 12 | { 13 | QueryHelper ByExpression(Expression> expression); 14 | 15 | QueryHelper OrderDescending(Expression> orderDescendingExpression); 16 | 17 | QueryHelper Take(int n); 18 | 19 | Task> GetQueryItemsAsync(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/IOrderManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | 8 | namespace RobotOrchestrator.OrderManager 9 | { 10 | public interface IOrderManager 11 | { 12 | Task> AcceptOrdersAsync(IEnumerable orders); 13 | 14 | Task AcceptOrderAsync(Order order, string robotId = null); 15 | 16 | Task> GetOrdersAsync(OrderStatus? status, int? numOrders); 17 | 18 | Task GetOrderAsync(Guid id); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Robot/src/orchestrated_robot/relay/relay.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | connection_string: $(arg connection_string) 7 | prefs_storage_file: '$(find orchestrated_robot)/relay/iot_relay.conf' 8 | timeout: 24100 9 | minimum_polling_time: 9 10 | message_timeout: 10000 11 | iot_hub_relay_queue_size: 10 12 | device_method_success_code: 200 13 | device_method_failure_code: 500 14 | 15 | 16 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/OrderAssignment.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace RobotOrchestrator.OrderManager 10 | { 11 | public class OrderAssignment 12 | { 13 | public OrderAssignment(Order order, Robot robot) 14 | { 15 | Robot = robot; 16 | Order = order; 17 | } 18 | 19 | public Order Order { get; set; } 20 | 21 | public Robot Robot { get; set; } 22 | 23 | public bool IsAssigned => Robot != null; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /RobotOrchestrator/EventProcessorHostConfig.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | 6 | namespace RobotOrchestrator 7 | { 8 | public class EventProcessorHostConfig : IEventProcessorHostConfig 9 | { 10 | public string HostName { get; } = Guid.NewGuid().ToString(); 11 | 12 | public string EventHubConnectionString { get; set; } 13 | 14 | public string EventHubPath { get; set; } 15 | 16 | public string ConsumerGroupName { get; set; } 17 | 18 | public string StorageConnectionString { get; set; } 19 | 20 | public string LeaseContainerName { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /RobotOrchestrator/IotHubEventProcessorFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.Azure.EventHubs.Processor; 5 | 6 | namespace RobotOrchestrator 7 | { 8 | public class IotHubEventProcessorFactory : IEventProcessorFactory 9 | { 10 | private readonly IEventProcessor eventProcessor; 11 | 12 | public IotHubEventProcessorFactory(IEventProcessor eventProcessor) 13 | { 14 | this.eventProcessor = eventProcessor; 15 | } 16 | 17 | public IEventProcessor CreateEventProcessor(PartitionContext context) 18 | { 19 | return eventProcessor; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/BatchJobOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | 6 | namespace RobotOrchestrator.OrderProducer 7 | { 8 | public class BatchJobOptions 9 | { 10 | // Set to -1 for infinite items 11 | public int MaxItems { get; set; } = 10; 12 | 13 | public int BatchSize { get; set; } = 1; 14 | 15 | public int DelayInSecs { get; set; } = 5; 16 | 17 | public void Validate() 18 | { 19 | if (MaxItems < -1 || MaxItems == 0 || BatchSize <= 0 || DelayInSecs < 0) 20 | { 21 | throw new ArgumentException("BatchJobOptions are not valid"); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "build/dist", 5 | "module": "esnext", 6 | "target": "es5", 7 | "lib": ["es6", "dom"], 8 | "sourceMap": true, 9 | "allowJs": true, 10 | "jsx": "react", 11 | "moduleResolution": "node", 12 | "rootDir": "src", 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "noUnusedLocals": true 20 | }, 21 | "exclude": [ 22 | "node_modules", 23 | "build", 24 | "scripts", 25 | "acceptance-tests", 26 | "webpack", 27 | "jest", 28 | "src/setupTests.ts" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /RobotOrchestrator/RobotOrchestrator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /RobotOrchestrator/RobotTelemetry.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Converters; 7 | 8 | namespace RobotOrchestrator 9 | { 10 | public class RobotTelemetry 11 | { 12 | [JsonProperty(PropertyName = "id")] 13 | public string Id { get; set; } 14 | 15 | public DateTime CreatedDateTime { get; set; } = DateTime.UtcNow; 16 | 17 | public Position Position { get; set; } = new Position(0, 0); 18 | 19 | [JsonConverter(typeof(StringEnumConverter))] 20 | public RobotStatus Status { get; set; } = RobotStatus.Onboarding; 21 | 22 | public string OrderId { get; set; } 23 | 24 | public string RobotId { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/RobotOrchestrator.OrderProducer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /RobotOrchestrator/CosmosDbOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace RobotOrchestrator 5 | { 6 | /// 7 | /// Generic T is used to differentiate different options for the CosmosDbClient\ generic implementations. 8 | /// So, if multiple CosmosDbClients for different types were needed, then different options could be created for 9 | /// each IoC configuration. 10 | /// 11 | /// e.g. CosmosDbClient would use CosmosDbOptions 12 | /// 13 | /// 14 | public class CosmosDbOptions 15 | { 16 | public string DbName { get; set; } 17 | public string DbCollectionName { get; set; } 18 | public string PartitionName { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /RobotOrchestrator.Tests/RobotOrchestrator.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /helm/ros-orchestrator/templates/service.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT License. 3 | 4 | {{ $Values := .Values }} 5 | --- 6 | {{ range $i, $e := until (int .Values.robotCount) | default 1 }} 7 | apiVersion: v1 8 | kind: Service 9 | metadata: 10 | name: {{ $Values.simulatorTargetPrefix }}{{ $i }} 11 | spec: 12 | ports: 13 | - port: {{ add $Values.portBase $i }} 14 | protocol: "{{ $Values.nimbroProtocol | upper }}" 15 | selector: 16 | name: simulator 17 | type: ClusterIP 18 | --- 19 | apiVersion: v1 20 | kind: Service 21 | metadata: 22 | name: {{ $Values.robotTargetPrefix }}{{ $i }} 23 | spec: 24 | ports: 25 | - port: {{ int $Values.portBase }} 26 | protocol: "{{ $Values.nimbroProtocol | upper }}" 27 | selector: 28 | name: robot-{{ $i }} 29 | type: ClusterIP 30 | --- 31 | {{ end }} 32 | 33 | -------------------------------------------------------------------------------- /RobotOrchestrator/ICosmosDbClient.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq.Expressions; 7 | using System.Threading.Tasks; 8 | using Microsoft.Azure.Documents; 9 | 10 | namespace RobotOrchestrator 11 | { 12 | public interface ICosmosDbClient 13 | { 14 | Task UpsertItemAsync(T document, PartitionKey partitionKey); 15 | 16 | Task DeleteItemAsync(string id, PartitionKey partitionKey); 17 | 18 | Task UpdateItemAsync(string id, T document, PartitionKey partitionKey); 19 | 20 | Task> GetItemsAsync(Expression> expression = null, Expression> orderByExpression = null, int? n = null); 21 | 22 | Task GetItemAsync(string id, PartitionKey partitionKey); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/RobotOrchestrator.OrderManager.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/IFleetManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | 7 | namespace RobotOrchestrator.FleetManager 8 | { 9 | public interface IFleetManager 10 | { 11 | Task CreateIfNotExistsRobotConnectionAsync(string deviceId); 12 | 13 | Task UpsertRobotAsync(string deviceId); 14 | 15 | Task DeleteRobotAsync(string deviceId); 16 | 17 | Task UpdateRobotAsync(Robot robot); 18 | 19 | Task> GetRobotsAsync(RobotStatus? status); 20 | 21 | Task GetRobotAsync(string deviceId); 22 | 23 | Task InsertTelemetriesAndUpdateRobotsAsync(IEnumerable robotTelemetry); 24 | 25 | Task> GetLatestTelemetriesAsync(string robotId, int? n); 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /RobotOrchestrator.Dispatcher/RobotOrchestrator.Dispatcher.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ui/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /RobotOrchestrator.Dispatcher.Tests/RobotOrchestrator.Dispatcher.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager.Tests/RobotOrchestrator.FleetManager.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager.Tests/RobotOrchestrator.OrderManager.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer.Tests/RobotOrchestrator.OrderProducer.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Robot/src/orchestrated_robot/scripts/jobs_handler.py: -------------------------------------------------------------------------------- 1 | import rospy 2 | from orchestrator_msgs.msg import Job 3 | 4 | class JobsHandler: 5 | 6 | def __init__(self, robot_controller): 7 | self.robot_controller = robot_controller 8 | self.jobs_subscriber = self.set_up_jobs_subscriber() 9 | self.jobs_publisher = self.set_up_jobs_publisher() 10 | 11 | def set_up_jobs_subscriber(self): 12 | """ create subscriber for jobs """ 13 | sub = rospy.Subscriber('jobs', Job, self.job_callback) 14 | return sub 15 | 16 | def set_up_jobs_publisher(self): 17 | """ create publisher for jobs messages """ 18 | pub = rospy.Publisher('jobsstatus', Job, queue_size=10) 19 | return pub 20 | 21 | def publish_job(self, job): 22 | """ publish job to topic """ 23 | self.jobs_publisher.publish(job) 24 | 25 | def job_callback(self, data): 26 | """ callback for job topic """ 27 | rospy.loginfo(" Received a job for %s", data.RobotId) 28 | self.robot_controller.handle_job_new(data) -------------------------------------------------------------------------------- /Robot/src/orchestrator_msgs/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | orchestrator_msgs 4 | 0.0.0 5 | The orchestrator_msgs package 6 | 7 | 8 | 9 | 10 | RosSimulationTeam 11 | 12 | 13 | 14 | 15 | 16 | MIT 17 | 18 | catkin 19 | message_generation 20 | message_generation 21 | message_runtime 22 | geometry_msgs 23 | std_msgs 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/DispatcherClient.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Newtonsoft.Json; 8 | 9 | namespace RobotOrchestrator.OrderManager 10 | { 11 | 12 | public class DispatcherClient : IDispatcherClient 13 | { 14 | private static readonly HttpClient httpClient = new HttpClient(); 15 | 16 | private readonly string dispatcherUri; 17 | 18 | public DispatcherClient(string dispatcherUri) 19 | { 20 | this.dispatcherUri = dispatcherUri; 21 | } 22 | 23 | public async Task SendJobAsync(Job job) 24 | { 25 | var jobJson = JsonConvert.SerializeObject(job); 26 | var content = new StringContent(jobJson, Encoding.UTF8, "application/json"); 27 | 28 | var httpResponseMessage = await httpClient.PostAsync(dispatcherUri, content); 29 | 30 | return httpResponseMessage.IsSuccessStatusCode; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RobotOrchestrator.Dispatcher/IotHubServiceClient.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Azure.Devices; 7 | using Microsoft.Extensions.Options; 8 | 9 | namespace RobotOrchestrator.Dispatcher 10 | { 11 | public class IotHubServiceClient : IIotHubServiceClient 12 | { 13 | private readonly ServiceClient iotHubServiceClient; 14 | 15 | public IotHubServiceClient(IOptions options) 16 | { 17 | var iotHubConnectionString = options?.Value.IotHubServiceConnectionString; 18 | 19 | iotHubServiceClient = ServiceClient.CreateFromConnectionString(iotHubConnectionString); 20 | } 21 | 22 | public async Task SendMessageFromCloudToDeviceAsync(string deviceId, string message) 23 | { 24 | var commandMessage = new Message(Encoding.UTF8.GetBytes(message)); 25 | 26 | await iotHubServiceClient.SendAsync(deviceId, commandMessage); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Robot/Dockerfile-robot: -------------------------------------------------------------------------------- 1 | FROM robot 2 | 3 | # Install pip and iot hub relay dependencies 4 | RUN apt-get update \ 5 | && apt-get install python-pip ros-kinetic-rosbridge-library -y \ 6 | && pip install azure-iothub-device-client \ 7 | && rm -rf /var/lib/apt/lists/* 8 | 9 | # Get ros relay 10 | RUN git clone https://github.com/xinyiou/ros_azure_iothub.git /root/catkin_ws/src/ros_azure_iothub \ 11 | && cd /root/catkin_ws/src/ros_azure_iothub \ 12 | && git checkout 1496b4bf462df3b57ba0b954ec1bba94caa7d2fa 13 | 14 | COPY src/argos_map /root/catkin_ws/src/argos_map 15 | COPY src/orchestrated_robot /root/catkin_ws/src/orchestrated_robot 16 | COPY src/orchestrator_msgs /root/catkin_ws/src/orchestrator_msgs 17 | RUN chmod +x /root/catkin_ws/src/orchestrated_robot/scripts/robot_controller.py 18 | RUN chmod +x /root/catkin_ws/src/orchestrated_robot/scripts/telemetry_controller.py 19 | 20 | RUN /bin/bash -c "source devel/setup.bash \ 21 | && catkin_make" 22 | 23 | # Useful for interactive bash, so that there is no need to set up workspace 24 | RUN echo "source /root/catkin_ws/devel/setup.bash" >> /root/.bashrc 25 | 26 | -------------------------------------------------------------------------------- /Docs/InstallRequirements.md: -------------------------------------------------------------------------------- 1 | # Install Requirements 2 | 3 | To run the project end to end, install the following tools: 4 | 5 | - [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) 6 | - [.NET Core 2.1 SDK](https://www.microsoft.com/net/download) or install [Visual Studio 2017](https://docs.microsoft.com/en-us/visualstudio/install/install-visual-studio?view=vs-2017) version 15.7.0 or higher with the .NET Core cross-platform development workload selected. 7 | - [Docker](https://docs.docker.com/v17.09/engine/installation/) 8 | - [Docker-compose](https://docs.docker.com/compose/install/) 9 | - [Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) 10 | - [Helm](https://docs.helm.sh/using_helm/) 11 | 12 | To run the orchestrator in the cloud, you will also need access to an [Azure subscription](https://azure.microsoft.com/en-us/free/search/?&OCID=AID719825_SEM_cZGgGOIg&lnkd=Google_Azure_Brand&gclid=EAIaIQobChMI9Lm4npLp3QIVUZN-Ch2jTAvUEAAYASAAEgJvm_D_BwE&dclid=CITpxaCS6d0CFY4XrQYd-NQAhg). 13 | 14 | An [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) instance is also recommended for CI/CD. 15 | -------------------------------------------------------------------------------- /ui/src/RobotInfoCollapse.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as React from 'react'; 5 | import { Panel } from 'react-bootstrap'; 6 | 7 | class RobotInfoCollapse extends React.Component { 8 | 9 | public render() { 10 | return ( 11 | 12 | 13 | {this.props.robot.id} 14 | 15 | 16 |
Telemetry id: {this.props.robot.telemetry.id}
17 |
X: {this.props.robot.telemetry.position.x.toFixed(3)}
18 |
Y: {this.props.robot.telemetry.position.y.toFixed(3)}
19 |
Status: {this.props.robot.telemetry.status}
20 |
Order: {this.props.robot.telemetry.orderId}
21 |
22 |
23 | ); 24 | } 25 | } 26 | 27 | export default RobotInfoCollapse; -------------------------------------------------------------------------------- /Docs/SendTestOrders.md: -------------------------------------------------------------------------------- 1 | # Send Test Orders 2 | 3 | - Open the OrderProducer in swagger: [https://robot-orderproducer-dev.azurewebsites.net/swagger](https://robot-orderproducer-dev.azurewebsites.net/swagger) 4 | - To create orders, there are two approaches available: ```Orders``` and ```OrderProducer``` 5 | - ```Orders``` allows you to send an order with a given message 6 | - ```OrderProducer``` creates a stream of orders. The two ```post``` Restful APIs *start* and *stop* the stream. To start the stream, 7 | - *maxItems* is the number of orders you want to post, **-1** makes the stream running infinitely 8 | - *batchSize* is the number of orders you want to post each time, e.g., *maxItems* is 10, *batchSize* is 2, then stream will post 5 iterations of orders with 2 orders each 9 | - *delayInSecs* is the interval time between posts 10 | 11 | ## Send a Job to a Robot 12 | 13 | - Open the Dispatcher in swagger: [https://robot-dispatcher-dev.azurewebsites.net/swagger](https://robot-dispatcher-dev.azurewebsites.net/swagger) 14 | - Create a job for the robot. The id and order id need to be a guid. The RobotId should correspond to one of the robots that you have up, hackbot{0 to n-1} -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/RobotOrchestrator.FleetManager.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ui/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT License. 3 | 4 | name: ReactApp-CI 5 | 6 | trigger: 7 | branches: 8 | include: 9 | - master 10 | paths: 11 | include: 12 | - ui/* 13 | 14 | pool: 15 | vmImage: 'vs2017-win2016' 16 | 17 | steps: 18 | - task: NodeTool@0 19 | inputs: 20 | versionSpec: '8.x' 21 | displayName: 'Install Node.js' 22 | 23 | - script: | 24 | cd ui 25 | npm install --verbose 26 | displayName: 'npm install' 27 | 28 | - script: | 29 | cd ui 30 | npm run build --verbose 31 | displayName: 'npm run build' 32 | 33 | - task: CopyFiles@2 34 | inputs: 35 | sourceFolder: $(Build.SourcesDirectory)/ui/build 36 | contents: '**' 37 | targetFolder: '$(Build.ArtifactStagingDirectory)' 38 | cleanTargetFolder: true 39 | 40 | - task: CopyFiles@2 41 | inputs: 42 | sourceFolder: $(Build.SourcesDirectory)/ui 43 | contents: 'web.config' 44 | targetFolder: '$(Build.ArtifactStagingDirectory)' 45 | cleanTargetFolder: false 46 | 47 | - task: PublishBuildArtifacts@1 48 | inputs: 49 | pathtoPublish: '$(Build.ArtifactStagingDirectory)' 50 | artifactName: 'drop' -------------------------------------------------------------------------------- /Robot/src/orchestrated_robot/scripts/fleet_manager_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. 5 | 6 | import httplib 7 | import os 8 | import sys 9 | import ssl 10 | 11 | class FleetManagerClient: 12 | 13 | def __init__(self, fleetmanager_url, version): 14 | print("Fleet Manager Host is: " + fleetmanager_url) 15 | self.fleetmanager_url = fleetmanager_url 16 | self.fleetmanager_version = version 17 | 18 | def get_fleetmanager_host(self): 19 | return self.fleetmanager_url 20 | 21 | def get_robot_config(self, robotId): 22 | print("Robot id is: " + robotId) 23 | 24 | host = self.get_fleetmanager_host() 25 | url = "/api/v" + str(self.fleetmanager_version) + "/Robots/" + str(robotId) + "/connection" 26 | 27 | conn = httplib.HTTPSConnection(host, context=ssl._create_unverified_context()) 28 | headers={"Accept": "application/json", "Content-Length": "0"} 29 | conn.request("PUT", url, None, headers) 30 | response = conn.getresponse() 31 | iot_connection_string = response.read() 32 | print(response.status, response.reason, iot_connection_string) 33 | conn.close() 34 | 35 | return iot_connection_string 36 | -------------------------------------------------------------------------------- /ui/src/RobotInfoPanel.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as React from 'react'; 5 | import { PanelGroup } from 'react-bootstrap'; 6 | import Robot from './Robot'; 7 | import RobotInfoCollapse from './RobotInfoCollapse'; 8 | 9 | class RobotInfoPanel extends React.Component { 10 | 11 | constructor(props: any, context: any) { 12 | super(props, context); 13 | this.handleSelect = this.handleSelect.bind(this); 14 | } 15 | 16 | public getRobotInfoCollapses() { 17 | 18 | const result : JSX.Element = this.props.robots.map((robot : Robot) => 19 | 20 | ); 21 | 22 | return result; 23 | } 24 | 25 | public handleSelect(activeKey : any) { 26 | this.props.onSelect(activeKey); 27 | } 28 | 29 | public render() { 30 | return ( 31 | 33 | { this.getRobotInfoCollapses() } 34 | 35 | ); 36 | } 37 | } 38 | 39 | export default RobotInfoPanel; 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /helm/ros-orchestrator/values.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT License. 3 | 4 | # Default values for ros-cluster. 5 | # This is a YAML-formatted file. 6 | # Declare variables to be passed into your templates. 7 | # These values are used when running locally. 8 | # These values can be overridden in the release definitions value section. 9 | 10 | robotPrefix: robot 11 | robotCount: 10 12 | simulatorThreadCount: 10 13 | registrySecret: "image-secret" 14 | robotTargetPrefix: robot-nimbro- 15 | simulatorTargetPrefix: simulator-nimbro- 16 | portBase: 17000 17 | nimbroProtocol: tcp # tcp or udp 18 | simulationCallbackTimeout: 0.1 19 | argosLaunchFile: "construct.argos" 20 | 21 | vncPassword: "vncpassword" 22 | 23 | fleetManagerUrl: "contoso-fleetmanager.azurewebsites.net" 24 | fleetManagerVersion: "1" 25 | 26 | simulatorImage: 27 | repository: contoso.azurecr.io/simulator-orch 28 | tag: latest 29 | pullPolicy: Always 30 | 31 | robotImage: 32 | repository: contoso.azurecr.io/robot-orch 33 | tag: latest 34 | pullPolicy: Always 35 | 36 | # Resources for robot containers 37 | resources: 38 | limits: 39 | cpu: 1 40 | memory: 1024Mi 41 | requests: 42 | cpu: 1 43 | memory: 1024Mi 44 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.AspNetCore; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | 8 | namespace RobotOrchestrator.OrderManager 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | CreateWebHostBuilder(args).Build().Run(); 15 | } 16 | 17 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 18 | WebHost.CreateDefaultBuilder(args) 19 | .ConfigureAppConfiguration((context, config) => 20 | { 21 | var builtConfig = config.Build(); 22 | 23 | if (!string.IsNullOrWhiteSpace(builtConfig["Vault"])) 24 | { 25 | config.AddAzureKeyVault( 26 | $"https://{builtConfig["Vault"]}.vault.azure.net/", 27 | builtConfig["ClientId"], 28 | builtConfig["ClientSecret"] 29 | ); 30 | } 31 | }) 32 | .UseStartup(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.AspNetCore; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | 8 | namespace RobotOrchestrator.FleetManager 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | CreateWebHostBuilder(args).Build().Run(); 15 | } 16 | 17 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 18 | 19 | WebHost.CreateDefaultBuilder(args) 20 | .ConfigureAppConfiguration((context, config) => 21 | { 22 | var builtConfig = config.Build(); 23 | 24 | if (!string.IsNullOrWhiteSpace(builtConfig["Vault"])) 25 | { 26 | config.AddAzureKeyVault( 27 | $"https://{builtConfig["Vault"]}.vault.azure.net/", 28 | builtConfig["ClientId"], 29 | builtConfig["ClientSecret"] 30 | ); 31 | } 32 | }) 33 | .UseStartup(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /RobotOrchestrator/Job.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Converters; 9 | 10 | namespace RobotOrchestrator 11 | { 12 | public class Job 13 | { 14 | [JsonProperty(PropertyName = "id")] 15 | public Guid Id { get; set; } = Guid.NewGuid(); 16 | 17 | /// 18 | /// Assigned robot for job 19 | /// 20 | public string RobotId { get; set; } 21 | 22 | /// 23 | /// Parent orderId for the job 24 | /// 25 | public Guid OrderId { get; set; } 26 | 27 | /// 28 | /// Status for the job 29 | /// 30 | [JsonConverter(typeof(StringEnumConverter))] 31 | 32 | public JobStatus Status { get; set; } 33 | 34 | /// 35 | /// Start position of delivery job 36 | /// 37 | public Position StartPosition { get; set; } 38 | 39 | /// 40 | /// End position of delivery job 41 | /// 42 | public Position EndPosition { get; set; } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@types/axios": "^0.14.0", 7 | "@types/d3": "^5.0.0", 8 | "@types/express": "^4.16.0", 9 | "@types/inline-style-prefixer": "^3.0.1", 10 | "@types/react-bootstrap": "^0.32.14", 11 | "@types/react-data-grid": "^4.0.0", 12 | "@types/react-router-dom": "^4.3.1", 13 | "axios": "^0.18.0", 14 | "body-parser": "^1.18.3", 15 | "bootstrap": "^4.1.3", 16 | "d3": "^5.7.0", 17 | "express": "^4.16.3", 18 | "guid-typescript": "^1.0.7", 19 | "react": "^16.5.1", 20 | "react-bootstrap": "^0.32.4", 21 | "react-data-grid": "^4.0.8", 22 | "react-dom": "^16.5.1", 23 | "react-router-dom": "^4.3.1", 24 | "react-scripts-ts": "2.17.0" 25 | }, 26 | "scripts": { 27 | "start": "react-scripts-ts start", 28 | "build": "react-scripts-ts build", 29 | "test": "react-scripts-ts test --env=jsdom", 30 | "citest": "CI=true react-scripts-ts test --env=jsdom", 31 | "eject": "react-scripts-ts eject" 32 | }, 33 | "devDependencies": { 34 | "@types/jest": "^23.3.2", 35 | "@types/node": "^10.10.0", 36 | "@types/react": "^16.4.14", 37 | "@types/react-dom": "^16.0.7", 38 | "typescript": "^3.0.3" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /RobotOrchestrator.Dispatcher/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.AspNetCore; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using System; 8 | using System.Security.Cryptography.X509Certificates; 9 | 10 | namespace RobotOrchestrator.Dispatcher 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateWebHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 20 | WebHost.CreateDefaultBuilder(args) 21 | .ConfigureAppConfiguration((context, config) => 22 | { 23 | var builtConfig = config.Build(); 24 | 25 | if (!string.IsNullOrWhiteSpace(builtConfig["Vault"])) 26 | { 27 | config.AddAzureKeyVault( 28 | $"https://{builtConfig["Vault"]}.vault.azure.net/", 29 | builtConfig["ClientId"], 30 | builtConfig["ClientSecret"] 31 | ); 32 | } 33 | }) 34 | .UseStartup(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /RobotOrchestrator.Dispatcher/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT License. 3 | 4 | 5 | name: Dispatcher-CI 6 | 7 | queue: 'Hosted Linux Preview' 8 | 9 | trigger: 10 | branches: 11 | include: 12 | - master 13 | paths: 14 | include: 15 | - RobotOrchestrator/* 16 | - RobotOrchestrator.Dispatcher/* 17 | 18 | variables: 19 | buildConfiguration: 'Release' 20 | projectPath: 'RobotOrchestrator.Dispatcher/RobotOrchestrator.Dispatcher.csproj' 21 | testPath: 'RobotOrchestrator.Dispatcher.Tests/RobotOrchestrator.Dispatcher.Tests.csproj' 22 | 23 | steps: 24 | - script: | 25 | dotnet build --configuration $(buildConfiguration) $(projectPath) 26 | dotnet test --configuration $(buildConfiguration) --logger trx $(testPath) 27 | displayName: Build and Test Dotnet Project 28 | - task: PublishTestResults@2 29 | inputs: 30 | testRunner: VSTest 31 | testResultsFiles: '**/*.trx' 32 | displayName: Publish Test Results 33 | - task: DotNetCoreCLI@2 34 | inputs: 35 | command: publish 36 | publishWebProjects: False 37 | projects: $(projectPath) 38 | arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' 39 | zipAfterPublish: True 40 | - task: PublishBuildArtifacts@1 41 | displayName: Publish Artifacts 42 | -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT License. 3 | 4 | name: FleetManager-CI 5 | 6 | queue: 'Hosted Linux Preview' 7 | 8 | trigger: 9 | branches: 10 | include: 11 | - master 12 | paths: 13 | include: 14 | - RobotOrchestrator/* 15 | - RobotOrchestrator.FleetManager/* 16 | 17 | variables: 18 | buildConfiguration: 'Release' 19 | projectPath: 'RobotOrchestrator.FleetManager/RobotOrchestrator.FleetManager.csproj' 20 | testPath: 'RobotOrchestrator.FleetManager.Tests/RobotOrchestrator.FleetManager.Tests.csproj' 21 | 22 | steps: 23 | - script: | 24 | dotnet build --configuration $(buildConfiguration) $(projectPath) 25 | dotnet test --configuration $(buildConfiguration) --logger trx $(testPath) 26 | displayName: Build and Test Dotnet Project 27 | - task: PublishTestResults@2 28 | inputs: 29 | testRunner: VSTest 30 | testResultsFiles: '**/*.trx' 31 | displayName: Publish Test Results 32 | - task: DotNetCoreCLI@2 33 | inputs: 34 | command: publish 35 | publishWebProjects: False 36 | projects: $(projectPath) 37 | arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' 38 | zipAfterPublish: True 39 | - task: PublishBuildArtifacts@1 40 | displayName: Publish Artifacts 41 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT License. 3 | 4 | name: OrderManager-CI 5 | 6 | queue: 'Hosted Linux Preview' 7 | 8 | trigger: 9 | branches: 10 | include: 11 | - master 12 | paths: 13 | include: 14 | - RobotOrchestrator/* 15 | - RobotOrchestrator.OrderManager/* 16 | 17 | variables: 18 | buildConfiguration: 'Release' 19 | projectPath: 'RobotOrchestrator.OrderManager/RobotOrchestrator.OrderManager.csproj' 20 | testPath: 'RobotOrchestrator.OrderManager.Tests/RobotOrchestrator.OrderManager.Tests.csproj' 21 | 22 | steps: 23 | - script: | 24 | dotnet build --configuration $(buildConfiguration) $(projectPath) 25 | dotnet test --configuration $(buildConfiguration) --logger trx $(testPath) 26 | displayName: Build and Test Dotnet Project 27 | - task: PublishTestResults@2 28 | inputs: 29 | testRunner: VSTest 30 | testResultsFiles: '**/*.trx' 31 | displayName: Publish Test Results 32 | - task: DotNetCoreCLI@2 33 | inputs: 34 | command: publish 35 | publishWebProjects: False 36 | projects: $(projectPath) 37 | arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' 38 | zipAfterPublish: True 39 | - task: PublishBuildArtifacts@1 40 | displayName: Publish Artifacts 41 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/Controllers/OrdersController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace RobotOrchestrator.OrderProducer.Controllers 9 | { 10 | [Route("api/v{version:apiVersion}/[controller]")] 11 | [ApiController] 12 | public class OrdersController : ControllerBase 13 | { 14 | private readonly ILogger logger; 15 | private readonly IOrderManagerClient orderManagerClient; 16 | private readonly IOrderFactory factory; 17 | 18 | public OrdersController(IOrderManagerClient orderManagerClient, IOrderFactory factory, ILogger logger) 19 | { 20 | this.orderManagerClient = orderManagerClient; 21 | this.factory = factory; 22 | this.logger = logger; 23 | } 24 | 25 | [HttpPost] 26 | public async Task PostOrder([FromBody] string message) 27 | { 28 | var order = factory.CreateOrder(message); 29 | 30 | var result = await orderManagerClient.SendOrderAsync(order); 31 | var okResult = new OkObjectResult(result); 32 | 33 | return okResult; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT License. 3 | 4 | name: OrderProducer-CI 5 | 6 | queue: 'Hosted Linux Preview' 7 | 8 | trigger: 9 | branches: 10 | include: 11 | - master 12 | paths: 13 | include: 14 | - RobotOrchestrator/* 15 | - RobotOrchestrator.OrderProducer/* 16 | 17 | variables: 18 | buildConfiguration: 'Release' 19 | projectPath: 'RobotOrchestrator.OrderProducer/RobotOrchestrator.OrderProducer.csproj' 20 | testPath: 'RobotOrchestrator.OrderProducer.Tests/RobotOrchestrator.OrderProducer.Tests.csproj' 21 | 22 | steps: 23 | - script: | 24 | dotnet build --configuration $(buildConfiguration) $(projectPath) 25 | dotnet test --configuration $(buildConfiguration) --logger trx $(testPath) 26 | displayName: Build and Test Dotnet Project 27 | - task: PublishTestResults@2 28 | inputs: 29 | testRunner: VSTest 30 | testResultsFiles: '**/*.trx' 31 | displayName: Publish Test Results 32 | - task: DotNetCoreCLI@2 33 | inputs: 34 | command: publish 35 | publishWebProjects: False 36 | projects: $(projectPath) 37 | arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' 38 | zipAfterPublish: True 39 | - task: PublishBuildArtifacts@1 40 | displayName: Publish Artifacts -------------------------------------------------------------------------------- /RobotOrchestrator.Dispatcher/Dispatcher.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Logging; 6 | using Newtonsoft.Json; 7 | 8 | namespace RobotOrchestrator.Dispatcher 9 | { 10 | public class Dispatcher : IDispatcher 11 | { 12 | private readonly IIotHubServiceClient iotHubServiceClient; 13 | private readonly ILogger logger; 14 | private static string TOPIC_NAME = "jobs"; 15 | private static string PACKAGE_NAME = "orchestrator_msgs"; 16 | 17 | public Dispatcher(IIotHubServiceClient iotHubServiceClient, ILogger logger) 18 | { 19 | this.iotHubServiceClient = iotHubServiceClient; 20 | this.logger = logger; 21 | } 22 | 23 | public async Task SendJobAsync(Job job) 24 | { 25 | var rosMessage = new RosMessage() 26 | { 27 | Topic = TOPIC_NAME, 28 | MessageType = $"{PACKAGE_NAME}.msg:{RosMessageType.Job.ToString()}", 29 | Payload = job 30 | }; 31 | 32 | var rosMessageJson = JsonConvert.SerializeObject(rosMessage); 33 | 34 | await iotHubServiceClient.SendMessageFromCloudToDeviceAsync(job.RobotId, rosMessageJson); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ui/src/OrderManagerClient.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import axios, {AxiosError, AxiosResponse} from 'axios'; 5 | import Configuration from './Configuration'; 6 | import Order from './Order'; 7 | 8 | export class OrderManagerClient 9 | { 10 | public response : Order[]; 11 | 12 | private uri : string 13 | 14 | constructor() 15 | { 16 | const orderManagerUrl = Configuration.orderManagerUrl; 17 | const orderManagerVersion = Configuration.orderManagerVersion; 18 | 19 | this.uri = "https://" + orderManagerUrl + "/api/v" + orderManagerVersion + "/Orders"; 20 | } 21 | 22 | public async getOrdersAsync() { 23 | await axios.get(this.uri) 24 | .then(this.handleResponse).catch(this.handleError); 25 | } 26 | 27 | private handleResponse = (response : AxiosResponse) => { 28 | this.response = response.data as Order[]; 29 | } 30 | 31 | private handleError = (error : AxiosError) => { 32 | if(error.response) 33 | { 34 | console.log(error.response.data); 35 | console.log(error.response.status); 36 | console.log(error.response.headers); 37 | } 38 | else{ 39 | console.log(error.message); 40 | } 41 | } 42 | } 43 | 44 | export default OrderManagerClient; -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/OrderHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace RobotOrchestrator.OrderProducer 8 | { 9 | 10 | public class OrderHandler : IOrderHandler 11 | { 12 | private readonly IOrderManagerClient orderManagerClient; 13 | private readonly IOrderFactory factory; 14 | private readonly ILogger logger; 15 | 16 | public OrderHandler(IOrderManagerClient orderManagerClient, IOrderFactory factory, ILogger logger) 17 | { 18 | this.orderManagerClient = orderManagerClient; 19 | this.factory = factory; 20 | this.logger = logger; 21 | } 22 | 23 | public void HandleBatch(int batchSize) 24 | { 25 | for (int i = 0; i < batchSize; i++) 26 | { 27 | var order = factory.CreateOrder($"Order{i}"); 28 | 29 | try 30 | { 31 | orderManagerClient.SendOrderAsync(order); 32 | } 33 | catch (Exception ex) 34 | { 35 | logger.LogError(ex.Message); 36 | } 37 | } 38 | 39 | logger.LogDebug($"OrderHandler called with batch size = {batchSize}"); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /cgmanifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "Registrations": [ 3 | { 4 | "component": { 5 | "type": "git", 6 | "git": { 7 | "repositoryUrl": "https://github.com/xinyiou/ros_azure_iothub.git", 8 | "commitHash": "1496b4bf462df3b57ba0b954ec1bba94caa7d2fa" 9 | } 10 | } 11 | }, 12 | { 13 | "Component": { 14 | "Type": "other", 15 | "Other": { 16 | "Name": "python-pip", 17 | "Version": "latest", 18 | "DownloadUrl": "https://codeload.github.com/pypa/pip/zip/master" 19 | } 20 | }, 21 | "DevelopmentDependency": false 22 | }, 23 | { 24 | "component": { 25 | "type": "git", 26 | "git": { 27 | "repositoryUrl": "https://github.com/RobotWebTools/rosbridge_suite.git", 28 | "commitHash": "5c423b8b133e6444206ff3e287f3f4495b73e5dc" 29 | } 30 | } 31 | }, 32 | { 33 | "component": { 34 | "type": "git", 35 | "git": { 36 | "repositoryUrl": "https://github.com/Azure/azure-iot-sdk-python.git", 37 | "commitHash": "568d7f9cde749ed2ddab3cadc474f59a911e6ff4" 38 | } 39 | } 40 | } 41 | ], 42 | "Version": 1 43 | } -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Security.Cryptography.X509Certificates; 9 | using System.Threading.Tasks; 10 | using Microsoft.AspNetCore; 11 | using Microsoft.AspNetCore.Hosting; 12 | using Microsoft.Extensions.Configuration; 13 | using Microsoft.Extensions.Logging; 14 | 15 | namespace RobotOrchestrator.OrderProducer 16 | { 17 | public class Program 18 | { 19 | public static void Main(string[] args) 20 | { 21 | CreateWebHostBuilder(args).Build().Run(); 22 | } 23 | 24 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 25 | WebHost.CreateDefaultBuilder(args) 26 | .ConfigureAppConfiguration((context, config) => 27 | { 28 | var builtConfig = config.Build(); 29 | 30 | if (!string.IsNullOrWhiteSpace(builtConfig["Vault"])) 31 | { 32 | config.AddAzureKeyVault( 33 | $"https://{builtConfig["Vault"]}.vault.azure.net/", 34 | builtConfig["ClientId"], 35 | builtConfig["ClientSecret"] 36 | ); 37 | } 38 | }) 39 | .UseStartup(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer.Tests/OrdersControllerTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Xunit; 5 | using Microsoft.AspNetCore.Mvc; 6 | using System.Net; 7 | using Moq; 8 | using RobotOrchestrator.OrderProducer.Controllers; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace RobotOrchestrator.OrderProducer.Tests 12 | { 13 | public class OrdersControllerTests 14 | { 15 | private Mock mockOrderManagerClient; 16 | private Mock> mockLogger; 17 | private Mock mockFactory; 18 | private OrdersController ordersController; 19 | 20 | public OrdersControllerTests() 21 | { 22 | mockOrderManagerClient = new Mock(); 23 | mockLogger = new Mock>(); 24 | mockFactory = new Mock(); 25 | ordersController = new OrdersController(mockOrderManagerClient.Object, mockFactory.Object, mockLogger.Object); 26 | } 27 | 28 | [Fact] 29 | public void Post_SingleOrder_ReturnsSuccess() 30 | { 31 | var actionResult = ordersController.PostOrder("test order"); 32 | var createdResult = new OkObjectResult(actionResult); 33 | 34 | Assert.NotNull(createdResult); 35 | Assert.NotNull(createdResult.Value); 36 | 37 | Assert.Equal((int?)HttpStatusCode.OK, createdResult.StatusCode); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ui/src/RobotManagerClient.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import axios, {AxiosError, AxiosResponse} from 'axios'; 5 | import Configuration from './Configuration'; 6 | import Robot from './Robot'; 7 | 8 | export class FleetManagerClient 9 | { 10 | public responseRobots : Robot[]; 11 | 12 | private uriRobots : string; 13 | 14 | constructor() 15 | { 16 | const fleetManagerUrl = Configuration.fleetManagerUrl; 17 | const fleetManagerVersion = Configuration.fleetManagerVersion; 18 | this.uriRobots = "https://" + fleetManagerUrl + "/api/v" + fleetManagerVersion + "/Robots"; 19 | } 20 | 21 | public async getRobotsAsync() { 22 | await axios.get(this.uriRobots) 23 | .then(this.handleResponseOfRobots).catch(this.handleError); 24 | } 25 | 26 | private handleResponseOfRobots = (response : AxiosResponse) => { 27 | this.responseRobots = response.data as Robot[]; 28 | 29 | // sort robots on id ascending. 30 | this.responseRobots.sort((one : Robot, two : Robot) => (one.id < two.id ? -1 : 1)); 31 | } 32 | 33 | private handleError = (error : AxiosError) => { 34 | if(error.response) 35 | { 36 | console.log(error.response.data); 37 | console.log(error.response.status); 38 | console.log(error.response.headers); 39 | } 40 | else{ 41 | console.log(error.message); 42 | } 43 | } 44 | } 45 | 46 | export default FleetManagerClient; -------------------------------------------------------------------------------- /RobotOrchestrator.Dispatcher/Controllers/JobsController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Azure.Devices.Common.Exceptions; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace RobotOrchestrator.Dispatcher.Controllers 11 | { 12 | [Route("api/v{version:apiVersion}/[controller]")] 13 | [ApiController] 14 | public class JobsController : ControllerBase 15 | { 16 | private readonly IDispatcher dispatcher; 17 | private readonly ILogger logger; 18 | 19 | public JobsController(IDispatcher dispatcher, ILogger logger) 20 | { 21 | this.dispatcher = dispatcher; 22 | this.logger = logger; 23 | } 24 | 25 | [HttpPost] 26 | public async Task PostJobAsync(Job job) 27 | { 28 | try 29 | { 30 | await dispatcher.SendJobAsync(job); 31 | 32 | var result = new OkResult(); 33 | return result; 34 | } 35 | catch (DeviceNotFoundException) 36 | { 37 | var result = new NotFoundObjectResult("Device Not Found"); 38 | return result; 39 | } 40 | catch (Exception ex) 41 | { 42 | var result = new BadRequestObjectResult(ex.Message); 43 | return result; 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Provisioning/keyvault.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "envName": { 6 | "type": "string", 7 | "minLength": 3, 8 | "metadata": { 9 | "description": "Environment name (e.g. dev, prod)" 10 | } 11 | } 12 | }, 13 | "variables": { 14 | "location": "[resourceGroup().location]", 15 | "keyVaultName": "[concat('robot-orch-kv-',parameters('envName'))]", 16 | "enableVaultForDeployment": false, 17 | "enableVaultForDiskEncryption": false, 18 | "enabledForTemplateDeployment": true, 19 | "keyVaultSkuName": "Standard" 20 | }, 21 | "resources": [ 22 | { 23 | "type": "Microsoft.KeyVault/vaults", 24 | "name": "[variables('keyVaultName')]", 25 | "apiVersion": "2018-02-14", 26 | "location": "[variables('location')]", 27 | "properties": { 28 | "enabledForDeployment": "[variables('enableVaultForDeployment')]", 29 | "enabledForDiskEncryption": "[variables('enableVaultForDiskEncryption')]", 30 | "enabledForTemplateDeployment": "[variables('enabledForTemplateDeployment')]", 31 | "tenantId": "[subscription().tenantId]", 32 | "accessPolicies": [], 33 | "sku": { 34 | "name": "[variables('keyVaultSkuName')]", 35 | "family": "A" 36 | } 37 | } 38 | } 39 | ], 40 | "outputs": { 41 | "keyVaultName": { 42 | "type": "string", 43 | "value": "[variables('keyVaultName')]" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /RobotOrchestrator/Order.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Converters; 6 | using System; 7 | using System.Collections.Generic; 8 | 9 | namespace RobotOrchestrator 10 | { 11 | public class Order 12 | { 13 | /// 14 | /// Order Id 15 | /// 16 | [JsonProperty(PropertyName = "id")] 17 | public Guid Id { get; set; } = Guid.NewGuid(); 18 | 19 | /// 20 | /// Start position pickup of delivery order 21 | /// 22 | public Position StartPosition { get; set; } 23 | 24 | /// 25 | /// End position pickup of delivery order 26 | /// 27 | public Position EndPosition { get; set; } 28 | 29 | /// 30 | /// Set the status of the order 31 | /// 32 | [JsonConverter(typeof(StringEnumConverter))] 33 | public OrderStatus Status { get; set; } = OrderStatus.New; 34 | 35 | /// 36 | /// Jobs created for the order 37 | /// 38 | public List Jobs { get; set; } = new List(); 39 | 40 | /// 41 | /// Time the order was created 42 | /// 43 | public DateTime CreatedDateTime { get; set; } = DateTime.UtcNow; 44 | 45 | /// 46 | /// Message accompanied with order 47 | /// Note: for now this is a placeholder and this can be any 48 | /// object for order creation later on 49 | /// 50 | public string Message { get; set; } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit 4 | 5 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repositories using our CLA. 6 | 7 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 8 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 10 | 11 | ## Support Channels 12 | 13 | TODO: Link to github issues 14 | 15 | RosSimulationTeam@microsoft.com 16 | 17 | ## Getting started 18 | 19 | To get set up for development and start running the robot orchestration suite, see the [README](README.md) for this project. 20 | 21 | ## Linters 22 | 23 | * Markdown (.md) files: [Markdown Lint for VS Code](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) 24 | * C#: [C# for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp) 25 | * Shell scripts (.sh): [Shellcheck for VS Code](https://github.com/timonwong/vscode-shellcheck/blob/master/README.md) 26 | * Spelling: [Code Spell Checker for VS Code](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) 27 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer.Tests/OrdersProducerControllerTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Xunit; 5 | using Microsoft.AspNetCore.Mvc; 6 | using System.Net; 7 | using Moq; 8 | 9 | using RobotOrchestrator.OrderProducer.Controllers; 10 | using Microsoft.Extensions.Logging; 11 | 12 | namespace RobotOrchestrator.OrderProducer.Tests 13 | { 14 | public class OrdersProducerControllerTests 15 | { 16 | [Fact] 17 | public void PostStartProducer_Start_ReturnsSuccess() 18 | { 19 | var controller = CreateController(); 20 | 21 | var actionResult = controller.PostStartProducer(new BatchJobOptions()); 22 | var okResult = actionResult as OkResult; 23 | 24 | Assert.NotNull(okResult); 25 | Assert.Equal((int?)HttpStatusCode.OK, okResult.StatusCode); 26 | } 27 | 28 | [Fact] 29 | public void PostStopProducer_Stop_ReturnsSuccess() 30 | { 31 | var controller = CreateController(); 32 | 33 | var actionResult = controller.PostStartProducer(new BatchJobOptions()); 34 | var okResult = actionResult as OkResult; 35 | 36 | Assert.NotNull(okResult); 37 | Assert.Equal((int?)HttpStatusCode.OK, okResult.StatusCode); 38 | } 39 | 40 | private OrdersProducerController CreateController() 41 | { 42 | var orderHandler = Mock.Of(); 43 | var batchManager = Mock.Of(); 44 | var logger = Mock.Of>(); 45 | var controller = new OrdersProducerController(orderHandler, batchManager, logger); 46 | 47 | return controller; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/OrderManagerClient.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Newtonsoft.Json; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Net.Http; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace RobotOrchestrator.OrderProducer 12 | { 13 | public class OrderManagerClient : IOrderManagerClient 14 | { 15 | private static readonly HttpClient httpClient; 16 | 17 | private readonly string orderManagerUri; 18 | 19 | static OrderManagerClient() 20 | { 21 | httpClient = new HttpClient(); 22 | } 23 | 24 | public OrderManagerClient(string orderManagerUri) 25 | { 26 | if (string.IsNullOrWhiteSpace(orderManagerUri)) 27 | { 28 | throw new ArgumentException("Value of orderManagerUri must be a valid Uri", "orderManagerUri"); 29 | } 30 | this.orderManagerUri = orderManagerUri + "/batch"; 31 | } 32 | 33 | public async Task SendOrderAsync(Order order) 34 | { 35 | var orders = new List { order }; 36 | var result = await SendOrdersAsync(orders); 37 | return result; 38 | } 39 | 40 | public async Task SendOrdersAsync(List orders) 41 | { 42 | var ordersAsJson = JsonConvert.SerializeObject(orders); 43 | var content = new StringContent(ordersAsJson, Encoding.UTF8, "application/json"); 44 | var httpResponseMessage = await httpClient.PostAsync(orderManagerUri, content); 45 | var response = await httpResponseMessage.Content.ReadAsStringAsync(); 46 | return response; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/TelemetryHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.Azure.Documents; 5 | using Microsoft.Extensions.Logging; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Threading.Tasks; 9 | 10 | namespace RobotOrchestrator.FleetManager 11 | { 12 | public class TelemetryHandler : ITelemetryHandler 13 | { 14 | private readonly ICosmosDbClient cosmosDbClient; 15 | private readonly ILogger logger; 16 | 17 | public TelemetryHandler(ICosmosDbClient cosmosDbClient, ILogger logger) 18 | { 19 | this.cosmosDbClient = cosmosDbClient; 20 | this.logger = logger; 21 | } 22 | 23 | public async Task InsertTelemetryAsync(IEnumerable robotTelemetry) 24 | { 25 | foreach(var telemetry in robotTelemetry) 26 | { 27 | try 28 | { 29 | await cosmosDbClient.UpsertItemAsync(telemetry, new PartitionKey(telemetry.RobotId)); 30 | } 31 | catch (Exception ex) 32 | { 33 | logger.LogError(ex.Message); 34 | } 35 | } 36 | } 37 | 38 | public async Task> GetLatestTelemetriesAsync(string robotId, int? n = null) 39 | { 40 | if (n < 0) 41 | { 42 | throw (new ArgumentOutOfRangeException("Number of items to retrieve cannot be negative")); 43 | } 44 | 45 | var result = await cosmosDbClient.GetItemsAsync(t => t.RobotId == robotId, t => t.CreatedDateTime, n); 46 | 47 | return result; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Robot/src/orchestrated_robot/launch/robot.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | true 13 | 14 | 15 | $(arg fleetmanager_url) 16 | $(arg fleetmanager_version) 17 | $(arg robot_name) 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/BatchManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace RobotOrchestrator.OrderProducer 8 | { 9 | public class BatchManager : IBatchManager 10 | { 11 | private BatchJob batchJob; 12 | 13 | private static readonly Object batchJobLock = new Object(); 14 | 15 | private readonly ILogger logger; 16 | 17 | public BatchManager(ILogger logger) 18 | { 19 | this.logger = logger; 20 | } 21 | 22 | public void StartBatchJob(Action handler, BatchJobOptions options) 23 | { 24 | lock (batchJobLock) 25 | { 26 | if (batchJob == null || !batchJob.IsRunning) 27 | { 28 | batchJob = new BatchJob(options, logger); 29 | batchJob.Start(handler); 30 | } 31 | else 32 | { 33 | throw new InvalidOperationException("Batch job already started."); 34 | } 35 | } 36 | } 37 | 38 | public void StopBatchJob() 39 | { 40 | lock (batchJobLock) 41 | { 42 | if (batchJob != null) 43 | { 44 | try 45 | { 46 | batchJob.Stop(); 47 | } 48 | finally 49 | { 50 | batchJob = null; 51 | } 52 | } 53 | else 54 | { 55 | throw new InvalidOperationException("Batch job does not exist."); 56 | } 57 | } 58 | } 59 | 60 | public bool HasActiveBatchJob() 61 | { 62 | var hasActiveBatchJob = batchJob != null && batchJob.IsRunning; 63 | return hasActiveBatchJob; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer.Tests/OrderFactoryTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Xunit; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | using System; 8 | using System.Collections; 9 | using System.Linq; 10 | using RobotOrchestrator.OrderProducer; 11 | using Moq; 12 | 13 | namespace RobotOrchestrator.OrderProducer.Tests 14 | { 15 | public class OrderFactoryTests 16 | { 17 | [Fact] 18 | public void CreateOrder_SingleOrder_ReturnsSingleOrder() 19 | { 20 | var logger = Mock.Of>(); 21 | var factory = new OrderFactory(logger); 22 | 23 | var message = "custom message"; 24 | var order = factory.CreateOrder(message); 25 | 26 | Assert.NotNull(order); 27 | Assert.Equal(message, order.Message); 28 | 29 | var noMessageOrder = factory.CreateOrder(); 30 | 31 | Assert.NotNull(noMessageOrder); 32 | Assert.Null(noMessageOrder.Message); 33 | } 34 | 35 | [Theory] 36 | [InlineData(1)] 37 | [InlineData(100)] 38 | public void CreateOrders_MultipleOrders_ReturnsMultipleOrders(int numOfOrders) 39 | { 40 | var logger = Mock.Of>(); 41 | var factory = new OrderFactory(logger); 42 | 43 | var orders = factory.CreateOrders(numOfOrders); 44 | 45 | Assert.NotNull(orders); 46 | Assert.Equal(numOfOrders, orders.Count()); 47 | } 48 | 49 | [Theory] 50 | [InlineData(0)] 51 | [InlineData(-1)] 52 | public void CreateOrders_InvalidNumOrders_ThrowsArgumentException(int numOfOrders) 53 | { 54 | var logger = Mock.Of>(); 55 | var factory = new OrderFactory(logger); 56 | 57 | var exception = Assert.Throws(() => factory.CreateOrders(numOfOrders)); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/OrderFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Microsoft.Extensions.Logging; 8 | using Newtonsoft.Json; 9 | 10 | namespace RobotOrchestrator.OrderProducer 11 | { 12 | public class OrderFactory : IOrderFactory 13 | { 14 | public double MinX { get; set; } = 20; 15 | public double MaxX { get; set; } = 50; 16 | 17 | public double MinY { get; set; } = 20; 18 | public double MaxY { get; set; } = 50; 19 | 20 | private ILogger logger; 21 | 22 | private Random random; 23 | 24 | public OrderFactory(ILogger logger) 25 | { 26 | this.logger = logger; 27 | random = new Random(Guid.NewGuid().GetHashCode()); 28 | } 29 | 30 | public Order CreateOrder(string message = null) 31 | { 32 | var order = new Order() 33 | { 34 | StartPosition = GenerateRandomPosition(), 35 | EndPosition = GenerateRandomPosition(), 36 | Message = message 37 | }; 38 | 39 | logger.LogDebug($"Created Order{JsonConvert.SerializeObject(order)}"); 40 | 41 | return order; 42 | } 43 | 44 | public IEnumerable CreateOrders(int numOfOrders, string message = null) 45 | { 46 | if (numOfOrders <= 0) 47 | { 48 | throw new ArgumentOutOfRangeException("numOfOrders"); 49 | } 50 | 51 | var orders = Enumerable.Range(0, numOfOrders).Select(i => CreateOrder($"Order{i}")); 52 | 53 | return orders; 54 | } 55 | 56 | private Position GenerateRandomPosition() { 57 | 58 | var x = random.NextDouble() * (MaxX - MinX) + MinX; 59 | var y = random.NextDouble() * (MaxY - MinY) + MinY; 60 | 61 | var position = new Position(x, y); 62 | 63 | return position; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/Controllers/TelemetryController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Cors; 8 | using Microsoft.AspNetCore.Mvc; 9 | 10 | namespace RobotOrchestrator.FleetManager.Controllers 11 | { 12 | [Route("api/v{version:apiVersion}/[controller]")] 13 | [ApiController] 14 | public class TelemetryController : ControllerBase 15 | { 16 | private IFleetManager fleetManager; 17 | 18 | public TelemetryController(IFleetManager fleetManager) 19 | { 20 | this.fleetManager = fleetManager; 21 | } 22 | 23 | [HttpGet("{robotId}")] 24 | [EnableCors("AllowAllOrigin")] 25 | public async Task>> GetTelemetriesAsync(string robotId, int? numTelemetry) 26 | { 27 | IEnumerable results = new List(); 28 | try 29 | { 30 | results = await fleetManager.GetLatestTelemetriesAsync(robotId, numTelemetry); 31 | } 32 | catch(Exception ex) 33 | { 34 | var badResult = new BadRequestObjectResult(ex.Message); 35 | return badResult; 36 | } 37 | 38 | return new OkObjectResult(results); 39 | 40 | } 41 | 42 | [HttpPut] 43 | public async Task>> PutTelemetriesAsync([FromBody] IEnumerable robotTelemetry) 44 | { 45 | try 46 | { 47 | await fleetManager.InsertTelemetriesAndUpdateRobotsAsync(robotTelemetry); 48 | } 49 | catch(Exception ex) 50 | { 51 | var badResult = new BadRequestObjectResult(ex.Message); 52 | return badResult; 53 | } 54 | 55 | var okResult = new OkObjectResult(robotTelemetry); 56 | return okResult; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager.Tests/IotHubRegistryClientTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.Logging; 6 | using Moq; 7 | using System; 8 | using Xunit; 9 | 10 | namespace RobotOrchestrator.FleetManager.Tests 11 | { 12 | public class IotHubRegistryClientTest 13 | { 14 | private Mock> mockLogger; 15 | 16 | public IotHubRegistryClientTest() 17 | { 18 | mockLogger = new Mock>(); 19 | } 20 | 21 | [Theory] 22 | [InlineData("HostName=iot-hub-test.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=fakekey=")] 23 | public void GetHostName_Success(string validConnectionString) 24 | { 25 | Assert.Equal("HostName=iot-hub-test.azure-devices.net", IotHubRegistryClient.GetHostName(validConnectionString)); 26 | } 27 | 28 | [Theory] 29 | [InlineData(null)] 30 | [InlineData("HostName=iot-hub-test.azure-devices.net")] 31 | public void GetHostName_Failed(string invalidConnectionString) 32 | { 33 | Action expectedException = () => { IotHubRegistryClient.GetHostName(invalidConnectionString); }; 34 | 35 | var exception = Record.Exception(expectedException); 36 | 37 | Assert.NotNull(exception); 38 | } 39 | 40 | [Fact] 41 | public void GetDeviceConnectionStringTest() 42 | { 43 | string validConnectionString = "HostName=iot-hub-test.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=fakekey="; 44 | IotHubRegistryClient iotHubRegistryClient = new IotHubRegistryClient(validConnectionString, mockLogger.Object); 45 | 46 | string expectedResult = iotHubRegistryClient.GetDeviceConnectionString("device", "key"); 47 | string factResult = "HostName=iot-hub-test.azure-devices.net;DeviceId=device;SharedAccessKey=key"; 48 | 49 | Assert.Equal(factResult, expectedResult); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Robot/src/orchestrated_robot/scripts/move_base_client.py: -------------------------------------------------------------------------------- 1 | import rospy 2 | import actionlib 3 | 4 | from geometry_msgs.msg import Pose 5 | from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal 6 | from actionlib_msgs.msg import GoalStatus 7 | 8 | class MoveBaseClient: 9 | 10 | def __init__(self, id, base_frame="map"): 11 | 12 | self.base_frame = base_frame 13 | self.client = actionlib.SimpleActionClient('move_base', MoveBaseAction) 14 | self.client.wait_for_server() 15 | self.id = id 16 | self.current_goal = None 17 | 18 | def create_pose(self, x, y): 19 | pose = Pose() 20 | pose.position.x = x 21 | pose.position.y = y 22 | pose.orientation.w = 1.0 23 | 24 | rospy.loginfo(pose) 25 | 26 | return pose 27 | 28 | def move(self, pose): 29 | 30 | rospy.loginfo("Move was called for id: " + self.id) 31 | 32 | goal = MoveBaseGoal() 33 | goal.target_pose.header.frame_id = self.base_frame 34 | goal.target_pose.header.stamp = rospy.Time.now() 35 | goal.target_pose.pose = pose 36 | 37 | self.current_goal = goal 38 | 39 | self.client.send_goal(goal, self.done_callback, self.active_callback) 40 | 41 | wait = self.client.wait_for_result() 42 | 43 | if not wait: 44 | rospy.logerr("Action server not available!") 45 | rospy.signal_shutdown("Action server not available!") 46 | else: 47 | return self.client.get_state() == GoalStatus.SUCCEEDED 48 | 49 | def active_callback(self): 50 | rospy.loginfo("Move base goal is active for id: " + self.id) 51 | 52 | def done_callback(self, status, result): 53 | 54 | if (status == GoalStatus.SUCCEEDED): 55 | rospy.loginfo("Goal succeeded for id: " + self.id) 56 | 57 | elif (status == GoalStatus.PREEMPTED or status == GoalStatus.RECALLED): 58 | rospy.loginfo("Goal was cancelled for id: " + self.id) 59 | 60 | elif (status == GoalStatus.ABORTED): 61 | rospy.loginfo("Goal aborted for id: " + self.id) 62 | 63 | elif (status == GoalStatus.REJECTED): 64 | rospy.loginfo("Goal rejected for id: " + self.id) 65 | -------------------------------------------------------------------------------- /RobotOrchestrator/QueryHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.Azure.Documents; 5 | using Microsoft.Azure.Documents.Client; 6 | using Microsoft.Azure.Documents.Linq; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Linq.Expressions; 11 | using System.Threading.Tasks; 12 | using Newtonsoft.Json; 13 | 14 | namespace RobotOrchestrator 15 | { 16 | public class QueryHelper : IQueryHelper 17 | { 18 | public IQueryable Query { get; set; } 19 | 20 | public QueryHelper(IDocumentClient client, Uri documentCollectionUri) 21 | { 22 | // create base query 23 | Query = client.CreateDocumentQuery(documentCollectionUri, new FeedOptions 24 | { 25 | EnableCrossPartitionQuery = true, 26 | JsonSerializerSettings = new JsonSerializerSettings() 27 | { 28 | DateParseHandling = DateParseHandling.None 29 | } 30 | }); 31 | } 32 | 33 | public QueryHelper ByExpression(Expression> expression) 34 | { 35 | Query = Query.Where(expression); 36 | return this; 37 | } 38 | 39 | public QueryHelper OrderDescending(Expression> orderDescendingExpression) 40 | { 41 | Query = Query.OrderByDescending(orderDescendingExpression); 42 | return this; 43 | } 44 | 45 | public QueryHelper Take(int n) 46 | { 47 | Query = Query.Take(n); 48 | return this; 49 | } 50 | 51 | public async Task> GetQueryItemsAsync() 52 | { 53 | var items = new List(); 54 | 55 | using (var queryable = Query.AsDocumentQuery()) 56 | { 57 | while (queryable.HasMoreResults) 58 | { 59 | foreach (var t in await queryable.ExecuteNextAsync()) 60 | { 61 | items.Add(t); 62 | } 63 | } 64 | }; 65 | 66 | return items; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer.Tests/BatchJobTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Xunit; 5 | using System.Threading.Tasks; 6 | using Moq; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace RobotOrchestrator.OrderProducer.Tests 10 | { 11 | public class BatchJobTests 12 | { 13 | [Theory] 14 | [InlineData(1, 1)] 15 | [InlineData(10, 3)] 16 | [InlineData(50, 10)] 17 | public async Task StartBatchJob_WithMaxItemsAndBatchSize_RunsAppropriateNumberOfTimes(int maxItems, int batchSize) 18 | { 19 | var expectedIterations = (maxItems / batchSize) + (maxItems % batchSize == 0 ? 0 : 1); 20 | 21 | BatchJobOptions options = new BatchJobOptions() 22 | { 23 | MaxItems = maxItems, 24 | BatchSize = batchSize, 25 | DelayInSecs = 0 26 | }; 27 | 28 | BatchJob job = new BatchJob(options, Mock.Of()); 29 | 30 | IOrderHandler handler = Mock.Of(); 31 | Mock.Get(handler).Setup(h => h.HandleBatch(It.IsAny())).Verifiable(); 32 | 33 | await job.Start(handler.HandleBatch); 34 | 35 | Mock.Get(handler).Verify((m) => m.HandleBatch(It.IsAny()), Times.Exactly(expectedIterations)); 36 | } 37 | 38 | [Theory] 39 | [InlineData(1)] 40 | [InlineData(10)] 41 | public void StartBatchJob_WithInfiniteItems_RunsMoreThanArbitraryTimes(int arbitraryRuns) 42 | { 43 | var batchSize = 1; 44 | 45 | BatchJobOptions options = new BatchJobOptions() 46 | { 47 | MaxItems = -1, 48 | BatchSize = batchSize, 49 | DelayInSecs = 0 50 | }; 51 | 52 | BatchJob job = new BatchJob(options, Mock.Of()); 53 | 54 | IOrderHandler handler = Mock.Of(); 55 | Mock.Get(handler).Setup(h => h.HandleBatch(batchSize)).Verifiable(); 56 | 57 | // fire and forget since it is infinite thread 58 | job.Start(handler.HandleBatch).Wait(1000); 59 | 60 | // give time for iterations 61 | job.Stop(); 62 | 63 | Mock.Get(handler).Verify((m) => m.HandleBatch(batchSize), Times.AtLeast(arbitraryRuns)); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/Controllers/OrdersProducerController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace RobotOrchestrator.OrderProducer.Controllers 9 | { 10 | [Route("api/v{version:apiVersion}/[controller]")] 11 | [ApiController] 12 | public class OrdersProducerController : ControllerBase 13 | { 14 | private readonly IOrderHandler orderHandler; 15 | 16 | private readonly IBatchManager batchManager; 17 | 18 | private readonly ILogger logger; 19 | 20 | public OrdersProducerController( 21 | IOrderHandler orderHandler, 22 | IBatchManager batchManager, 23 | ILogger logger) 24 | { 25 | this.orderHandler = orderHandler; 26 | this.batchManager = batchManager; 27 | this.logger = logger; 28 | } 29 | 30 | [HttpPost("start")] 31 | public IActionResult PostStartProducer(BatchJobOptions options) 32 | { 33 | IActionResult result; 34 | 35 | try 36 | { 37 | batchManager.StartBatchJob(orderHandler.HandleBatch, options); 38 | } 39 | catch (Exception ex) 40 | { 41 | result = new BadRequestObjectResult(ex.Message); 42 | return result; 43 | } 44 | 45 | result = new OkResult(); 46 | return result; 47 | } 48 | 49 | [HttpPost("stop")] 50 | public IActionResult PostStopProducer() 51 | { 52 | IActionResult result; 53 | 54 | try 55 | { 56 | batchManager.StopBatchJob(); 57 | } 58 | catch (Exception ex) 59 | { 60 | result = new BadRequestObjectResult(ex.Message); 61 | return result; 62 | } 63 | 64 | result = new OkResult(); 65 | return result; 66 | } 67 | 68 | [HttpGet("status")] 69 | public IActionResult GetProducerStatus() 70 | { 71 | var streamStatus = batchManager.HasActiveBatchJob(); 72 | 73 | var result = new OkObjectResult(new { IsRunning = streamStatus } ); 74 | return result; 75 | 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ui/public/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 31 | React App 32 | 33 | 34 | 37 |
38 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Build a Docker image 2 | # Build a Docker image to run, deploy, or push to a container registry. 3 | # Add steps that use Docker Compose, tag images, push to a registry, run an image, and more: 4 | # https://docs.microsoft.com/vsts/pipelines/languages/docker 5 | 6 | name: Ros-Orchestration-CI 7 | 8 | queue: 'Hosted Ubuntu 1604' 9 | 10 | pr: none 11 | trigger: 12 | branches: 13 | include: 14 | - master 15 | 16 | variables: 17 | simulatorBaseImageName: simulator:latest 18 | robotBaseImageName: robot:latest 19 | simulatorImageName: 'simulator-orch:$(build.buildId)' 20 | robotImageName: 'robot-orch:$(build.buildId)' 21 | 22 | steps: 23 | - script: | 24 | # Pull down the base simulator image and tag to simulator 25 | docker login $(registry) -u $(registry-username) -p $(registry-password) 26 | docker pull $(registry)/$(simulatorBaseImageName) 27 | docker tag $(registry)/$(simulatorBaseImageName) simulator 28 | 29 | # Build new simulator-orch image 30 | docker build -f ./Robot/Dockerfile-simulator -t $(registry)/$(simulatorImageName) ./Robot/. 31 | displayName: 'docker build $(simulatorImageName)' 32 | 33 | - script: | 34 | # Pull down the base robot image and tag to robot 35 | docker login $(registry) -u $(registry-username) -p $(registry-password) 36 | docker pull $(registry)/$(robotBaseImageName) 37 | docker tag $(registry)/$(robotBaseImageName) robot 38 | 39 | # Build new robot-orch image 40 | docker build -f ./Robot/Dockerfile-robot -t $(registry)/$(robotImageName) ./Robot/. 41 | displayName: 'docker build $(robotImageName)' 42 | 43 | - script: | 44 | docker login $(registry) -u $(registry-username) -p $(registry-password) 45 | docker push $(registry)/$(simulatorImageName) 46 | displayName: 'docker push $(registry)/$(simulatorImageName)' 47 | 48 | - script: | 49 | docker login $(registry) -u $(registry-username) -p $(registry-password) 50 | docker push $(registry)/$(robotImageName) 51 | displayName: 'docker push $(registry)/$(robotImageName)' 52 | 53 | - task: CopyFiles@2 54 | inputs: 55 | sourceFolder: $(Build.SourcesDirectory) 56 | contents: 'helm/**' 57 | targetFolder: '$(Build.ArtifactStagingDirectory)' 58 | cleanTargetFolder: true 59 | 60 | - task: PublishBuildArtifacts@1 61 | condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) 62 | inputs: 63 | pathtoPublish: '$(Build.ArtifactStagingDirectory)' 64 | artifactName: 'drop' -------------------------------------------------------------------------------- /ui/src/App.css: -------------------------------------------------------------------------------- 1 | /* Copyright (c) Microsoft Corporation. All rights reserved. 2 | Licensed under the MIT License. */ 3 | 4 | .App { 5 | text-align: center; 6 | font-weight: 300; 7 | font-family: "Work Sans"; 8 | } 9 | 10 | #brand { 11 | color: white; 12 | font-size: 1em; 13 | } 14 | 15 | .navbar-nav span[role=button] { 16 | display: inline-block; 17 | line-height: 20px; 18 | } 19 | 20 | .nav a { 21 | color: white !important; 22 | font-size: 1em; 23 | float: left; 24 | display: block; 25 | padding: 15px; 26 | text-align: center; 27 | text-decoration: none; 28 | border-bottom: 3px solid transparent; 29 | } 30 | 31 | .nav a:hover { 32 | border-bottom: 3px solid #c7ecee; 33 | } 34 | 35 | .nav a.active { 36 | border-bottom: 3px solid #c7ecee; 37 | } 38 | 39 | body { 40 | background-color: #30336b; 41 | } 42 | 43 | 44 | .line { 45 | fill: none; 46 | stroke: steelblue; 47 | stroke-width: 2px; 48 | } 49 | 50 | .grid line { 51 | stroke: lightgrey; 52 | stroke-opacity: 0.7; 53 | shape-rendering: crispEdges; 54 | } 55 | 56 | .grid path { 57 | stroke-width: 0; 58 | } 59 | 60 | #wrapper { 61 | padding-bottom: 5%; 62 | } 63 | 64 | #accordion { 65 | margin-top: 40px; 66 | } 67 | 68 | #map-wrapper { 69 | background-color: whitesmoke; 70 | box-shadow: 0 0 6px 2px rgba(0,0,0,.1); 71 | } 72 | 73 | #orders-wrapper { 74 | background-color: whitesmoke; 75 | box-shadow: 0 0 6px 2px rgba(0,0,0,.1); 76 | padding-top: 1%; 77 | padding-bottom: 5%; 78 | } 79 | 80 | #refresh-wrapper { 81 | padding-top: .5%; 82 | padding-bottom: .5%; 83 | display: inline-block; 84 | } 85 | 86 | #map { 87 | padding: 100px; 88 | display: inline-block; 89 | position: relative; 90 | width: 100%; 91 | padding-bottom: 100%; 92 | vertical-align: top; 93 | overflow: hidden; 94 | } 95 | 96 | .svg-content { 97 | display: inline-block; 98 | position: absolute; 99 | top: 0; 100 | left: 0; 101 | } 102 | 103 | .legend { 104 | font-weight: 300; 105 | font-family: "Work Sans"; 106 | font-size: 18px; 107 | font-style: oblique; 108 | } 109 | 110 | .cursortext { 111 | font-weight: 300; 112 | font-family: "Work Sans"; 113 | font-size: 12px; 114 | } 115 | 116 | .robotLabel { 117 | font-weight: 300; 118 | font-family: "Work Sans"; 119 | font-size: 10px; 120 | } -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/FleetManagerClient.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Net.Http; 7 | using System.Threading.Tasks; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.Extensions.Options; 10 | using Newtonsoft.Json; 11 | 12 | namespace RobotOrchestrator.OrderManager 13 | { 14 | public class FleetManagerClient : IFleetManagerClient 15 | { 16 | private static readonly HttpClient httpClient = new HttpClient(); 17 | 18 | private readonly string fleetManagerUrl; 19 | 20 | private readonly ILogger logger; 21 | 22 | public FleetManagerClient(IOptions options, ILogger logger) 23 | { 24 | var fleetManagerUrl = options?.Value.FleetManagerUrl; 25 | 26 | if (string.IsNullOrWhiteSpace(fleetManagerUrl)) 27 | { 28 | throw new ArgumentNullException("FleetManagerUrl is required."); 29 | } 30 | 31 | this.fleetManagerUrl = fleetManagerUrl; 32 | this.logger = logger; 33 | } 34 | 35 | public async Task GetRobot(string id) 36 | { 37 | var uri = $"{fleetManagerUrl}/{id}"; 38 | var robot = await GetHttpResponse(uri); 39 | 40 | logger.LogDebug("Got robot from fleet manager."); 41 | 42 | return robot; 43 | } 44 | 45 | public async Task> GetAvailableRobotsAsync() 46 | { 47 | logger.LogDebug("Getting available robots from fleet manager."); 48 | 49 | // Only find idle robots 50 | var robotIdleStatus = RobotStatus.Idle.ToString(); 51 | var uri = $"{fleetManagerUrl}?status={robotIdleStatus}"; 52 | var robots = await GetHttpResponse>(uri); 53 | 54 | logger.LogDebug("Got available robots from fleet manager."); 55 | 56 | return robots; 57 | } 58 | 59 | private async Task GetHttpResponse(string uri) 60 | { 61 | var httpResponseMessage = await httpClient.GetAsync(uri); 62 | var response = await httpResponseMessage.Content.ReadAsStringAsync(); 63 | 64 | var deserializedResponse = JsonConvert.DeserializeObject(response); 65 | 66 | return deserializedResponse; 67 | } 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /RobotOrchestrator/IotHubListener.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.Azure.EventHubs.Processor; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Options; 9 | 10 | namespace RobotOrchestrator 11 | { 12 | public class IotHubListener : IHostedService 13 | { 14 | private readonly IEventProcessorFactory iotHubEventProcessorFactory; 15 | private readonly IEventProcessorHostConfig eventProcessorHostConfig; 16 | private readonly EventProcessorOptions eventProcessorOptions; 17 | 18 | private EventProcessorHost eventProcessorHost; 19 | 20 | public IotHubListener( 21 | IEventProcessorFactory iotHubEventProcessorFactory, 22 | IEventProcessorHostConfig eventProcessorHostConfig, 23 | IOptions eventProcessorOptions) 24 | { 25 | this.iotHubEventProcessorFactory = iotHubEventProcessorFactory; 26 | this.eventProcessorHostConfig = eventProcessorHostConfig; 27 | this.eventProcessorOptions = eventProcessorOptions?.Value; 28 | } 29 | 30 | public async Task StartAsync(CancellationToken cancellationToken) 31 | { 32 | eventProcessorHost = new EventProcessorHost(eventProcessorHostConfig.HostName, 33 | eventProcessorHostConfig.EventHubPath, 34 | eventProcessorHostConfig.ConsumerGroupName, 35 | eventProcessorHostConfig.EventHubConnectionString, 36 | eventProcessorHostConfig.StorageConnectionString, 37 | eventProcessorHostConfig.LeaseContainerName); 38 | 39 | if (eventProcessorOptions == null) 40 | { 41 | await eventProcessorHost.RegisterEventProcessorFactoryAsync(iotHubEventProcessorFactory); 42 | } 43 | else 44 | { 45 | await eventProcessorHost.RegisterEventProcessorFactoryAsync(iotHubEventProcessorFactory, eventProcessorOptions); 46 | } 47 | } 48 | 49 | public async Task StopAsync(CancellationToken cancellationToken) 50 | { 51 | await eventProcessorHost.UnregisterEventProcessorAsync(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Robot/src/orchestrated_robot/scripts/telemetry_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. 5 | 6 | import rospy 7 | from datetime import datetime 8 | from nav_msgs.msg import Odometry 9 | from orchestrator_msgs.msg import Telemetry 10 | from geometry_msgs.msg import Pose 11 | from orchestrator_msgs.srv import RobotInfo 12 | 13 | class TelemetryController: 14 | 15 | def __init__(self): 16 | rospy.init_node('telemetry_controller', anonymous=True) 17 | 18 | self.telemetry_publisher = self.set_up_telemetry_publisher() 19 | self.pose_subscriber = self.set_up_pose_subscriber() 20 | self.currentPose = Pose() 21 | self.publish_latest_telemetry(self.telemetry_publisher) 22 | 23 | def set_up_telemetry_publisher(self): 24 | """ create publisher for fake jobs test messages """ 25 | pub = rospy.Publisher('telemetry', Telemetry, queue_size=10) 26 | return pub 27 | 28 | def set_up_pose_subscriber(self): 29 | """ create subscriber for pose messages """ 30 | sub = rospy.Subscriber('odom', Odometry, self.update_current_pose) 31 | return sub 32 | 33 | def update_current_pose(self, odom_data): 34 | self.currentPose = odom_data.pose.pose 35 | 36 | def publish_latest_telemetry(self, pub): 37 | 38 | # connect to robot controller service 39 | get_robot_info = rospy.ServiceProxy('get_robot_info', RobotInfo) 40 | 41 | while not rospy.is_shutdown(): 42 | utctime = datetime.utcnow().isoformat() 43 | 44 | telemetry = Telemetry() 45 | 46 | # get robot info from robot controller 47 | resp = get_robot_info() 48 | robotId = resp.RobotId 49 | robotStatus = resp.Status 50 | currentOrder = resp.OrderId 51 | 52 | # create current telemetry 53 | telemetry.OrderId = currentOrder 54 | telemetry.Position.X = self.currentPose.position.x 55 | telemetry.Position.Y = self.currentPose.position.y 56 | telemetry.id = utctime 57 | telemetry.Status = robotStatus 58 | telemetry.RobotId = robotId 59 | telemetry.CreatedDateTime = utctime 60 | 61 | #rospy.loginfo(telemetry) 62 | 63 | # publish 1 time per second 64 | rate = rospy.Rate(1) 65 | 66 | pub.publish(telemetry) 67 | rate.sleep() 68 | 69 | if __name__ == '__main__': 70 | 71 | rospy.wait_for_service('get_robot_info') 72 | telemetryController = TelemetryController() -------------------------------------------------------------------------------- /RobotOrchestrator.Dispatcher/Startup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Swashbuckle.AspNetCore.Swagger; 10 | 11 | namespace RobotOrchestrator.Dispatcher 12 | { 13 | public class Startup 14 | { 15 | public Startup(IConfiguration configuration) 16 | { 17 | Configuration = configuration; 18 | } 19 | 20 | public IConfiguration Configuration { get; } 21 | 22 | // This method gets called by the runtime. Use this method to add services to the container. 23 | public void ConfigureServices(IServiceCollection services) 24 | { 25 | services.AddSingleton(); 26 | services.AddSingleton(); 27 | 28 | services.Configure(Configuration); 29 | 30 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 31 | 32 | // add versioning, the default is 1.0 33 | services.AddApiVersioning(); 34 | 35 | // Register the Swagger generator, defining 1 or more Swagger documents 36 | services.AddSwaggerGen(c => 37 | { 38 | c.SwaggerDoc("v1", new Info { Title = "RobotOrchestrator.Dispatcher", Version = "v1" }); 39 | 40 | c.DescribeAllEnumsAsStrings(); 41 | }); 42 | } 43 | 44 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 45 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 46 | { 47 | if (env.IsDevelopment()) 48 | { 49 | app.UseDeveloperExceptionPage(); 50 | } 51 | else 52 | { 53 | app.UseHsts(); 54 | } 55 | 56 | app.UseHttpsRedirection(); 57 | 58 | // Enable middleware to serve generated Swagger as a JSON endpoint. 59 | app.UseSwagger(); 60 | 61 | // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), 62 | // specifying the Swagger JSON endpoint. 63 | app.UseSwaggerUI(c => 64 | { 65 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "RobotOrchestrator.Dispatcher V1"); 66 | }); 67 | 68 | app.UseMvc(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ui/src/RobotView.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as React from 'react'; 5 | import {Col, Grid, Row} from 'react-bootstrap'; 6 | import Configuration from './Configuration'; 7 | import Map from './Map'; 8 | import Robot from './Robot'; 9 | import RobotInfoPanel from './RobotInfoPanel'; 10 | import RobotManagerClient from './RobotManagerClient'; 11 | 12 | class RobotView extends React.Component { 13 | 14 | private static refreshInMs : number = +Configuration.refreshInMs; 15 | 16 | private robotManagerClient : RobotManagerClient; 17 | 18 | private interval : NodeJS.Timer; 19 | 20 | private isCancelled : boolean; 21 | 22 | constructor(props: any, context: any) { 23 | super(props, context); 24 | 25 | this.isCancelled = false; 26 | 27 | this.robotManagerClient = new RobotManagerClient(); 28 | 29 | this.onRefreshAsync = this.onRefreshAsync.bind(this); 30 | this.onActiveRobot = this.onActiveRobot.bind(this); 31 | 32 | this.state = { 33 | activeRobot : {}, 34 | robots : [] 35 | }; 36 | } 37 | 38 | public componentDidMount() { 39 | this.getRobotsAsync(); 40 | this.interval = setInterval(() => this.getRobotsAsync(), RobotView.refreshInMs); 41 | } 42 | 43 | public componentWillUnmount() { 44 | this.isCancelled = true; 45 | clearInterval(this.interval); 46 | } 47 | 48 | public async getRobotsAsync(){ 49 | await this.robotManagerClient.getRobotsAsync(); 50 | 51 | const robotsInfo : Robot[] = this.robotManagerClient.responseRobots; 52 | 53 | if(robotsInfo !== undefined && this.isCancelled === false){ 54 | this.setState({ 55 | robots : robotsInfo 56 | }); 57 | } 58 | } 59 | 60 | public async onRefreshAsync() { 61 | await this.getRobotsAsync(); 62 | } 63 | 64 | public onActiveRobot(robotId : string) { 65 | this.setState({ 66 | activeRobot : robotId 67 | }) 68 | } 69 | 70 | public render() { 71 | return ( 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | ) 83 | } 84 | } 85 | 86 | export default RobotView; -------------------------------------------------------------------------------- /ui/src/Configuration.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import axios, {AxiosError, AxiosResponse} from 'axios'; 5 | 6 | class Configuration 7 | { 8 | public static fleetManagerUrl : string = "localhost:44301"; 9 | public static fleetManagerVersion : string = "1"; 10 | 11 | public static orderManagerUrl : string = "localhost:44307"; 12 | public static orderManagerVersion : string = "1"; 13 | 14 | public static refreshInMs : number = 1000; 15 | 16 | public static mapWidthInPixels : string = "600"; 17 | public static mapHeightInPixels : string = "529"; 18 | 19 | public static marginsTop : string = "40"; 20 | public static marginsRight : string = "25"; 21 | public static marginsBottom : string = "30"; 22 | public static marginsLeft : string = "25"; 23 | 24 | public static mapWidthInMeters : string = "63.9000"; 25 | public static mapHeightInMeters : string = "56.3000"; 26 | 27 | public static async initialize() 28 | { 29 | await axios.get("configuration.json").then(this.handleJsonFileResponse).catch(this.handleError); 30 | } 31 | 32 | public static handleJsonFileResponse = (response : AxiosResponse) => 33 | { 34 | Configuration.jsonResponse = response.data as Map; 35 | 36 | const fleetManagerUrlKey = "REACT_APP_FLEETMANAGER_URL"; 37 | const orderManagerUrlKey = "REACT_APP_ORDERMANAGER_URL"; 38 | 39 | if (Configuration.jsonResponse == null) 40 | { 41 | return; 42 | } 43 | 44 | const fleetManagerUrl = Configuration.jsonResponse[fleetManagerUrlKey]; 45 | const orderManagerUrl = Configuration.jsonResponse[orderManagerUrlKey]; 46 | 47 | if (fleetManagerUrl != null) 48 | { 49 | Configuration.fleetManagerUrl = Configuration.jsonResponse[fleetManagerUrlKey]; 50 | console.log("fleet manager url: " + Configuration.fleetManagerUrl); 51 | } 52 | 53 | if (orderManagerUrl != null) 54 | { 55 | Configuration.orderManagerUrl = Configuration.jsonResponse[orderManagerUrlKey]; 56 | console.log("order manager url: " + Configuration.orderManagerUrl); 57 | } 58 | } 59 | 60 | private static jsonResponse: Map; 61 | 62 | private static handleError = (error : AxiosError) => { 63 | if(error.response) 64 | { 65 | console.log(error.response.data); 66 | console.log(error.response.status); 67 | console.log(error.response.headers); 68 | } 69 | else{ 70 | console.log(error.message); 71 | } 72 | } 73 | } 74 | 75 | export default Configuration; -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/IotHubRegistryClient.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Threading.Tasks; 6 | using Microsoft.Azure.Devices; 7 | using Microsoft.Azure.Devices.Common.Exceptions; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.Extensions.Options; 10 | 11 | namespace RobotOrchestrator.FleetManager 12 | { 13 | public class IotHubRegistryClient : IIotHubRegistryClient 14 | { 15 | private readonly RegistryManager registryManager; 16 | private readonly string hostName; 17 | private readonly ILogger loggerFleetManager; 18 | 19 | public IotHubRegistryClient(IOptions options, ILogger loggerFleetManager) 20 | : this (options?.Value.IotHubRegistryConnectionString, loggerFleetManager) 21 | { 22 | } 23 | 24 | public IotHubRegistryClient(string connectionString, ILogger loggerFleetManager) 25 | { 26 | this.loggerFleetManager = loggerFleetManager; 27 | hostName = GetHostName(connectionString); 28 | registryManager = RegistryManager.CreateFromConnectionString(connectionString); 29 | } 30 | 31 | public static string GetHostName(string connectionString) 32 | { 33 | int hostNameLength = connectionString.IndexOf(";"); 34 | string hostName = connectionString.Substring(0, hostNameLength); 35 | return hostName; 36 | } 37 | 38 | public async Task AddDeviceAsync(string deviceId) 39 | { 40 | Device device; 41 | 42 | try 43 | { 44 | device = await registryManager.AddDeviceAsync(new Device(deviceId)); 45 | } 46 | catch (DeviceAlreadyExistsException) 47 | { 48 | // If the device already exists, simply retrieve its info 49 | device = await registryManager.GetDeviceAsync(deviceId); 50 | } 51 | 52 | string primaryKey = device.Authentication.SymmetricKey.PrimaryKey; 53 | 54 | string connectionString = GetDeviceConnectionString(deviceId, primaryKey); 55 | 56 | return connectionString; 57 | } 58 | 59 | public async Task DeleteDeviceAsync(string deviceId) 60 | { 61 | await registryManager.RemoveDeviceAsync(deviceId); 62 | } 63 | 64 | public string GetDeviceConnectionString(string deviceId, string key) 65 | { 66 | string connectionString = String.Format("{0};DeviceId={1};SharedAccessKey={2}", hostName, deviceId, key); 67 | return connectionString; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/Startup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Swashbuckle.AspNetCore.Swagger; 10 | 11 | namespace RobotOrchestrator.OrderProducer 12 | { 13 | public class Startup 14 | { 15 | public Startup(IConfiguration configuration) 16 | { 17 | Configuration = configuration; 18 | } 19 | 20 | public IConfiguration Configuration { get; } 21 | 22 | // This method gets called by the runtime. Use this method to add services to the container. 23 | public void ConfigureServices(IServiceCollection services) 24 | { 25 | services.AddSingleton(); 26 | services.AddSingleton(); 27 | services.AddSingleton(); 28 | 29 | string uri = Configuration.GetValue("OrderManagerUrl"); 30 | services.AddSingleton(new OrderManagerClient(uri)); 31 | 32 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 33 | 34 | // add versioning, the default is 1.0 35 | services.AddApiVersioning(); 36 | 37 | // Register the Swagger generator, defining 1 or more Swagger documents 38 | services.AddSwaggerGen(c => 39 | { 40 | c.SwaggerDoc("v1", new Info { Title = "RobotOrchestrator.OrderProducer", Version = "v1" }); 41 | 42 | c.DescribeAllEnumsAsStrings(); 43 | }); 44 | } 45 | 46 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 47 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 48 | { 49 | if (env.IsDevelopment()) 50 | { 51 | app.UseDeveloperExceptionPage(); 52 | } 53 | else 54 | { 55 | app.UseHsts(); 56 | } 57 | 58 | app.UseHttpsRedirection(); 59 | 60 | // Enable middleware to serve generated Swagger as a JSON endpoint. 61 | app.UseSwagger(); 62 | 63 | // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), 64 | // specifying the Swagger JSON endpoint. 65 | app.UseSwaggerUI(c => 66 | { 67 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "RobotOrchestrator.OrderProducer V1"); 68 | }); 69 | 70 | app.UseMvc(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ui/src/App.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as React from 'react'; 5 | import { Nav, Navbar, NavItem } from 'react-bootstrap' 6 | import { NavLink, Redirect, Route, Switch } from 'react-router-dom'; // import the react-router-dom components 7 | import './App.css'; 8 | import Configuration from './Configuration'; 9 | import { Orders, Visualization } from './Pages' 10 | 11 | class App extends React.Component{ 12 | 13 | constructor(props: any, context: any) { 14 | super(props, context); 15 | 16 | this.handleSelect = this.handleSelect.bind(this); 17 | 18 | this.state = { 19 | configLoaded: false, 20 | key: 1 21 | }; 22 | } 23 | 24 | public componentWillMount() { 25 | Configuration.initialize().then(() => { 26 | this.setState({ configLoaded: true }); 27 | }); 28 | } 29 | 30 | public render() { 31 | return ( 32 |
33 | 34 | {this.state && this.state.configLoaded && 35 | 36 | } 37 |
38 | ); 39 | } 40 | 41 | private handleSelect(key: any) { 42 | this.setState({ key }); 43 | } 44 | 45 | private Header = () => ( 46 | 47 | 48 | 49 | Ros-Orchestrator 50 | 51 | 52 | 53 | 54 | 62 | 67 | 68 | 69 | ) 70 | 71 | private Main = () => ( 72 |
73 | 74 | 75 | 76 | 77 | 78 |
79 | ); 80 | } 81 | 82 | const RedirectVisualization = () => { 83 | return 84 | } 85 | 86 | export default App; 87 | -------------------------------------------------------------------------------- /Robot/src/orchestrated_robot/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | orchestrated_robot 4 | 0.0.0 5 | The orchestrated_robot package 6 | 7 | 8 | 9 | 10 | RosSimulationTeam 11 | 12 | 13 | 14 | 15 | MIT 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | catkin 51 | orchestrator_msgs 52 | orchestrator_msgs 53 | actionlib 54 | move_base 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/Controllers/OrdersController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | using System.Linq; 8 | using Microsoft.AspNetCore.Cors; 9 | using Microsoft.AspNetCore.Mvc; 10 | 11 | namespace RobotOrchestrator.OrderManager.Controllers 12 | { 13 | [Route("api/v{version:apiVersion}/[controller]")] 14 | [ApiController] 15 | public class OrdersController : ControllerBase 16 | { 17 | private readonly IOrderManager orderManager; 18 | 19 | public OrdersController(IOrderManager orderManager) 20 | { 21 | this.orderManager = orderManager; 22 | } 23 | 24 | [HttpGet] 25 | [EnableCors("AllowAllOrigin")] 26 | public async Task>> GetOrdersAsync(OrderStatus? status, int? numOrders) 27 | { 28 | IEnumerable orders; 29 | 30 | try 31 | { 32 | orders = await orderManager.GetOrdersAsync(status, numOrders); 33 | } 34 | catch (Exception ex) 35 | { 36 | var badObjectResult = new BadRequestObjectResult(ex.Message); 37 | return badObjectResult; 38 | } 39 | 40 | var result = new OkObjectResult(orders); 41 | return result; 42 | } 43 | 44 | [HttpGet("{id}")] 45 | public async Task> GetOrderAsync(Guid id) 46 | { 47 | Order order; 48 | 49 | try 50 | { 51 | order = await orderManager.GetOrderAsync(id); 52 | } 53 | catch (RecordNotFoundException) 54 | { 55 | var notFoundResult = new NotFoundResult(); 56 | return notFoundResult; 57 | } 58 | catch (Exception ex) 59 | { 60 | var badObjectResult = new BadRequestObjectResult(ex.Message); 61 | return badObjectResult; 62 | } 63 | 64 | var result = new OkObjectResult(order); 65 | return result; 66 | } 67 | 68 | [HttpPost()] 69 | public async Task PostOrderAsync([FromBody] Order order, string robotId = null) 70 | { 71 | order = await orderManager.AcceptOrderAsync(order, robotId); 72 | 73 | var result = new OkObjectResult(order); 74 | 75 | return result; 76 | } 77 | 78 | [HttpPost("batch")] 79 | public async Task>> PostOrdersAsync([FromBody] IEnumerable orders) 80 | { 81 | orders = await orderManager.AcceptOrdersAsync(orders); 82 | 83 | var orderIds = orders.Select(order => order.Id); 84 | 85 | var result = Ok(orderIds); 86 | return result; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Docs/CICD.md: -------------------------------------------------------------------------------- 1 | # Continuous Integration and Deployment 2 | 3 | ## Continuous Integration 4 | 5 | The CI build pipelines are provided for the web apis and the UI in this project. 6 | 7 | - [RobotOrchestrator.OrderProducer/azure-pipelines.yml](../RobotOrchestrator.OrderProducer/azure-pipelines.yml) 8 | - [RobotOrchestrator.OrderManager/azure-pipelines.yml](../RobotOrchestrator.OrderManager/azure-pipelines.yml) 9 | - [RobotOrchestrator.FleetManager/azure-pipelines.yml](../RobotOrchestrator.FleetManager/azure-pipelines.yml) 10 | - [RobotOrchestrator.Dispatcher/azure-pipelines.yml](../RobotOrchestrator.Dispatcher/azure-pipelines.yml) 11 | - [ui/azure-pipelines.yml](../ui/azure-pipelines.yml) 12 | 13 | For more information on yaml builds, see [YAML Schema Reference](https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts). In the azure-pipelines.yml files for this project, the build will automatically be triggered by changes to the master branch. 14 | 15 | For each of the APIs and UI projects, create a new Build Pipeline and reference the corresponding yaml file 16 | 17 | Reference Yaml File 18 | 19 | ## Continuous Deployment 20 | 21 | In order to create the Continuous Deployment releases in Azure DevOps, use the Azure Web App Deploy Task for the Web APIs. See the official docs [here](https://docs.microsoft.com/en-us/azure/devops/pipelines/apps/cd/deploy-webdeploy-webapps?view=vsts). 22 | 23 | - Create a New Pipeline. 24 | 25 | Create Release Pipeline 26 | 27 | - Select "Azure App Service Deployment" template, and name stage (i.e. 'Dev') 28 | - Open 'Tasks', and fill out required fields. Select your Azure Subscription, and the name of the App Service you want to deploy the app to. 29 | 30 | Edit Tasks 31 | 32 | - Edit the fields in the "Deploy Azure App Service" task as shown. Be sure to select the Package from your CI build. 33 | 34 | Edit Azure Deploy Task 35 | 36 | - Save and Queue your Release. It will automatically pick up the latest _ReactApp-CI artifact. To test a release for a specific artifact, find the specific build in your CI History, and click 'Release'. 37 | - Repeat these steps for all Web APIs (OrderProducer, OrderManager, FleetManager, and Dispatcher) 38 | 39 | ### React App Specific Steps 40 | 41 | - Follow the steps above for the react app deployment, with the following steps as well 42 | - Edit the Azure App Service Deploy task for variable substitution in the file configuration.json. This will replace the variables in the json file with the variables in the next step during deployment 43 | 44 | Json Variable Substitution 45 | 46 | - Add variables to the release as shown below to point at your APIs 47 | 48 | Release Variables -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/JobEventProcessor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Microsoft.Azure.EventHubs; 9 | using Microsoft.Azure.EventHubs.Processor; 10 | using Microsoft.Extensions.Logging; 11 | using Newtonsoft.Json; 12 | 13 | namespace RobotOrchestrator.OrderManager 14 | { 15 | public class JobEventProcessor : IEventProcessor 16 | { 17 | private readonly IJobMessageHandler jobMessageHandler; 18 | 19 | private readonly ILogger logger; 20 | 21 | public JobEventProcessor(IJobMessageHandler jobMessageHandler, ILogger logger) 22 | { 23 | this.jobMessageHandler = jobMessageHandler; 24 | this.logger = logger; 25 | } 26 | 27 | public Task CloseAsync(PartitionContext context, CloseReason reason) 28 | { 29 | logger.LogDebug($"JobEventProcessor Shutting Down. Partition '{context.PartitionId}', Reason: '{reason}'."); 30 | return Task.CompletedTask; 31 | } 32 | 33 | public Task OpenAsync(PartitionContext context) 34 | { 35 | logger.LogDebug($"JobEventProcessor initialized. Partition: '{context.PartitionId}'"); 36 | return Task.CompletedTask; 37 | } 38 | 39 | public Task ProcessErrorAsync(PartitionContext context, Exception error) 40 | { 41 | logger.LogError($"Error on Partition: {context.PartitionId}, Error: {error.Message}"); 42 | return Task.CompletedTask; 43 | } 44 | 45 | public async Task ProcessEventsAsync(PartitionContext context, IEnumerable messages) 46 | { 47 | var jobMessages = new List(); 48 | 49 | foreach (var eventData in messages) 50 | { 51 | var data = Encoding.UTF8.GetString(eventData.Body.Array, eventData.Body.Offset, eventData.Body.Count); 52 | 53 | logger.LogDebug($"Message received. Partition: '{context.PartitionId}', Data: '{data}'"); 54 | 55 | var job = ConvertToJob(data); 56 | 57 | if (job != null) 58 | { 59 | jobMessages.Add(job); 60 | } 61 | } 62 | 63 | await jobMessageHandler.UpdateOrdersAsync(jobMessages); 64 | 65 | await context.CheckpointAsync(); 66 | } 67 | 68 | private Job ConvertToJob(string rawData) 69 | { 70 | try 71 | { 72 | var rosMessage = JsonConvert.DeserializeObject>(rawData); 73 | var job = rosMessage.Payload; 74 | 75 | return job; 76 | } 77 | catch (Exception ex) 78 | { 79 | logger.LogError("Failed to deserialize telemetry message.", ex); 80 | } 81 | 82 | return null; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/TelemetryEventProcessor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Microsoft.Azure.EventHubs; 9 | using Microsoft.Azure.EventHubs.Processor; 10 | using Microsoft.Extensions.Logging; 11 | using Newtonsoft.Json; 12 | 13 | namespace RobotOrchestrator.FleetManager 14 | { 15 | public class TelemetryEventProcessor : IEventProcessor 16 | { 17 | private readonly IFleetManager fleetManager; 18 | private readonly ILogger logger; 19 | 20 | public TelemetryEventProcessor(IFleetManager fleetManager, ILogger logger) 21 | { 22 | this.fleetManager = fleetManager; 23 | this.logger = logger; 24 | } 25 | 26 | public Task CloseAsync(PartitionContext context, CloseReason reason) 27 | { 28 | logger.LogDebug($"TelemetryEventProcessor Shutting Down. Partition '{context.PartitionId}', Reason: '{reason}'."); 29 | return Task.CompletedTask; 30 | } 31 | 32 | public Task OpenAsync(PartitionContext context) 33 | { 34 | logger.LogDebug($"TelemetryEventProcessor initialized. Partition: '{context.PartitionId}'"); 35 | return Task.CompletedTask; 36 | } 37 | 38 | public Task ProcessErrorAsync(PartitionContext context, Exception error) 39 | { 40 | logger.LogError($"Error on Partition: {context.PartitionId}, Error: {error.Message}"); 41 | return Task.CompletedTask; 42 | } 43 | 44 | public async Task ProcessEventsAsync(PartitionContext context, IEnumerable messages) 45 | { 46 | var telemetryMessages = new List(); 47 | 48 | foreach (var eventData in messages) 49 | { 50 | var data = Encoding.UTF8.GetString(eventData.Body.Array, eventData.Body.Offset, eventData.Body.Count); 51 | 52 | logger.LogDebug($"Message received. Partition: '{context.PartitionId}', Data: '{data}'"); 53 | 54 | var telemetry = ConvertToRobotTelemetry(data); 55 | 56 | if (telemetry != null) 57 | { 58 | telemetryMessages.Add(telemetry); 59 | } 60 | } 61 | 62 | await fleetManager.InsertTelemetriesAndUpdateRobotsAsync(telemetryMessages); 63 | 64 | await context.CheckpointAsync(); 65 | } 66 | 67 | private RobotTelemetry ConvertToRobotTelemetry(string rawData) 68 | { 69 | try 70 | { 71 | var rosMessage = JsonConvert.DeserializeObject>(rawData); 72 | var telemetry = rosMessage.Payload; 73 | 74 | return telemetry; 75 | } 76 | catch (Exception ex) 77 | { 78 | logger.LogError("Failed to deserialize telemetry message.", ex); 79 | } 80 | 81 | return null; 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT License. 3 | 4 | version: '3' 5 | networks: 6 | shared_network: 7 | driver: bridge 8 | services: 9 | simulator: 10 | image: "simulator-orch" 11 | build: 12 | context: ./Robot/ 13 | dockerfile: Dockerfile-simulator 14 | environment: 15 | - ROS_MASTER_URI=http://localhost:11311 16 | - ROBOT_PREFIX=hackbot 17 | - ROBOT_TARGET_PREFIX=robot-nimbro- 18 | - ROBOT_COUNT=2 19 | - SIMULATOR_THREAD_COUNT=4 20 | - VNC_PW=vncpassword 21 | command: ["/bin/bash", "-c", "source devel/setup.bash && roslaunch argos_bridge simulation.launch 22 | robot_count:=$${ROBOT_COUNT} simulator_thread_count:=$${SIMULATOR_THREAD_COUNT} 23 | port_base:=17000 robot_target_prefix:=$${ROBOT_TARGET_PREFIX} robot_prefix:=$${ROBOT_PREFIX} 24 | argos_world_file:=construct.argos"] 25 | ports: 26 | - "17000-17005" 27 | networks: 28 | shared_network: 29 | aliases: 30 | - "simulator-nimbro-0" 31 | - "simulator-nimbro-1" 32 | robot-0: 33 | image: "robot-orch" 34 | build: 35 | context: ./Robot/ 36 | dockerfile: Dockerfile-robot 37 | environment: 38 | - ROS_MASTER_URI=http://localhost:11311 39 | - ROBOT_ID=hackbot0 40 | - SIMULATOR_NIMBRO_TARGET=simulator-nimbro-0 41 | - SIMULATOR_NIMBRO_PORT=17000 42 | - VERBOSE="True" 43 | - FLEET_MANAGER_URL=robot-fleetmanager-dev.azurewebsites.net 44 | - FLEET_MANAGER_VERSION=1 45 | command: ["/bin/bash", "-c", "source devel/setup.bash && 46 | roslaunch orchestrated_robot robot.launch 47 | fleetmanager_url:=$${FLEET_MANAGER_URL} 48 | robot_name:=$${ROBOT_ID} 49 | fleetmanager_version:=$${FLEET_MANAGER_VERSION} 50 | simulator_nimbro_target:=$${SIMULATOR_NIMBRO_TARGET} 51 | simulator_nimbro_port:=$${SIMULATOR_NIMBRO_PORT}"] 52 | ports: 53 | - "17000" 54 | networks: 55 | shared_network: 56 | aliases: 57 | - "robot-nimbro-0" 58 | robot-1: 59 | image: "robot-orch" 60 | build: 61 | context: ./Robot/ 62 | dockerfile: Dockerfile-robot 63 | environment: 64 | - ROS_MASTER_URI=http://localhost:11311 65 | - ROBOT_ID=hackbot1 66 | - SIMULATOR_NIMBRO_TARGET=simulator-nimbro-1 67 | - SIMULATOR_NIMBRO_PORT=17001 68 | - VERBOSE="True" 69 | - FLEET_MANAGER_URL=robot-fleetmanager-dev.azurewebsites.net 70 | - FLEET_MANAGER_VERSION=1 71 | command: ["/bin/bash", "-c", "source devel/setup.bash && 72 | roslaunch orchestrated_robot robot.launch 73 | fleetmanager_url:=$${FLEET_MANAGER_URL} 74 | robot_name:=$${ROBOT_ID} 75 | fleetmanager_version:=$${FLEET_MANAGER_VERSION} 76 | simulator_nimbro_target:=$${SIMULATOR_NIMBRO_TARGET} 77 | simulator_nimbro_port:=$${SIMULATOR_NIMBRO_PORT}"] 78 | ports: 79 | - "17000" 80 | networks: 81 | shared_network: 82 | aliases: 83 | - "robot-nimbro-1" -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/JobMessageHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Logging; 8 | using Microsoft.Azure.Documents; 9 | 10 | namespace RobotOrchestrator.OrderManager 11 | { 12 | public class JobMessageHandler : IJobMessageHandler 13 | { 14 | private readonly ICosmosDbClient ordersDbClient; 15 | 16 | private readonly ILogger logger; 17 | 18 | public JobMessageHandler(ICosmosDbClient ordersDbClient, ILogger logger) 19 | { 20 | this.ordersDbClient = ordersDbClient; 21 | this.logger = logger; 22 | } 23 | 24 | public async Task UpdateOrdersAsync(IEnumerable jobs) 25 | { 26 | // TO-DO: parallelize and make more performant 27 | foreach (var job in jobs) 28 | { 29 | await UpdateOrderAsync(job); 30 | } 31 | } 32 | 33 | public async Task UpdateOrderAsync(Job job) 34 | { 35 | try 36 | { 37 | await GetAndUpdateOrderWithJobAsync(job); 38 | } 39 | catch (Exception ex) 40 | { 41 | logger.LogError(ex.Message, ex); 42 | } 43 | } 44 | 45 | private async Task GetAndUpdateOrderWithJobAsync(Job job) 46 | { 47 | var orderId = job.OrderId.ToString(); 48 | var partitionKey = new PartitionKey(orderId); 49 | var order = await ordersDbClient.GetItemAsync(orderId, partitionKey); 50 | 51 | // TO-DO: Store jobs in data structure that is easier for access and update 52 | var index = order.Jobs.FindIndex(j => j.Id == job.Id); 53 | 54 | if (index != -1) { 55 | order.Jobs[index] = job; 56 | 57 | order = UpdateOrderStatus(order, job); 58 | 59 | await ordersDbClient.UpdateItemAsync(orderId, order, partitionKey); 60 | } else { 61 | logger.LogError($"Job Id not found in Order: { orderId }."); 62 | } 63 | } 64 | 65 | private Order UpdateOrderStatus(Order order, Job job) 66 | { 67 | switch (job.Status) 68 | { 69 | case JobStatus.Queued: 70 | order.Status = OrderStatus.InProgress; 71 | break; 72 | case JobStatus.InProgress: 73 | order.Status = OrderStatus.InProgress; 74 | break; 75 | case JobStatus.Complete: 76 | order.Status = OrderStatus.Complete; 77 | break; 78 | case JobStatus.Failed: 79 | order.Status = OrderStatus.Failed; 80 | break; 81 | default: 82 | throw new NotSupportedException($"Job Status {job.Status} is not supported in OrderStatus conversion."); 83 | } 84 | 85 | return order; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Robot/src/argos_bridge/argos_worlds/construct.argos: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 92 | 93 | -------------------------------------------------------------------------------- /Docs/RunLocally.md: -------------------------------------------------------------------------------- 1 | # Run Locally 2 | 3 | ## Pre-requisites 4 | 5 | While it is possible to run certain parts of the project locally for debugging, the orchestrator requires Azure resources, such as Key Vault, IotHub, EventHub, and Azure Storage. Please follow the steps in the [Provisioning](./Provisioning.md) before attempting to run any component locally. 6 | 7 | ## Web APIs 8 | 9 | Open the [RobotOrchestrator.sln](./../RobotOrchestrator.sln) in Visual Studio, and run build. Then set **Multiple Start Up Projects**: 10 | 11 | - RobotOrchestrator.OrderProducer 12 | - RobotOrchestrator.FleetManager 13 | - RobotOrchestrator.OrderManager 14 | - RobotOrchestrator.Dispatcher 15 | 16 | Add an **appsettings.Local.json** file to each of the project folders above, with the following content: 17 | 18 | ```json 19 | { 20 | "Logging": { 21 | "LogLevel": { 22 | "Default": "Debug", 23 | "System": "Information", 24 | "Microsoft": "Information" 25 | } 26 | }, 27 | "Vault": "", //e.g. robot-orch-kv-dev 28 | "ClientId": "", 29 | "ClientSecret": "" 30 | } 31 | ``` 32 | 33 | Reference the **Client Id** and **Client Secret** you created in [Provisioning](./Provisioning.md). This will pull various secrets needed to run your application, such as IotHub Connection Strings, EventHub ConnectionStrings, Storage ConnectionStrings, and more. 34 | 35 | ## UI 36 | 37 | - Run the following 38 | 39 | ```bash 40 | cd ui 41 | npm install 42 | npm run build 43 | npm run start 44 | ``` 45 | 46 | A window will open with the UI running locally. By default it will point to your local web api services. See section above on running [web apis locally](#Web-APIs). 47 | 48 | To use azure services instead, set the corresponding environment variables or replace the strings ("localhost:44301", "localhost:44307") in [../ui/public/configuration.json](../ui/public/configuration.json) with the URLs for the services in the Azure Portal for the fleet manager and order manager. Please note, you have to remove "https://" in the URLs and get the URLs like "fleetmanager.azurewebsites.net" and "ordermanager.azurewebsites.net". 49 | 50 | ### Debug UI Locally 51 | 52 | - For debugging the UI locally we recommend following the steps laid out at [this link](https://code.visualstudio.com/docs/nodejs/reactjs-tutorial#_debugging-react). 53 | 54 | ## Simulator and Robots 55 | 56 | Follow the steps in the Ros-Simulation repo for building the `robot` and `simulator` images locally. They will serve as the base images for the orchestrator project. 57 | 58 | To run locally, run the following command from the root directory of the project: 59 | 60 | ```bash 61 | docker-compose up -d 62 | ``` 63 | 64 | This will build the orchestrator images robot-orch and simulator-orch the first time the command is run on your environment. To force a rebuild of the orchestrator images, run: 65 | 66 | ```bash 67 | docker-compose build 68 | ``` 69 | 70 | A simulator and two robots will be created, hackbot0 and hackbot1. 71 | 72 | To see/follow the logs for each container, run: 73 | 74 | ```bash 75 | docker-compose logs -f simulator 76 | docker-compose logs -f robot-0 77 | docker-compose logs -f robot-1 78 | ``` 79 | 80 | To execute commands in the containers, run: 81 | 82 | ```bash 83 | docker-compose exec simulator /bin/bash 84 | docker-compose exec robot-0 /bin/bash 85 | docker-compose exec robot-1 /bin/bash 86 | ``` -------------------------------------------------------------------------------- /ui/src/OrderTable.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as React from 'react'; 5 | import {Button, Col, Glyphicon, Grid, Row} from 'react-bootstrap'; 6 | import * as ReactDataGrid from 'react-data-grid'; 7 | import EmptyRowsView from "./EmptyTable"; 8 | import Order from './Order'; 9 | import OrderManagerClient from "./OrderManagerClient"; 10 | import Position2d from './Position2d'; 11 | 12 | class OrderTable extends React.Component { 13 | public state: any; 14 | 15 | private columns: any; 16 | 17 | private orders : Order[]; 18 | private orderManagerClient : OrderManagerClient; 19 | 20 | constructor(props: any, context: any) { 21 | super(props, context); 22 | 23 | this.columns = []; 24 | 25 | this.state = {rows : []}; 26 | 27 | this.orderManagerClient = new OrderManagerClient(); 28 | 29 | this.columns = [ 30 | { key: 'Id', name: 'ID', resizable: true }, 31 | { key: 'StartPosition', name: 'StartPosition', resizable: true }, 32 | { key: 'EndPosition', name: 'EndPosition', resizable: true }, 33 | { key: 'Status', name: 'Status', resizable: true }, 34 | { key: 'CreatedDateTime', name: 'CreatedDateTime', resizable: true }, 35 | { key: 'Message', name: 'Message', resizable: true } 36 | ]; 37 | 38 | this.onclickAsync = this.onclickAsync.bind(this); 39 | } 40 | 41 | public rowGetter = (i : number) => { 42 | return this.state.rows[i]; 43 | }; 44 | 45 | public async onclickAsync(){ 46 | await this.getOrdersAsync(); 47 | this.createRows(); 48 | } 49 | 50 | public render() { 51 | return ( 52 | 53 | 54 | 55 | 56 | 60 | 61 | 62 |
63 | 69 |
70 | 71 |
72 |
73 | ); 74 | } 75 | 76 | private async getOrdersAsync(){ 77 | await this.orderManagerClient.getOrdersAsync(); 78 | const orders : Order[] = this.orderManagerClient.response; 79 | 80 | this.orders = orders; 81 | } 82 | 83 | private createRows = () => { 84 | const rows = []; 85 | 86 | if(null != this.orders){ 87 | for (const order of this.orders) { 88 | rows.push({ 89 | CreatedDateTime : order.createdDateTime.toString(), 90 | EndPosition : this.formatPosition(order.endPosition), 91 | Id: order.id.toString(), 92 | Message : order.message, 93 | StartPosition : this.formatPosition(order.startPosition), 94 | Status : order.status 95 | }); 96 | } 97 | } 98 | 99 | this.setState({rows}) 100 | }; 101 | 102 | private formatPosition(position : Position2d) { 103 | return `X: ${position.x.toFixed(3)}, Y: ${position.y.toFixed(3)}` 104 | } 105 | } 106 | 107 | export default OrderTable; -------------------------------------------------------------------------------- /Docs/Provisioning.md: -------------------------------------------------------------------------------- 1 | # Provisioning 2 | 3 | The Azure resources for the orchestrator are provisioned through ARM templates. 4 | 5 | ## Create a resource group 6 | 7 | ```bash 8 | az login 9 | az group create -n -l 10 | ``` 11 | 12 | Save the Resource Group Name for the next steps. 13 | 14 | ## Create a service principal 15 | 16 | This service principal will be used by the web apis to access secrets stored in Key Vault. For more details on how to create a service principal, see the [official docs](https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?view=azure-cli-latest). 17 | 18 | ```bash 19 | az login 20 | az ad sp create-for-rbac -n --skip-assignment 21 | ``` 22 | 23 | Save the Client Id (also known as App Id) and Client Secret for the service principal for the next steps. 24 | 25 | ## Deploy Key Vault 26 | 27 | First, edit the envName in the command below to be something unique (such as envName="myprojdev"). The envName must only be alphanumeric. Since these values are concatenated to form globally unique values, do not set envName to be "dev" or "prod", since those will likely be taken. 28 | 29 | The same env name must be used later in the [Deploy All Other Azure Resources](#Deploy-All-Other-Azure-Resources) section below. 30 | 31 | ```bash 32 | cd Provisioning 33 | az group deployment create \ 34 | -n keyvaultdeploy -g \ 35 | --template-file keyvault.json \ 36 | --parameters keyvault.parameters.json \ 37 | --parameters envName= \ 38 | --query 'properties.outputs' 39 | ``` 40 | 41 | The command above will deploy the following resources: 42 | 43 | - Key Vault 44 | 45 | You should see the output, which will tell you the final keyVaultName (based on your chosen envName): 46 | 47 | ```bash 48 | { 49 | "keyVaultName": { 50 | "type": "String", 51 | "value": "" 52 | } 53 | } 54 | ``` 55 | 56 | Then, in order to add the access policy for the service principal, run: 57 | 58 | ```bash 59 | az keyvault set-policy --name --spn --secret-permissions "list" "set" "get" 60 | ``` 61 | 62 | **Find the `` from the output of the key vault deploy or look in the portal, and get the service principal client id, or app id, from the step above.** 63 | 64 | Key vault is deployed in a separate ARM template because of a current restriction that access policies defined in the template will override manual access policies set in the portal. 65 | 66 | Because of this, we recommend only deploying this template once at the beginning of the project, if you have set any manual access policies. 67 | 68 | ## Deploy All Other Azure Resources 69 | 70 | First, edit the env name in the command below to be the same as the envName used in the step above. 71 | 72 | ```bash 73 | cd Provisioning 74 | az group deployment create \ 75 | -n orchestratordeploy -g \ 76 | --template-file azuredeploy.json \ 77 | --parameters azuredeploy.parameters.json \ 78 | --parameters keyVaultClientId= \ 79 | --parameters keyVaultClientSecret= \ 80 | --parameters envName= 81 | ``` 82 | 83 | This command will deploy the following resources: 84 | 85 | - IotHub 86 | - EventHub Namespace 87 | - EventHub for Fleet Manager Routing 88 | - EventHub for Order Manager Routing 89 | - Azure Container Registry (ACR) 90 | - Storage Account 91 | - Cosmos DB Account 92 | - App Service Plan (Windows) 93 | - Web Apps 94 | - Order Producer 95 | - Order Manager 96 | - Fleet Manager 97 | - Dispatcher 98 | - UI 99 | - App Settings Configs for All Web Apps 100 | - Key Vault Secrets needed for apps to run 101 | 102 | It may take around 10 minutes to deploy. 103 | 104 | The azuredeploy.json ARM template is safe to deploy as many times as needed, and changes will be automatically detected and deployed. Take care that app settings should be set only in the arm template, and manual app settings will be overridden by the template. 105 | 106 | The default modifier for the project is the envName 'dev'. If deploying to a second environment, you can add an optional `--parameters envName=roboprod` or other envName to the last two steps to recreate the entire environment. -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager.Tests/FleetManagerTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.Azure.Documents; 5 | using Microsoft.Extensions.Logging; 6 | using Moq; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Threading.Tasks; 10 | using Xunit; 11 | 12 | namespace RobotOrchestrator.FleetManager.Tests 13 | { 14 | public class FleetManagerTests 15 | { 16 | private Mock mockIotClient; 17 | private Mock> mockCosmosClientRobot; 18 | private Mock> mockCosmosClientTelemetry; 19 | private ITelemetryHandler telemetryHandler; 20 | private Mock> mockLogger; 21 | 22 | public FleetManagerTests() 23 | { 24 | mockIotClient = new Mock(); 25 | mockCosmosClientRobot = new Mock>(); 26 | mockCosmosClientTelemetry = new Mock>(); 27 | telemetryHandler = new TelemetryHandler(mockCosmosClientTelemetry.Object, Mock.Of>()); 28 | mockLogger = new Mock>(); 29 | } 30 | 31 | /// 32 | /// Verifies that if an exception is thrown while updating robots, 33 | /// we will attempt to update all robots and not bail out 34 | /// 35 | /// 36 | /// 37 | [Theory] 38 | [InlineData(6)] 39 | public async Task TestInsertTelemetryAndUpdateRobotsAsync_UpdateRobot_ExpectedExecutions(int numItems) 40 | { 41 | mockCosmosClientRobot.Setup(x => 42 | x.UpdateItemAsync(It.IsAny(), It.IsAny(), It.IsAny())).Throws(new Exception()); 43 | 44 | FleetManager fleetManager = new FleetManager(mockIotClient.Object, mockCosmosClientRobot.Object, telemetryHandler, mockLogger.Object); 45 | 46 | IEnumerable testTelemetry = GetTestTelemetry(numItems); 47 | 48 | // handles exception 49 | await fleetManager.InsertTelemetriesAndUpdateRobotsAsync(testTelemetry); 50 | mockCosmosClientRobot.Verify(x => x.UpdateItemAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(numItems)); 51 | } 52 | 53 | /// 54 | /// Verifies that if an exception is thrown while upserting telemetry, 55 | /// we will attempt to upsert all telemetry and not bail out 56 | /// 57 | /// 58 | /// 59 | [Theory] 60 | [InlineData(10)] 61 | public async Task TestInsertTelemetryAndUpdateRobotsAsync_UpsertTelemetry_ExpectedExecutions(int numItems) 62 | { 63 | mockCosmosClientTelemetry.Setup(x => 64 | x.UpsertItemAsync(It.IsAny(), It.IsAny())).Throws(new Exception("invalid")); 65 | 66 | FleetManager fleetManager = new FleetManager(mockIotClient.Object, mockCosmosClientRobot.Object, telemetryHandler, mockLogger.Object); 67 | 68 | IEnumerable testTelemetry = GetTestTelemetry(numItems); 69 | 70 | // should not throw exception 71 | await fleetManager.InsertTelemetriesAndUpdateRobotsAsync(testTelemetry); 72 | mockCosmosClientTelemetry.Verify(x => x.UpsertItemAsync(It.IsAny(), It.IsAny()), Times.Exactly(numItems)); 73 | } 74 | 75 | private IEnumerable GetTestTelemetry(int numItems) 76 | { 77 | var testTelemetry = new List(); 78 | 79 | for (int i = 0; i < numItems; i ++) 80 | { 81 | var telemetry = new RobotTelemetry() 82 | { 83 | RobotId = $"robot{numItems}", 84 | Status = RobotStatus.Busy, 85 | OrderId = $"Order{numItems}", 86 | CreatedDateTime = DateTime.UtcNow, 87 | }; 88 | 89 | testTelemetry.Add(telemetry); 90 | } 91 | 92 | return testTelemetry; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderProducer/BatchJob.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace RobotOrchestrator.OrderProducer 10 | { 11 | public class BatchJob 12 | { 13 | public bool IsRunning 14 | { 15 | get; private set; 16 | } 17 | 18 | public int MaxItems { get; } 19 | 20 | public int BatchSize { get; } 21 | 22 | public int DelayInSecs { get; } 23 | 24 | private readonly ILogger logger; 25 | 26 | private CancellationTokenSource cancellationTokenSource; 27 | 28 | private Task backgroundBatchTask; 29 | 30 | private static readonly Object batchJobLock = new Object(); 31 | 32 | public BatchJob(BatchJobOptions batchJobOptions, ILogger logger) 33 | { 34 | if (batchJobOptions == null) 35 | { 36 | throw new ArgumentNullException("BatchJobOptions cannot be null."); 37 | } 38 | 39 | batchJobOptions.Validate(); 40 | 41 | MaxItems = batchJobOptions.MaxItems; 42 | BatchSize = batchJobOptions.BatchSize; 43 | DelayInSecs = batchJobOptions.DelayInSecs; 44 | IsRunning = false; 45 | 46 | this.logger = logger; 47 | } 48 | 49 | public Task Start(Action handler) 50 | { 51 | lock (batchJobLock) 52 | { 53 | Task backgroundTask; 54 | 55 | if (!IsRunning) 56 | { 57 | backgroundTask = startBatchJob(handler); 58 | } 59 | else 60 | { 61 | throw new InvalidOperationException("Batch job task already started."); 62 | } 63 | 64 | return backgroundTask; 65 | } 66 | } 67 | 68 | public void Stop() 69 | { 70 | lock (batchJobLock) 71 | { 72 | if (IsRunning) 73 | { 74 | stopBatchJob(); 75 | } 76 | else 77 | { 78 | throw new InvalidOperationException("Batch job task is not running."); 79 | } 80 | } 81 | } 82 | 83 | private Task startBatchJob(Action handler) 84 | { 85 | logger.LogDebug("Starting batch job."); 86 | 87 | IsRunning = true; 88 | 89 | cancellationTokenSource = new CancellationTokenSource(); 90 | 91 | var cancellationToken = cancellationTokenSource.Token; 92 | 93 | backgroundBatchTask = Task.Run(async () => await ProcessBatches(handler, cancellationToken), 94 | cancellationToken) 95 | .ContinueWith((antecedent) => 96 | { 97 | IsRunning = false; 98 | }); 99 | 100 | return backgroundBatchTask; 101 | } 102 | 103 | private void stopBatchJob() 104 | { 105 | try 106 | { 107 | logger.LogDebug("Stopping batch job."); 108 | 109 | cancellationTokenSource.Cancel(); 110 | backgroundBatchTask.Wait(); 111 | } 112 | catch (AggregateException e) 113 | { 114 | foreach (var v in e.InnerExceptions) 115 | { 116 | logger.LogError(e.Message + " " + v.Message); 117 | } 118 | } 119 | } 120 | 121 | private async Task ProcessBatches(Action handler, CancellationToken cancellationToken) 122 | { 123 | var itemsLeft = MaxItems; 124 | 125 | while (!cancellationToken.IsCancellationRequested && (itemsLeft > 0 || itemsLeft == -1)) 126 | { 127 | var batchSize = BatchSize; 128 | 129 | if (MaxItems != -1) 130 | { 131 | batchSize = Math.Min(itemsLeft, BatchSize); 132 | itemsLeft -= batchSize; 133 | } 134 | 135 | handler?.Invoke(batchSize); 136 | await Task.Delay(DelayInSecs * 1000); 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /helm/ros-orchestrator/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT License. 3 | 4 | {{ $Values := .Values }} 5 | --- 6 | apiVersion: apps/v1 7 | kind: Deployment 8 | metadata: 9 | name: simulator 10 | labels: 11 | name: simulator 12 | spec: 13 | selector: 14 | matchLabels: 15 | name: simulator 16 | template: 17 | metadata: 18 | name: simulator 19 | labels: 20 | name: simulator 21 | spec: 22 | nodeSelector: 23 | dedicated: simulator 24 | tolerations: 25 | - key: "dedicated" 26 | operator: "Equal" 27 | value: "simulator" 28 | effect: "NoSchedule" 29 | imagePullSecrets: 30 | - name: "{{ .Values.registrySecret }}" 31 | containers: 32 | - name: simulator 33 | image: "{{ .Values.simulatorImage.repository }}:{{ .Values.simulatorImage.tag }}" 34 | imagePullPolicy: {{ .Values.simulatorImage.pullPolicy }} 35 | ports: 36 | {{- range $i, $e := until (int .Values.robotCount) | default 1 }} 37 | - containerPort: {{ add $Values.portBase $i }} 38 | protocol: "{{ $Values.nimbroProtocol | upper }}" 39 | {{- end }} 40 | env: 41 | - name: ROS_MASTER_URI 42 | value: http://localhost:11311 43 | - name: ROBOT_PREFIX 44 | value: "{{ .Values.robotPrefix }}" 45 | - name: ROBOT_COUNT 46 | value: "{{ .Values.robotCount }}" 47 | - name: SIMULATOR_THREAD_COUNT 48 | value: "{{ .Values.simulatorThreadCount }}" 49 | - name: VNC_PW 50 | value: "{{ .Values.vncPassword }}" 51 | command: ["/bin/bash"] 52 | args: ["-c", "source devel/setup.bash && roslaunch argos_bridge simulation.launch 53 | robot_count:=${ROBOT_COUNT} simulator_thread_count:=${SIMULATOR_THREAD_COUNT} 54 | port_base:={{ .Values.portBase }} robot_target_prefix:={{ .Values.robotTargetPrefix }} robot_prefix:=${ROBOT_PREFIX} 55 | protocol:={{ .Values.nimbroProtocol | lower }} simulator_callback_timeout:={{ .Values.simulationCallbackTimeout }} 56 | argos_world_file:={{ .Values.argosLaunchFile }}" 57 | ] 58 | --- 59 | {{ range $i, $e := until (int .Values.robotCount) | default 1 }} 60 | apiVersion: apps/v1 61 | kind: Deployment 62 | metadata: 63 | name: robot-{{ $i }} 64 | labels: 65 | name: robot-{{ $i }} 66 | spec: 67 | selector: 68 | matchLabels: 69 | name: robot-{{ $i }} 70 | template: 71 | metadata: 72 | name: robot-{{ $i }} 73 | labels: 74 | name: robot-{{ $i }} 75 | spec: 76 | imagePullSecrets: 77 | - name: "{{ $Values.registrySecret }}" 78 | containers: 79 | - name: robot 80 | image: "{{ $Values.robotImage.repository }}:{{ $Values.robotImage.tag }}" 81 | imagePullPolicy: {{ $Values.robotImage.pullPolicy }} 82 | ports: 83 | - containerPort: {{ $Values.portBase }} 84 | protocol: "{{ $Values.nimbroProtocol | upper }}" 85 | env: 86 | - name: ROS_MASTER_URI 87 | value: http://localhost:11311 88 | - name: ROBOT_ID 89 | value: {{ $Values.robotPrefix }}{{ $i }} 90 | - name: SIMULATOR_NIMBRO_TARGET 91 | value: {{ $Values.simulatorTargetPrefix }}{{ $i }} 92 | - name: SIMULATOR_NIMBRO_PORT 93 | value: {{ add $Values.portBase $i | quote }} 94 | - name: VERBOSE 95 | value: "True" 96 | - name: FLEET_MANAGER_URL 97 | value: {{ $Values.fleetManagerUrl }} 98 | - name: FLEET_MANAGER_VERSION 99 | value: {{ $Values.fleetManagerVersion | quote }} 100 | command: ["/bin/bash"] 101 | args: ["-c", "source devel/setup.bash && 102 | roslaunch orchestrated_robot robot.launch 103 | simulator_nimbro_target:=${SIMULATOR_NIMBRO_TARGET} 104 | simulator_nimbro_port:=${SIMULATOR_NIMBRO_PORT} 105 | robot_name:=${ROBOT_ID} 106 | fleetmanager_url:=${FLEET_MANAGER_URL} 107 | fleetmanager_version:=${FLEET_MANAGER_VERSION} 108 | protocol:={{ $Values.nimbroProtocol | lower }}"] 109 | resources: 110 | {{ toYaml $Values.resources | indent 12 }} 111 | --- 112 | {{ end }} 113 | -------------------------------------------------------------------------------- /RobotOrchestrator.FleetManager/FleetManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.Documents; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace RobotOrchestrator.FleetManager 11 | { 12 | public class FleetManager : IFleetManager 13 | { 14 | private readonly ICosmosDbClient cosmosDbClientRobot; 15 | private readonly IIotHubRegistryClient iotHubRegistryClient; 16 | private readonly ITelemetryHandler telemetryHandler; 17 | private readonly ILogger logger; 18 | 19 | public FleetManager( 20 | IIotHubRegistryClient iotHubRegistryClient, 21 | ICosmosDbClient cosmosDbClientRobot, 22 | ITelemetryHandler telemetryHandler, 23 | ILogger logger) 24 | { 25 | this.iotHubRegistryClient = iotHubRegistryClient; 26 | this.cosmosDbClientRobot = cosmosDbClientRobot; 27 | this.telemetryHandler = telemetryHandler; 28 | this.logger = logger; 29 | } 30 | 31 | public async Task UpsertRobotAsync(string deviceId) 32 | { 33 | Robot robot = new Robot() 34 | { 35 | DeviceId = deviceId, 36 | Telemetry = new RobotTelemetry() 37 | }; 38 | 39 | var robotResult = await cosmosDbClientRobot.UpsertItemAsync(robot, new PartitionKey(robot.DeviceId)); 40 | return robotResult; 41 | } 42 | 43 | private async Task CreateIotHubConnectionAsync(string deviceId) 44 | { 45 | var iotResult = await iotHubRegistryClient.AddDeviceAsync(deviceId); 46 | return iotResult; 47 | } 48 | 49 | public async Task CreateIfNotExistsRobotConnectionAsync(string deviceId) 50 | { 51 | await UpsertRobotAsync(deviceId); 52 | var connectionResult = await CreateIotHubConnectionAsync(deviceId); 53 | 54 | return connectionResult; 55 | } 56 | 57 | public async Task DeleteRobotAsync(string deviceId) 58 | { 59 | await cosmosDbClientRobot.DeleteItemAsync(deviceId, new PartitionKey(deviceId)); 60 | await iotHubRegistryClient.DeleteDeviceAsync(deviceId ); 61 | } 62 | 63 | public async Task UpdateRobotAsync(Robot robot) 64 | { 65 | await cosmosDbClientRobot.UpdateItemAsync(robot.DeviceId, robot, new PartitionKey(robot.DeviceId)); 66 | return robot; 67 | } 68 | 69 | public async Task> GetRobotsAsync(RobotStatus? status) 70 | { 71 | IEnumerable robots; 72 | 73 | if (status == null) 74 | { 75 | robots = await cosmosDbClientRobot.GetItemsAsync(); 76 | } 77 | else 78 | { 79 | robots = await cosmosDbClientRobot.GetItemsAsync(r => r.Telemetry.Status == status); 80 | } 81 | 82 | return robots; 83 | } 84 | 85 | public async Task GetRobotAsync(string deviceId) 86 | { 87 | Robot robot = await cosmosDbClientRobot.GetItemAsync(deviceId, new PartitionKey(deviceId)); 88 | 89 | return robot; 90 | } 91 | 92 | public async Task InsertTelemetriesAndUpdateRobotsAsync(IEnumerable robotTelemetry) 93 | { 94 | // telemetry handler inserts telemetry into cosmos 95 | await telemetryHandler.InsertTelemetryAsync(robotTelemetry); 96 | 97 | // update robot statuses in robot db 98 | foreach (RobotTelemetry telemetry in robotTelemetry) 99 | { 100 | var robot = new Robot() 101 | { 102 | DeviceId = telemetry.RobotId, 103 | Telemetry = telemetry 104 | }; 105 | try 106 | { 107 | await UpdateRobotAsync(robot); 108 | } 109 | catch(Exception ex) 110 | { 111 | logger.LogError(ex.Message); 112 | } 113 | } 114 | } 115 | 116 | public async Task> GetLatestTelemetriesAsync(string robotId, int? n) 117 | { 118 | var telemetry = await telemetryHandler.GetLatestTelemetriesAsync(robotId, n); 119 | return telemetry; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /RobotOrchestrator.OrderManager/Startup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Swashbuckle.AspNetCore.Swagger; 11 | using Microsoft.Azure.EventHubs; 12 | using Microsoft.Azure.EventHubs.Processor; 13 | 14 | namespace RobotOrchestrator.OrderManager 15 | { 16 | public class Startup 17 | { 18 | public Startup(IConfiguration configuration) 19 | { 20 | Configuration = configuration; 21 | } 22 | 23 | public IConfiguration Configuration { get; } 24 | 25 | // This method gets called by the runtime. Use this method to add services to the container. 26 | public void ConfigureServices(IServiceCollection services) 27 | { 28 | services.AddSingleton(); 29 | services.AddSingleton(new DispatcherClient(Configuration.GetValue("DispatcherUrl"))); 30 | 31 | services.Configure(Configuration); 32 | services.AddSingleton(); 33 | 34 | services.Configure>(Configuration.GetSection("Order")); 35 | services.AddSingleton, CosmosDbClient>(); 36 | services.AddSingleton(); 37 | 38 | ConfigureEventProcessorHostServices(services); 39 | 40 | services.AddCors(options => 41 | { 42 | options.AddPolicy("AllowAllOrigin", 43 | builder => builder.WithOrigins("*")); 44 | }); 45 | 46 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 47 | 48 | // add versioning, the default is 1.0 49 | services.AddApiVersioning(); 50 | 51 | // Register the Swagger generator, defining 1 or more Swagger documents 52 | services.AddSwaggerGen(c => 53 | { 54 | c.SwaggerDoc("v1", new Info { Title = "RobotOrchestrator.OrderManager", Version = "v1" }); 55 | 56 | c.DescribeAllEnumsAsStrings(); 57 | }); 58 | } 59 | 60 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 61 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 62 | { 63 | if (env.IsDevelopment()) 64 | { 65 | app.UseDeveloperExceptionPage(); 66 | } 67 | else 68 | { 69 | app.UseHsts(); 70 | } 71 | 72 | app.UseHttpsRedirection(); 73 | 74 | // Enable middleware to serve generated Swagger as a JSON endpoint. 75 | app.UseSwagger(); 76 | 77 | // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), 78 | // specifying the Swagger JSON endpoint. 79 | app.UseSwaggerUI(c => 80 | { 81 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "RobotOrchestrator.OrderManager V1"); 82 | }); 83 | 84 | app.UseMvc(); 85 | } 86 | 87 | private void ConfigureEventProcessorHostServices(IServiceCollection services) 88 | { 89 | services.AddSingleton(); 90 | services.AddSingleton(); 91 | services.AddSingleton(new EventProcessorHostConfig() 92 | { 93 | EventHubConnectionString = Configuration.GetValue("OrderManagerEventHubConnectionString"), 94 | ConsumerGroupName = Configuration.GetValue("OrderManagerEventHubConsumerGroup"), 95 | EventHubPath = Configuration.GetValue("OrderManagerEventHubPath"), 96 | StorageConnectionString = Configuration.GetValue("BlobStorageConnectionString"), 97 | LeaseContainerName = "orderleases" 98 | }); 99 | 100 | services.Configure(options => 101 | { 102 | options.MaxBatchSize = 10; 103 | options.PrefetchCount = 100; 104 | options.InitialOffsetProvider = (partitionId) => EventPosition.FromEnqueuedTime(DateTime.UtcNow); 105 | }); 106 | 107 | services.AddHostedService(); 108 | } 109 | } 110 | } 111 | --------------------------------------------------------------------------------