├── 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 |
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 |
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 |
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 |
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 |
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 |
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 |
45 |
46 | - Add variables to the release as shown below to point at your APIs
47 |
48 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------