├── src ├── assets │ ├── .gitkeep │ ├── icons │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-384x384.png │ │ ├── icon-512x512.png │ │ ├── icon-72x72.png │ │ └── icon-96x96.png │ └── configs │ │ └── reset │ │ ├── robots.json │ │ ├── poses.json │ │ └── cmd-lists.json ├── app │ ├── app.component.css │ ├── poses │ │ ├── pose-detail │ │ │ ├── pose-detail.component.css │ │ │ ├── pose-detail.component.html │ │ │ └── pose-detail.component.ts │ │ ├── poses.component.css │ │ ├── shared │ │ │ ├── pose.model.ts │ │ │ └── pose.service.ts │ │ ├── pose-form │ │ │ ├── pose-form.component.css │ │ │ ├── pose-form.component.html │ │ │ └── pose-form.component.ts │ │ ├── poses.component.ts │ │ └── poses.component.html │ ├── robots │ │ ├── robot-detail │ │ │ ├── robot-detail.component.css │ │ │ ├── robot-detail.component.html │ │ │ └── robot-detail.component.ts │ │ ├── robots.component.css │ │ ├── shared │ │ │ ├── robot.model.ts │ │ │ ├── robot-util.ts │ │ │ └── robot.service.ts │ │ ├── robot-form │ │ │ ├── robot-form.component.css │ │ │ ├── robot-form.component.ts │ │ │ └── robot-form.component.html │ │ ├── robots.component.ts │ │ └── robots.component.html │ ├── viewer │ │ ├── input-dialog │ │ │ ├── input-dialog.component.css │ │ │ ├── input-dialog-data.ts │ │ │ ├── input-dialog.component.html │ │ │ └── input-dialog.component.ts │ │ ├── select-dialog │ │ │ ├── select-dialog.component.css │ │ │ ├── select-dialog-data.ts │ │ │ ├── select-dialog.component.html │ │ │ └── select-dialog.component.ts │ │ ├── viewer.component.css │ │ ├── shared │ │ │ ├── text-position.enum.ts │ │ │ ├── markers │ │ │ │ ├── icon.model.ts │ │ │ │ ├── marker.ts │ │ │ │ ├── ros-marker.ts │ │ │ │ ├── grid-marker.ts │ │ │ │ ├── base-marker.ts │ │ │ │ ├── orientation-marker.ts │ │ │ │ ├── robot-status-marker.ts │ │ │ │ ├── icon-marker.ts │ │ │ │ ├── text-marker.ts │ │ │ │ └── path-marker.ts │ │ │ ├── viewer-state.ts │ │ │ ├── protocol │ │ │ │ ├── protocol.model.ts │ │ │ │ ├── protocol-agent.ts │ │ │ │ ├── robot-command-request.model.ts │ │ │ │ ├── robot-command-response.model.ts │ │ │ │ └── protocol-manager.ts │ │ │ ├── base-viewer-component.ts │ │ │ ├── base-viewer.ts │ │ │ └── abstract-viewer-component.ts │ │ ├── viewer.component.html │ │ └── viewer.component.ts │ ├── cmd-lists │ │ ├── cmd-list-detail │ │ │ ├── cmd-list-detail.component.css │ │ │ ├── cmd-list-detail.component.html │ │ │ └── cmd-list-detail.component.ts │ │ ├── cmd-lists.component.css │ │ ├── shared │ │ │ ├── cmd-list.model.ts │ │ │ └── cmd-list.service.ts │ │ ├── cmd-list-form │ │ │ ├── cmd-list-form.component.css │ │ │ ├── cmd-list-form.component.html │ │ │ └── cmd-list-form.component.ts │ │ ├── cmd-lists.component.html │ │ └── cmd-lists.component.ts │ ├── home │ │ ├── home.component.css │ │ ├── home.component.html │ │ └── home.component.ts │ ├── sidenav │ │ ├── sidenav.component.css │ │ ├── sidenav.component.ts │ │ └── sidenav.component.html │ ├── app.component.html │ ├── settings │ │ ├── settings.component.css │ │ ├── shared │ │ │ ├── setting.model.ts │ │ │ └── setting.service.ts │ │ ├── setting-detail │ │ │ ├── setting-detail.component.css │ │ │ ├── setting-detail.component.html │ │ │ └── setting-detail.component.ts │ │ ├── settings.component.html │ │ └── settings.component.ts │ ├── shared │ │ ├── pickable-model.ts │ │ ├── material-color.ts │ │ ├── ros │ │ │ ├── connection-state.enum.ts │ │ │ ├── topic-subscriber.ts │ │ │ ├── messages │ │ │ │ ├── robo-cmd.model.ts │ │ │ │ ├── int-parameter.model.ts │ │ │ │ ├── str-parameter.model.ts │ │ │ │ ├── bool-parameter.model.ts │ │ │ │ ├── double-parameter.model.ts │ │ │ │ ├── robo-res-list.model.ts │ │ │ │ ├── robo-cmd-list.model.ts │ │ │ │ ├── robo-res.model.ts │ │ │ │ ├── reconfigure-service-request.model.ts │ │ │ │ ├── reconfigure-service-response.model.ts │ │ │ │ ├── group-state.model.ts │ │ │ │ ├── occupancy-grid.model.ts │ │ │ │ ├── map-meta-data.model.ts │ │ │ │ └── config.model.ts │ │ │ └── pose.ts │ │ ├── pickable-service.ts │ │ ├── storage │ │ │ ├── storage-data.ts │ │ │ ├── storage-detail.ts │ │ │ ├── storage-service.ts │ │ │ └── storage-component.ts │ │ ├── store │ │ │ ├── key-value-store.ts │ │ │ ├── store.service.ts │ │ │ └── local-storage-store.ts │ │ ├── config │ │ │ ├── initializer.service.ts │ │ │ └── config.service.ts │ │ └── material-color-options.ts │ ├── app.component.ts │ ├── commands │ │ ├── shared │ │ │ ├── command.model.ts │ │ │ └── command-info.model.ts │ │ ├── commands.component.css │ │ ├── commands.component.ts │ │ └── commands.component.html │ ├── app-routing.module.ts │ └── app.module.ts ├── styles.css ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── main.ts ├── index.html ├── test.ts ├── manifest.webmanifest └── polyfills.ts ├── docs ├── _config.yml ├── figs │ ├── en.png │ ├── ja.png │ └── amr-if-ui_00.png ├── index.md ├── index_en.md ├── install.md ├── deploy.md ├── deploy_en.md ├── install_en.md └── setup.md ├── img └── AMR-IF-UI.png ├── LICENSE_HEADER ├── .editorconfig ├── tsconfig.app.json ├── tsconfig.spec.json ├── browserslist ├── ngsw-config.json ├── tsconfig.json ├── .gitignore ├── karma.conf.js ├── LICENSE ├── package.json ├── tslint.json └── angular.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /docs/figs/en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AMR-IF/AMR-IF-UI/HEAD/docs/figs/en.png -------------------------------------------------------------------------------- /docs/figs/ja.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AMR-IF/AMR-IF-UI/HEAD/docs/figs/ja.png -------------------------------------------------------------------------------- /img/AMR-IF-UI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AMR-IF/AMR-IF-UI/HEAD/img/AMR-IF-UI.png -------------------------------------------------------------------------------- /LICENSE_HEADER: -------------------------------------------------------------------------------- 1 | Copyright (C) 2020 TOSHIBA Corporation. 2 | SPDX-License-Identifier: BSD-2-Clause -------------------------------------------------------------------------------- /src/app/poses/pose-detail/pose-detail.component.css: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | margin: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/robots/robot-detail/robot-detail.component.css: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | margin: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /docs/figs/amr-if-ui_00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AMR-IF/AMR-IF-UI/HEAD/docs/figs/amr-if-ui_00.png -------------------------------------------------------------------------------- /src/app/viewer/input-dialog/input-dialog.component.css: -------------------------------------------------------------------------------- 1 | .form-container > * { 2 | width: 100%; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/viewer/select-dialog/select-dialog.component.css: -------------------------------------------------------------------------------- 1 | .form-container > * { 2 | width: 100%; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/cmd-lists/cmd-list-detail/cmd-list-detail.component.css: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | margin: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AMR-IF/AMR-IF-UI/HEAD/src/assets/icons/icon-128x128.png -------------------------------------------------------------------------------- /src/assets/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AMR-IF/AMR-IF-UI/HEAD/src/assets/icons/icon-144x144.png -------------------------------------------------------------------------------- /src/assets/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AMR-IF/AMR-IF-UI/HEAD/src/assets/icons/icon-152x152.png -------------------------------------------------------------------------------- /src/assets/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AMR-IF/AMR-IF-UI/HEAD/src/assets/icons/icon-192x192.png -------------------------------------------------------------------------------- /src/assets/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AMR-IF/AMR-IF-UI/HEAD/src/assets/icons/icon-384x384.png -------------------------------------------------------------------------------- /src/assets/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AMR-IF/AMR-IF-UI/HEAD/src/assets/icons/icon-512x512.png -------------------------------------------------------------------------------- /src/assets/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AMR-IF/AMR-IF-UI/HEAD/src/assets/icons/icon-72x72.png -------------------------------------------------------------------------------- /src/assets/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AMR-IF/AMR-IF-UI/HEAD/src/assets/icons/icon-96x96.png -------------------------------------------------------------------------------- /src/app/home/home.component.css: -------------------------------------------------------------------------------- 1 | .home-container { 2 | padding: 0; 3 | margin: 0; 4 | width: 100%; 5 | height: 100%; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/sidenav/sidenav.component.css: -------------------------------------------------------------------------------- 1 | .sidenav-container { 2 | height: 100%; 3 | } 4 | 5 | .sidenav { 6 | width: 55px; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | html, body { height: 100%; } 4 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } 5 | -------------------------------------------------------------------------------- /src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | 8 |
9 | -------------------------------------------------------------------------------- /src/app/settings/settings.component.css: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | margin: 20px; 3 | } 4 | 5 | a { 6 | color: #ffffff; 7 | } 8 | 9 | table { 10 | width: 100%; 11 | } 12 | 13 | td { 14 | text-align: center; 15 | } 16 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | export const environment = { 7 | production: true, 8 | config: {} as any 9 | }; 10 | -------------------------------------------------------------------------------- /src/app/shared/pickable-model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * ピックアップ可能なモデルです。 8 | */ 9 | export interface PickableModel { 10 | /** アイディー */ 11 | id: number; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/viewer/viewer.component.css: -------------------------------------------------------------------------------- 1 | .viewer-container { 2 | padding: 0; 3 | margin: 0; 4 | width: 100%; 5 | height: 100%; 6 | } 7 | 8 | .viewer-canvas { 9 | padding: 0; 10 | margin: 0; 11 | overflow: hidden; 12 | width: 100%; 13 | height: 100%; 14 | } 15 | -------------------------------------------------------------------------------- /src/assets/configs/reset/robots.json: -------------------------------------------------------------------------------- 1 | { 2 | "array": [ 3 | { 4 | "name": "Trr001", 5 | "namespace": "trr1", 6 | "color": "#E91E6380", 7 | "address": "localhost", 8 | "port": 9091, 9 | "id": 0 10 | } 11 | ], 12 | "nextId": 1 13 | } 14 | -------------------------------------------------------------------------------- /src/app/poses/poses.component.css: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | margin: 20px; 3 | } 4 | 5 | a { 6 | color: #ffffff; 7 | } 8 | 9 | table { 10 | width: 100%; 11 | } 12 | 13 | td { 14 | text-align: center; 15 | } 16 | 17 | .mat-action-row { 18 | place-content: space-between; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/robots/robots.component.css: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | margin: 20px; 3 | } 4 | 5 | a { 6 | color: #ffffff; 7 | } 8 | 9 | table { 10 | width: 100%; 11 | } 12 | 13 | td { 14 | text-align: center; 15 | } 16 | 17 | .mat-action-row { 18 | place-content: space-between; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/cmd-lists/cmd-lists.component.css: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | margin: 20px; 3 | } 4 | 5 | a { 6 | color: #ffffff; 7 | } 8 | 9 | table { 10 | width: 100%; 11 | } 12 | 13 | td { 14 | text-align: center; 15 | } 16 | 17 | .mat-action-row { 18 | place-content: space-between; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/viewer/shared/text-position.enum.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * テキストポジションを定義する。 8 | */ 9 | export enum TextPosition { 10 | Above, 11 | Below, 12 | Left, 13 | Right, 14 | Center 15 | } 16 | -------------------------------------------------------------------------------- /src/app/shared/material-color.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * マテリアルカラーです。 8 | */ 9 | export interface MaterialColor { 10 | /** ネーム */ 11 | name: string; 12 | 13 | /** バリュー */ 14 | value: string; 15 | } 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /src/app/viewer/viewer.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /src/app/shared/ros/connection-state.enum.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * 接続ステートを定義する。 8 | */ 9 | export enum ConnectionState { 10 | Disconnected, 11 | Connecting, 12 | Connected, 13 | Disconnecting, 14 | Retrying 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts", 10 | "src/app/shared/ros/pose.ts" 11 | ], 12 | "exclude": [ 13 | "src/test.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/app/viewer/shared/markers/icon.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * アイコンです。 8 | */ 9 | export interface Icon { 10 | /** アイディー */ 11 | id: number; 12 | 13 | /** アイコン */ 14 | icon: string; 15 | 16 | /** ツールチップ */ 17 | tooltip: string; 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/app/shared/pickable-service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * ピックアップ可能なサービスです。 8 | */ 9 | export interface PickableService { 10 | /** 11 | * 指定したアイディーを持つモデルを取得する。 12 | * 13 | * @param id アイディー 14 | * @return モデル 15 | */ 16 | getById(id: number): M | undefined; 17 | } 18 | -------------------------------------------------------------------------------- /src/app/shared/ros/topic-subscriber.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * トピックサブスクライバーです。 8 | */ 9 | export interface TopicSubscriber { 10 | /** 11 | * トピックをサブスクライブする。 12 | */ 13 | subscribe(): void; 14 | 15 | /** 16 | * トピックをアンサブスクライブする。 17 | */ 18 | unsubscribe(): void; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component } from '@angular/core'; 7 | 8 | @Component({ 9 | selector: 'app-root', 10 | templateUrl: './app.component.html', 11 | styleUrls: ['./app.component.css'] 12 | }) 13 | export class AppComponent { 14 | title = 'AMR-IF-UI'; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/settings/shared/setting.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { PickableModel } from '../../shared/pickable-model'; 7 | 8 | /** 9 | * セッティングです。 10 | */ 11 | export interface Setting extends PickableModel { 12 | /** ネーム */ 13 | name: string; 14 | 15 | /** バリュー */ 16 | value: string; 17 | } 18 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # AMR-IF-UI: 目次 2 | 3 | 4 | 5 | 本パッケージは、NEDOロボット活用型市場化適用技術開発プロジェクト(2017-2020)で開発された、移動ロボット (Autonomout Mobile Robot: AMR) 用のWeb GUIインターフェースです。 6 | 7 | 8 | 9 | ## 目次 10 | 11 | - [1. 環境構築](setup) 12 | - [2. インストール](install) 13 | - [3. デプロイ](deploy) 14 | - [4. UI要素と機能](gui) 15 | 16 | -------------------------------------------------------------------------------- /src/app/shared/storage/storage-data.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { PickableModel } from '../pickable-model'; 7 | 8 | /** 9 | * ストレージモデルデータです。 10 | */ 11 | export interface StorageData { 12 | /** データアレイ */ 13 | array: M[]; 14 | 15 | /** ネクストId */ 16 | nextId: number; 17 | } 18 | -------------------------------------------------------------------------------- /src/app/shared/ros/messages/robo-cmd.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * RoboCmdメッセージです。 8 | */ 9 | export interface RoboCmd { 10 | /** アイディー */ 11 | id: string; 12 | 13 | /** コマンド */ 14 | cmd: string; 15 | 16 | /** パラメータアレイ */ 17 | params: string[]; 18 | 19 | /** ネクストアイディー */ 20 | nextid: string[]; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/commands/shared/command.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { PickableModel } from '../../shared/pickable-model'; 7 | import { RoboCmd } from '../../shared/ros/messages/robo-cmd.model'; 8 | 9 | /** 10 | * コマンドです。 11 | */ 12 | export interface Command extends PickableModel { 13 | /** コマンド */ 14 | command: RoboCmd; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/shared/ros/messages/int-parameter.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * IntParameterメッセージです。 8 | * 9 | * @see http://docs.ros.org/kinetic/api/dynamic_reconfigure/html/msg/IntParameter.html 10 | */ 11 | export interface IntParameter { 12 | /** ネーム */ 13 | name: string; 14 | 15 | /** バリュー */ 16 | value: number; 17 | } 18 | -------------------------------------------------------------------------------- /src/app/shared/ros/messages/str-parameter.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * StrParameterメッセージです。 8 | * 9 | * @see http://docs.ros.org/kinetic/api/dynamic_reconfigure/html/msg/StrParameter.html 10 | */ 11 | export interface StrParameter { 12 | /** ネーム */ 13 | name: string; 14 | 15 | /** バリュー */ 16 | value: string; 17 | } 18 | -------------------------------------------------------------------------------- /src/app/commands/commands.component.css: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | margin: 20px; 3 | } 4 | 5 | a { 6 | color: #ffffff; 7 | } 8 | 9 | table { 10 | width: 100%; 11 | } 12 | 13 | td { 14 | text-align: center; 15 | margin-bottom: 0px; 16 | } 17 | 18 | .form-params { 19 | display: flex; 20 | flex-direction: column; 21 | } 22 | 23 | .form-params > * { 24 | width: 100%; 25 | } 26 | 27 | .form-params .mat-form-field { 28 | } 29 | -------------------------------------------------------------------------------- /src/app/shared/ros/messages/bool-parameter.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * BoolParameterメッセージです。 8 | * 9 | * @see http://docs.ros.org/kinetic/api/dynamic_reconfigure/html/msg/BoolParameter.html 10 | */ 11 | export interface BoolParameter { 12 | /** ネーム */ 13 | name: string; 14 | 15 | /** バリュー */ 16 | value: boolean; 17 | } 18 | -------------------------------------------------------------------------------- /src/app/shared/ros/messages/double-parameter.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * DoubleParameterメッセージです。 8 | * 9 | * @see http://docs.ros.org/kinetic/api/dynamic_reconfigure/html/msg/DoubleParameter.html 10 | */ 11 | export interface DoubleParameter { 12 | /** ネーム */ 13 | name: string; 14 | 15 | /** バリュー */ 16 | value: number; 17 | } 18 | -------------------------------------------------------------------------------- /src/app/shared/ros/messages/robo-res-list.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { RoboRes } from './robo-res.model'; 7 | 8 | /** 9 | * RoboResListメッセージです。 10 | */ 11 | export interface RoboResList { 12 | /** アイディー */ 13 | id: string; 14 | 15 | /** レスポンスリスト */ 16 | reslist: RoboRes[]; 17 | 18 | /** ステータス */ 19 | status: string; 20 | } 21 | -------------------------------------------------------------------------------- /src/app/viewer/input-dialog/input-dialog-data.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * インプットダイアログデータです。 8 | */ 9 | export interface InputDialogData { 10 | /** タイトル */ 11 | title: string; 12 | 13 | /** プレースホルダー */ 14 | placeholder: string; 15 | 16 | /** オプション */ 17 | options: string[]; 18 | 19 | /** インプット */ 20 | inputed: string; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/viewer/select-dialog/select-dialog-data.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * セレクトダイアログデータです。 8 | */ 9 | export interface SelectDialogData { 10 | /** タイトル */ 11 | title: string; 12 | 13 | /** プレースホルダー */ 14 | placeholder: string; 15 | 16 | /** オプション */ 17 | options: string[]; 18 | 19 | /** セレクト */ 20 | selected: string; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/shared/ros/messages/robo-cmd-list.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { RoboCmd } from './robo-cmd.model'; 7 | 8 | /** 9 | * RoboCmdListメッセージです。 10 | */ 11 | export interface RoboCmdList { 12 | /** アイディー */ 13 | id: string; 14 | 15 | /** コマンドリスト */ 16 | cmdlist: RoboCmd[]; 17 | 18 | /** パラメータアレイ */ 19 | params: string[]; 20 | } 21 | -------------------------------------------------------------------------------- /src/app/viewer/shared/viewer-state.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * ビューワーステートです。 8 | */ 9 | export class ViewerState { 10 | /** スケール */ 11 | scale = 1.0; 12 | 13 | /** オリジンX */ 14 | originX = 0; 15 | 16 | /** オリジンY */ 17 | originY = 0; 18 | 19 | /** ポジションX */ 20 | positionX = 0; 21 | 22 | /** ポジションY */ 23 | positionY = 0; 24 | } 25 | -------------------------------------------------------------------------------- /src/app/shared/ros/messages/robo-res.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * RoboResメッセージです。 8 | */ 9 | export interface RoboRes { 10 | /** アイディー */ 11 | id: string; 12 | 13 | /** コマンド */ 14 | cmd: string; 15 | 16 | /** パラメータアレイ */ 17 | params: string[]; 18 | 19 | /** ネクストアイディー */ 20 | nextid: string[]; 21 | 22 | /** ステータス */ 23 | status: string; 24 | } 25 | -------------------------------------------------------------------------------- /src/app/poses/shared/pose.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import * as ROSLIB from 'roslib'; 7 | import { PickableModel } from '../../shared/pickable-model'; 8 | 9 | /** 10 | * ポーズです。 11 | */ 12 | export interface Pose extends PickableModel { 13 | /** ネーム */ 14 | name: string; 15 | 16 | /** カラー */ 17 | color: string; 18 | 19 | /** ポーズ */ 20 | rospose: ROSLIB.Pose; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/shared/ros/messages/reconfigure-service-request.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Config } from './config.model'; 7 | 8 | /** 9 | * Reconfigureサービスのリクエストメッセージです。 10 | * 11 | * @see http://docs.ros.org/kinetic/api/dynamic_reconfigure/html/srv/Reconfigure.html 12 | */ 13 | export interface ReconfigureServiceRequest { 14 | /** コンフィグ */ 15 | config: Config; 16 | } 17 | -------------------------------------------------------------------------------- /src/app/shared/ros/messages/reconfigure-service-response.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Config } from './config.model'; 7 | 8 | /** 9 | * Reconfigureサービスのレスポンスメッセージです。 10 | * 11 | * @see http://docs.ros.org/kinetic/api/dynamic_reconfigure/html/srv/Reconfigure.html 12 | */ 13 | export interface ReconfigureServiceResponse { 14 | /** コンフィグ */ 15 | config: Config; 16 | } 17 | -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /src/app/settings/setting-detail/setting-detail.component.css: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | margin: 20px; 3 | } 4 | 5 | .form-container { 6 | display: flex; 7 | flex-direction: column; 8 | } 9 | 10 | .form-container .mat-form-field { 11 | margin-bottom: 5px; 12 | } 13 | 14 | .form-value { 15 | display: flex; 16 | flex-direction: row; 17 | } 18 | 19 | .form-value > * { 20 | width: 100%; 21 | } 22 | 23 | .form-value .mat-form-field { 24 | margin-bottom: 15px; 25 | } 26 | -------------------------------------------------------------------------------- /src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component } from '@angular/core'; 7 | 8 | /** 9 | * ホームのコンポーネントです。 10 | */ 11 | @Component({ 12 | selector: 'app-home', 13 | templateUrl: './home.component.html', 14 | styleUrls: ['./home.component.css'] 15 | }) 16 | export class HomeComponent { 17 | 18 | /** 19 | * オブジェクトを構築する。 20 | */ 21 | constructor() { } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/sidenav/sidenav.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component } from '@angular/core'; 7 | 8 | /** 9 | * サイドナビのコンポーネントです。 10 | */ 11 | @Component({ 12 | selector: 'app-sidenav', 13 | templateUrl: './sidenav.component.html', 14 | styleUrls: ['./sidenav.component.css'] 15 | }) 16 | export class SidenavComponent { 17 | 18 | /** 19 | * オブジェクトを構築する。 20 | */ 21 | constructor() { } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/shared/ros/messages/group-state.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * GroupStateメッセージです。 8 | * 9 | * @see http://docs.ros.org/kinetic/api/dynamic_reconfigure/html/msg/GroupState.html 10 | */ 11 | export interface GroupState { 12 | /** ネーム */ 13 | name: string; 14 | 15 | /** ステート */ 16 | state: boolean; 17 | 18 | /** アイディー */ 19 | id: number; 20 | 21 | /** ペアレント */ 22 | parent: number; 23 | } 24 | -------------------------------------------------------------------------------- /src/app/cmd-lists/shared/cmd-list.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { PickableModel } from '../../shared/pickable-model'; 7 | import { Command } from '../../commands/shared/command.model'; 8 | 9 | /** 10 | * コマンドリストです。 11 | */ 12 | export interface CmdList extends PickableModel { 13 | /** ネーム */ 14 | name: string; 15 | 16 | /** モード(custom) */ 17 | mode: string; 18 | 19 | /** コマンドリスト */ 20 | commands: Command[]; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/cmd-lists/cmd-list-form/cmd-list-form.component.css: -------------------------------------------------------------------------------- 1 | .form-container { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .form-container .mat-form-field { 7 | margin-bottom: 5px; 8 | } 9 | 10 | .form-container > * { 11 | width: 100%; 12 | } 13 | 14 | .form-cmd-list { 15 | display: flex; 16 | flex-direction: row; 17 | } 18 | 19 | .form-cmd-list > * { 20 | width: 100%; 21 | } 22 | 23 | .form-cmd-list .mat-form-field { 24 | margin-bottom: 15px; 25 | } 26 | 27 | .right { 28 | text-align: right; 29 | } 30 | -------------------------------------------------------------------------------- /src/app/robots/shared/robot.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { PickableModel } from '../../shared/pickable-model'; 7 | 8 | /** 9 | * ロボットです。 10 | */ 11 | export interface Robot extends PickableModel { 12 | /** ネーム */ 13 | name: string; 14 | 15 | /** ネームスペース */ 16 | namespace: string; 17 | 18 | /** カラー */ 19 | color: string; 20 | 21 | /** アドレス */ 22 | address: string; 23 | 24 | /** ポートナンバー */ 25 | port: number; 26 | } 27 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { enableProdMode } from '@angular/core'; 7 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 8 | 9 | import { AppModule } from './app/app.module'; 10 | import { environment } from './environments/environment'; 11 | 12 | if (environment.production) { 13 | enableProdMode(); 14 | } 15 | 16 | platformBrowserDynamic().bootstrapModule(AppModule) 17 | .catch(err => console.error(err)); 18 | -------------------------------------------------------------------------------- /src/app/viewer/shared/protocol/protocol.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { RobotCommandRequest } from './robot-command-request.model'; 7 | import { RobotCommandResponse } from './robot-command-response.model'; 8 | 9 | /** 10 | * プロトコルです。 11 | */ 12 | export interface Protocol { 13 | /** タイプ */ 14 | type: string; 15 | 16 | /** リクエスト */ 17 | request: RobotCommandRequest; 18 | 19 | /** レスポンス */ 20 | response: RobotCommandResponse; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/viewer/shared/protocol/protocol-agent.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Robot } from '../../../robots/shared/robot.model'; 7 | 8 | /** 9 | * プロトコルエージェントです。 10 | */ 11 | export interface ProtocolAgent { 12 | /** 13 | * ロボットを取得する。 14 | * 15 | * @return ロボット 16 | */ 17 | getRobot(): Robot; 18 | 19 | /** 20 | * トピックを取得する。 21 | * 22 | * @param options オプション 23 | * @return トピック 24 | */ 25 | getTopic(options: any): ROSLIB.Topic | null; 26 | } 27 | -------------------------------------------------------------------------------- /docs/index_en.md: -------------------------------------------------------------------------------- 1 | # AMR-IF-UI: TOC 2 | 3 | 4 | 5 | This package is a Web GUI interface package for mobile robots (Autonomous Mobile Robot: AMR) that is released from the NEDO Technology Development Project for Robot Commercialization Applications (Shijyouka Project, robo-marc Project, FY2017-2020. 6 | 7 | 8 | 9 | ## Table of Contents 10 | 11 | - [1. Setup](setup_en) 12 | - [2. Installation](install_en) 13 | - [3. Deployment](deploy_en) 14 | - [4. UI Elements and Functions](gui_en) 15 | 16 | -------------------------------------------------------------------------------- /src/app/robots/shared/robot-util.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Robot } from './robot.model'; 7 | 8 | /** 9 | * ロボットユーティリティです。 10 | */ 11 | export class RobotUtil { 12 | /** 13 | * URLを取得する。 14 | * 15 | * @param robot ロボット 16 | * @return ロボットのURL 17 | */ 18 | static getURL(robot: Robot): string { 19 | if (window.location.protocol === 'https:') { 20 | return 'wss://' + robot.address + ':' + robot.port; 21 | } else { 22 | return 'ws://' + robot.address + ':' + robot.port; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/app/commands/shared/command-info.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * コマンドインフォです。 8 | */ 9 | export interface CommandInfo { 10 | /** タイプ */ 11 | type: string; 12 | 13 | /** リクエストトピックネーム */ 14 | request: string; 15 | 16 | /** レスポンストピックネーム */ 17 | response: string; 18 | 19 | /** コマンド */ 20 | cmd: string; 21 | 22 | /** パラメータカウント */ 23 | paramCount: number; 24 | 25 | /** パラメータインデックス */ 26 | paramIndex: number; 27 | 28 | /** パラメータ */ 29 | param: string; 30 | 31 | /** オートコンプリート */ 32 | autocomplete: string[]; 33 | } 34 | -------------------------------------------------------------------------------- /src/app/shared/ros/messages/occupancy-grid.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { MapMetaData } from './map-meta-data.model'; 7 | 8 | /** 9 | * オキュパンシーグリッドです。 10 | * @see http://docs.ros.org/kinetic/api/nav_msgs/html/msg/OccupancyGrid.html 11 | */ 12 | export interface OccupancyGrid { 13 | /** ヘッダー */ 14 | header: any; 15 | 16 | /** MetaData for the map */ 17 | info: MapMetaData; 18 | 19 | /** The map data, in row-major order, starting with (0,0). Occupancy probabilities are in the range [0,100]. Unknown is -1. */ 20 | data: Int8Array; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/viewer/shared/markers/marker.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * マーカーです。 8 | */ 9 | export interface Marker { 10 | /** 11 | * ビューが変化した際にアップデートする。 12 | */ 13 | update(): void; 14 | 15 | /** 16 | * 表示する。 17 | */ 18 | show(): void; 19 | 20 | /** 21 | * 非表示にする。 22 | */ 23 | hide(): void; 24 | 25 | /** 26 | * 表示か非表示かを返却する。 27 | * 28 | * @return true(表示)/false(非表示) 29 | */ 30 | isVisibled(): boolean; 31 | 32 | /** 33 | * 破棄する。 34 | */ 35 | dispose(): void; 36 | 37 | /** 38 | * 削除する。 39 | */ 40 | remove(): void; 41 | } 42 | -------------------------------------------------------------------------------- /src/app/shared/ros/messages/map-meta-data.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import * as ROSLIB from 'roslib'; 7 | 8 | /** 9 | * MapMetaDataメッセージです。 10 | * @see http://docs.ros.org/kinetic/api/nav_msgs/html/msg/MapMetaData.html 11 | */ 12 | export interface MapMetaData { 13 | /** The map resolution [m/cell] */ 14 | resolution: number; 15 | 16 | /** Map width [cells] */ 17 | width: number; 18 | 19 | /** Map height [cells] */ 20 | height: number; 21 | 22 | /** The origin of the map [m, m, rad]. This is the real-world pose of the cell (0,0) in the map. */ 23 | origin: ROSLIB.Pose; 24 | } 25 | -------------------------------------------------------------------------------- /ngsw-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/service-worker/config/schema.json", 3 | "index": "/index.html", 4 | "assetGroups": [ 5 | { 6 | "name": "app", 7 | "installMode": "prefetch", 8 | "resources": { 9 | "files": [ 10 | "/index.html", 11 | "/manifest.webmanifest", 12 | "/*.css", 13 | "/*.js" 14 | ] 15 | } 16 | }, { 17 | "name": "assets", 18 | "installMode": "lazy", 19 | "updateMode": "prefetch", 20 | "resources": { 21 | "files": [ 22 | "/assets/**", 23 | "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)" 24 | ] 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/app/robots/robot-detail/robot-detail.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 |

{{model.id}}: {{model.name}} Details

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | -------------------------------------------------------------------------------- /src/app/poses/pose-detail/pose-detail.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 |

{{model.id}}: {{model.name}} Details

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "strictNullChecks": true, 14 | "noImplicitAny": true, 15 | "noImplicitThis": true, 16 | "noImplicitReturns": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "target": "es2015", 20 | "typeRoots": [ 21 | "node_modules/@types" 22 | ], 23 | "lib": [ 24 | "es2018", 25 | "dom" 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/cmd-lists/cmd-list-detail/cmd-list-detail.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 |

{{ model.id }}: {{ model.name }} Details

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 | -------------------------------------------------------------------------------- /src/app/poses/pose-form/pose-form.component.css: -------------------------------------------------------------------------------- 1 | .form-container { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .form-container .mat-form-field { 7 | margin-bottom: 5px; 8 | } 9 | 10 | .form-container > * { 11 | width: 100%; 12 | } 13 | 14 | .form-name-and-color { 15 | display: flex; 16 | flex-direction: row; 17 | } 18 | 19 | .form-name-and-color > * { 20 | width: 50%; 21 | } 22 | 23 | .form-name-and-color .mat-form-field { 24 | margin-bottom: 15px; 25 | } 26 | 27 | .form-position { 28 | display: flex; 29 | flex-direction: row; 30 | } 31 | 32 | .form-position > * { 33 | width: 33%; 34 | } 35 | 36 | .form-position .mat-form-field { 37 | margin-bottom: 15px; 38 | } 39 | 40 | .right { 41 | text-align: right; 42 | } 43 | -------------------------------------------------------------------------------- /src/app/viewer/select-dialog/select-dialog.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |

{{ data.title }}

7 |
8 |
9 | 10 | 11 | 12 | {{ option }} 13 | 14 | 15 | 16 |
17 |
18 |
19 | 20 | 21 |
22 | -------------------------------------------------------------------------------- /src/app/viewer/shared/protocol/robot-command-request.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * ロボットコマンドパラメーターです。 8 | */ 9 | export interface RobotCommandParameter { 10 | /** ネーム */ 11 | name: string; 12 | 13 | /** バリュー */ 14 | values: string[]; 15 | } 16 | 17 | /** 18 | * ロボットコマンドです。 19 | */ 20 | export interface RobotCommand { 21 | /** コマンド名 */ 22 | cmd: string; 23 | 24 | /** パラメータ */ 25 | params: RobotCommandParameter[]; 26 | } 27 | 28 | /** 29 | * ロボットコマンドリクエストです。 30 | */ 31 | export interface RobotCommandRequest { 32 | /** トピック */ 33 | topic: string; 34 | 35 | /** メッセージタイプ */ 36 | messageType: string; 37 | 38 | /** メッセージ */ 39 | messages: RobotCommand[]; 40 | } 41 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | AMRIF 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/app/shared/store/key-value-store.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * キーバリューストアです。 8 | */ 9 | export interface KeyValueStore { 10 | /** 11 | * イニシャライズする。 12 | * 13 | * @return プロミス 14 | */ 15 | initialize(): Promise; 16 | 17 | /** 18 | * キーのバリューをゲットする。 19 | * 20 | * @param key キー 21 | * @return プロミス 22 | */ 23 | get(key: string): Promise; 24 | 25 | /** 26 | * キーをリムーブする。 27 | * 28 | * @param key キー 29 | * @return プロミス 30 | */ 31 | remove(key: string): Promise; 32 | 33 | /** 34 | * キーにバリューをセットする。 35 | * 36 | * @param key キー 37 | * @param value バリュー 38 | * @return プロミス 39 | */ 40 | set(key: string, value: string): Promise; 41 | } 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /src/app/robots/robot-form/robot-form.component.css: -------------------------------------------------------------------------------- 1 | .form-container { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .form-container .mat-form-field { 7 | margin-bottom: 5px; 8 | } 9 | 10 | .form-container .mat-checkbox { 11 | margin-bottom: 15px; 12 | } 13 | 14 | .form-container .mat-radio-button { 15 | margin: 0 5px; 16 | } 17 | 18 | .form-name-and-color { 19 | display: flex; 20 | flex-direction: row; 21 | } 22 | 23 | .form-name-and-color > * { 24 | width: 50%; 25 | } 26 | 27 | .form-name-and-color .mat-form-field { 28 | margin-bottom: 15px; 29 | } 30 | 31 | .form-robot { 32 | display: flex; 33 | flex-direction: row; 34 | } 35 | 36 | .form-robot > * { 37 | width: 33%; 38 | } 39 | 40 | .form-robot .mat-form-field { 41 | margin-bottom: 15px; 42 | } 43 | 44 | .right { 45 | text-align: right; 46 | } 47 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 7 | 8 | import 'zone.js/dist/zone-testing'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | declare const require: any; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /src/app/cmd-lists/cmd-list-form/cmd-list-form.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 |
9 | 10 | 16 | MyCmdList 17 | 18 | 19 | {{ option }} 20 | 21 | 22 | 23 |
24 | 25 |
26 |
27 | -------------------------------------------------------------------------------- /src/app/shared/ros/pose.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import * as ROSLIB from 'roslib'; 7 | 8 | // add type definition 9 | 10 | declare module 'roslib' { 11 | /** 12 | * ポーズです。 13 | * roslibで型定義が宣言されていないので追加する。 14 | */ 15 | export class Pose { 16 | /** 17 | * オブジェクトを構築する。 18 | * 19 | * @param options オプション 20 | */ 21 | constructor(options: { position: any; orientation: any }); 22 | 23 | /** ポジション */ 24 | position: ROSLIB.Vector3; 25 | 26 | /** オリエンテーション */ 27 | orientation: ROSLIB.Quaternion; 28 | 29 | /** 30 | * トランスフォームをアプライする。 31 | * 32 | * @param tf tf 33 | */ 34 | applyTransform(tf: { rotation: any }): void; 35 | 36 | /** 37 | * クローンする。 38 | */ 39 | clone(): void; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | // This file can be replaced during build by using the `fileReplacements` array. 7 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 8 | // The list of file replacements can be found in `angular.json`. 9 | 10 | export const environment = { 11 | production: false, 12 | config: {} as any 13 | }; 14 | 15 | /* 16 | * For easier debugging in development mode, you can import the following file 17 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 18 | * 19 | * This import should be commented out in production mode because it will have a negative impact 20 | * on performance if an error is thrown. 21 | */ 22 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 23 | -------------------------------------------------------------------------------- /src/app/shared/ros/messages/config.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { BoolParameter } from './bool-parameter.model'; 7 | import { IntParameter } from './int-parameter.model'; 8 | import { StrParameter } from './str-parameter.model'; 9 | import { DoubleParameter } from './double-parameter.model'; 10 | import { GroupState } from './group-state.model'; 11 | 12 | /** 13 | * Configメッセージです。 14 | * 15 | * @see http://docs.ros.org/kinetic/api/dynamic_reconfigure/html/msg/Config.html 16 | */ 17 | export interface Config { 18 | /** boolパラメータ配列 */ 19 | bools: BoolParameter[]; 20 | 21 | /** intパラメータ配列 */ 22 | ints: IntParameter[]; 23 | 24 | /** strパラメータ配列 */ 25 | strs: StrParameter[]; 26 | 27 | /** doubleパラメータ配列 */ 28 | doubles: DoubleParameter[]; 29 | 30 | /** groupsパラメータ配列 */ 31 | groups: GroupState[]; 32 | } 33 | -------------------------------------------------------------------------------- /src/app/shared/store/store.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Injectable } from '@angular/core'; 7 | import { KeyValueStore } from './key-value-store'; 8 | import { LocalStorageStore } from './local-storage-store'; 9 | import { HttpClient } from '@angular/common/http'; 10 | 11 | /** 12 | * ストアサービスする。 13 | */ 14 | @Injectable({ 15 | providedIn: 'root' 16 | }) 17 | export class StoreService { 18 | /** キーバリューストア */ 19 | keyValueStore: KeyValueStore; 20 | 21 | /** 22 | * オブジェクトを構築する。 23 | * 24 | * @param http HTTPクライアント 25 | */ 26 | constructor(public http: HttpClient) { 27 | this.keyValueStore = new LocalStorageStore(); 28 | } 29 | 30 | /** 31 | * キーバリューストアを返す。 32 | * 33 | * @return キーバリューストア 34 | */ 35 | getKeyValueStore(): KeyValueStore { 36 | return this.keyValueStore; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/assets/configs/reset/poses.json: -------------------------------------------------------------------------------- 1 | { 2 | "array": [ 3 | { 4 | "name": "P000", 5 | "color": "#03A9F480", 6 | "rospose": { 7 | "position": { 8 | "x": 0.00000000000000, 9 | "y": 0.00000000000000, 10 | "z": 0 11 | }, 12 | "orientation": { 13 | "x": 0, 14 | "y": 0, 15 | "z": 0, 16 | "w": 1 17 | } 18 | }, 19 | "id": 0 20 | }, 21 | { 22 | "name": "P001", 23 | "color": "#FF980080", 24 | "rospose": { 25 | "position": { 26 | "x": -0.484975072012876, 27 | "y": -2.70799473067883, 28 | "z": 0 29 | }, 30 | "orientation": { 31 | "x": 0, 32 | "y": 0, 33 | "z": -0.7071067811865476, 34 | "w": 0.7071067811865475 35 | } 36 | }, 37 | "id": 1 38 | } 39 | ], 40 | "nextId": 2 41 | } 42 | -------------------------------------------------------------------------------- /src/app/viewer/input-dialog/input-dialog.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |

{{ data.title }}

7 |
8 |
9 | 10 | 17 | 18 | 19 | {{ option }} 20 | 21 | 22 | 23 |
24 |
25 |
26 | 27 | 28 |
29 | -------------------------------------------------------------------------------- /src/app/viewer/shared/protocol/robot-command-response.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * ロボットレスポンスパラメーターです。 8 | */ 9 | export interface RobotResponseParameter { 10 | /** ネーム */ 11 | name: string; 12 | 13 | /** バリュー */ 14 | values: string[]; 15 | } 16 | 17 | /** 18 | * ロボットレスポンスステータスです。 19 | */ 20 | export interface RobotResponseStatus { 21 | /** ステータス */ 22 | statuses: string[]; 23 | } 24 | 25 | /** 26 | * ロボットレスポンスです。 27 | */ 28 | export interface RobotResponse { 29 | /** コマンド名 */ 30 | cmd: string; 31 | 32 | /** パラメータ */ 33 | params: RobotResponseParameter[]; 34 | 35 | /** ステータス */ 36 | status: RobotResponseStatus; 37 | } 38 | 39 | /** 40 | * ロボットコマンドレスポンスです。 41 | */ 42 | export interface RobotCommandResponse { 43 | /** トピック */ 44 | topic: string; 45 | 46 | /** メッセージタイプ */ 47 | messageType: string; 48 | 49 | /** メッセージ */ 50 | messages: RobotResponse[]; 51 | } 52 | -------------------------------------------------------------------------------- /src/app/settings/settings.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |

Settings

8 |
9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
IdNameValue
{{ row.id }}{{ row.name }}{{ row.value }}
27 |
28 |
29 |
30 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /src/app/poses/poses.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component, Renderer2 } from '@angular/core'; 7 | import { Pose } from './shared/pose.model'; 8 | import { PoseService } from './shared/pose.service'; 9 | import { StorageComponent } from '../shared/storage/storage-component'; 10 | import { StoreService } from '../shared/store/store.service'; 11 | 12 | /** 13 | * ポーズのコンポーネントです。 14 | */ 15 | @Component({ 16 | selector: 'app-poses', 17 | templateUrl: './poses.component.html', 18 | styleUrls: ['./poses.component.css'] 19 | }) 20 | export class PosesComponent extends StorageComponent { 21 | /** ダウンロードプレフィックスネーム */ 22 | downloadPrefixName = 'poses'; 23 | 24 | /** 25 | * オブジェクトを構築する。 26 | * 27 | * @param service ポーズサービス 28 | * @param renderer2 レンダラー2 29 | * @param storeService ストアサービス 30 | */ 31 | constructor( 32 | public service: PoseService, 33 | public renderer2: Renderer2, 34 | public storeService: StoreService 35 | ) { 36 | super(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/robots/robots.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component, Renderer2 } from '@angular/core'; 7 | import { Robot } from './shared/robot.model'; 8 | import { RobotService } from './shared/robot.service'; 9 | import { StorageComponent } from '../shared/storage/storage-component'; 10 | import { StoreService } from '../shared/store/store.service'; 11 | 12 | /** 13 | * ロボットのコンポーネントです。 14 | */ 15 | @Component({ 16 | selector: 'app-robots', 17 | templateUrl: './robots.component.html', 18 | styleUrls: ['./robots.component.css'] 19 | }) 20 | export class RobotsComponent extends StorageComponent { 21 | /** ダウンロードプレフィックスネーム */ 22 | downloadPrefixName = 'robots'; 23 | 24 | /** 25 | * オブジェクトを構築する。 26 | * 27 | * @param service ロボットサービス 28 | * @param storeService ストアサービス 29 | * @param renderer2 レンダラー2 30 | */ 31 | constructor( 32 | public service: RobotService, 33 | public storeService: StoreService, 34 | public renderer2: Renderer2 35 | ) { 36 | super(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/poses/pose-detail/pose-detail.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component, Input } from '@angular/core'; 7 | import { ActivatedRoute } from '@angular/router'; 8 | import { Location } from '@angular/common'; 9 | import { Pose } from '../shared/pose.model'; 10 | import { PoseService } from '../shared/pose.service'; 11 | import { StorageDetail } from '../../shared/storage/storage-detail'; 12 | 13 | /** 14 | * ポーズの詳細を表示するコンポーネントです。 15 | */ 16 | @Component({ 17 | selector: 'app-pose-detail', 18 | templateUrl: './pose-detail.component.html', 19 | styleUrls: ['./pose-detail.component.css'] 20 | }) 21 | export class PoseDetailComponent extends StorageDetail { 22 | /** ポーズ */ 23 | @Input() 24 | model: Pose; 25 | 26 | /** 27 | * オブジェクトを構築する。 28 | * 29 | * @param service サービス 30 | * @param route ルート 31 | * @param location ロケーション 32 | */ 33 | constructor( 34 | public service: PoseService, 35 | public route: ActivatedRoute, 36 | public location: Location 37 | ) { 38 | super(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/AMR-IF-UI'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /src/app/cmd-lists/cmd-list-form/cmd-list-form.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component, OnInit, Input } from '@angular/core'; 7 | import { CmdList } from '../shared/cmd-list.model'; 8 | import { CommandService } from '../../commands/shared/command.service'; 9 | 10 | /** 11 | * コマンドリストのフォームを表示するコンポーネントです。 12 | */ 13 | @Component({ 14 | selector: 'app-cmd-list-form', 15 | templateUrl: './cmd-list-form.component.html', 16 | styleUrls: ['./cmd-list-form.component.css'] 17 | }) 18 | export class CmdListFormComponent implements OnInit { 19 | /** コマンドリスト */ 20 | @Input() 21 | edit: CmdList; 22 | 23 | /** 候補として表示するネームのオプション */ 24 | nameOptions: string[] = []; 25 | 26 | /** 27 | * オブジェクトを構築する 28 | * 29 | * @param commandService コマンドサービス 30 | */ 31 | constructor( 32 | private commandService: CommandService 33 | ) {} 34 | 35 | /** 36 | * コンポーネントを初期化する。 37 | */ 38 | ngOnInit(): void { 39 | this.commandService.clear().then(() => { 40 | this.commandService.setArray(this.edit.commands); 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app/shared/store/local-storage-store.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { KeyValueStore } from './key-value-store'; 7 | 8 | /** 9 | * ローカルストレージストアです。 10 | */ 11 | export class LocalStorageStore implements KeyValueStore { 12 | /** 13 | * オブジェクトを構築する。 14 | */ 15 | constructor() {} 16 | 17 | /** 18 | * イニシャライズする。 19 | * 20 | * @return プロミス 21 | */ 22 | async initialize(): Promise {} 23 | 24 | /** 25 | * キーのバリューをゲットする。 26 | * 27 | * @param key キー 28 | * @return プロミス 29 | */ 30 | async get(key: string): Promise { 31 | return localStorage.getItem(key); 32 | } 33 | 34 | /** 35 | * キーをリムーブする。 36 | * 37 | * @param key キー 38 | * @return プロミス 39 | */ 40 | async remove(key: string): Promise { 41 | localStorage.removeItem(key); 42 | } 43 | 44 | /** 45 | * キーにバリューをセットする。 46 | * 47 | * @param key キー 48 | * @param value バリュー 49 | * @return プロミス 50 | */ 51 | async set(key: string, value: string): Promise { 52 | localStorage.setItem(key, value); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/app/robots/robot-detail/robot-detail.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component, Input } from '@angular/core'; 7 | import { ActivatedRoute } from '@angular/router'; 8 | import { Location } from '@angular/common'; 9 | import { Robot } from '../shared/robot.model'; 10 | import { RobotService } from '../shared/robot.service'; 11 | import { StorageDetail } from '../../shared/storage/storage-detail'; 12 | 13 | /** 14 | * ロボットの詳細を表示するコンポーネントです。 15 | */ 16 | @Component({ 17 | selector: 'app-robot-detail', 18 | templateUrl: './robot-detail.component.html', 19 | styleUrls: ['./robot-detail.component.css'] 20 | }) 21 | export class RobotDetailComponent extends StorageDetail { 22 | /** ロボット */ 23 | @Input() 24 | model: Robot; 25 | 26 | /** 27 | * オブジェクトを構築する。 28 | * 29 | * @param service サービス 30 | * @param route ルート 31 | * @param location ロケーション 32 | */ 33 | constructor( 34 | public service: RobotService, 35 | public route: ActivatedRoute, 36 | public location: Location 37 | ) { 38 | super(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/sidenav/sidenav.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | home 11 | 12 | 13 | airport_shuttle 14 | 15 | 16 | place 17 | 18 | 19 | list 20 | 21 | 22 | settings 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/app/robots/robot-form/robot-form.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component, Input } from '@angular/core'; 7 | import { Robot } from '../shared/robot.model'; 8 | import { MaterialColor } from '../../shared/material-color'; 9 | import { MaterialColorOptions } from '../../shared/material-color-options'; 10 | 11 | /** 12 | * ロボットのフォームを表示するコンポーネントです。 13 | */ 14 | @Component({ 15 | selector: 'app-robot-form', 16 | templateUrl: './robot-form.component.html', 17 | styleUrls: ['./robot-form.component.css'] 18 | }) 19 | export class RobotFormComponent { 20 | /** ロボット */ 21 | @Input() 22 | edit: Robot; 23 | 24 | /** 候補として表示するネームのオプション */ 25 | nameOptions: string[] = []; 26 | 27 | /** 候補として表示するネームスペースのオプション */ 28 | namespaceOptions: string[] = []; 29 | 30 | /** 候補として表示するカラーのオプション */ 31 | colorOptions: MaterialColor[] = MaterialColorOptions.colorOptions; 32 | 33 | /** 候補として表示するアドレスのオプション */ 34 | addressOptions: string[] = []; 35 | 36 | /** 候補として表示するポートナンバーのオプション */ 37 | portOptions: number[] = []; 38 | 39 | /** 40 | * オブジェクトを構築する 41 | */ 42 | constructor() {} 43 | } 44 | -------------------------------------------------------------------------------- /src/app/settings/setting-detail/setting-detail.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 |

{{model.id}}: {{model.name}} Details

9 | 10 | 11 |
12 | 13 | 19 | 20 |
21 | 22 | 27 | 28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 |
36 |
37 |
38 | -------------------------------------------------------------------------------- /src/app/viewer/input-dialog/input-dialog.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component, Inject } from '@angular/core'; 7 | import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; 8 | import { InputDialogData } from './input-dialog-data'; 9 | 10 | /** 11 | * インプットダイアログのコンポーネントです。 12 | */ 13 | @Component({ 14 | selector: 'app-input-dialog', 15 | templateUrl: './input-dialog.component.html', 16 | styleUrls: ['./input-dialog.component.css'] 17 | }) 18 | export class InputDialogComponent { 19 | /** 20 | * オブジェクトを構築する。 21 | * 22 | * @param dialogRef ダイアログのリファレンス 23 | * @param data ダイアログデータ 24 | */ 25 | constructor( 26 | public dialogRef: MatDialogRef, 27 | @Inject(MAT_DIALOG_DATA) public data: InputDialogData 28 | ) {} 29 | 30 | /** 31 | * ダイアログのOkボタンが押下された際にコールバックされる。 32 | */ 33 | onOkClick(): void { 34 | const inputed = this.data.inputed; 35 | this.dialogRef.close(inputed); 36 | } 37 | 38 | /** 39 | * ダイアログのCancelボタンが押下された際にコールバックされる。 40 | */ 41 | onCancelClick(): void { 42 | this.dialogRef.close(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/app/settings/setting-detail/setting-detail.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component, Input } from '@angular/core'; 7 | import { ActivatedRoute } from '@angular/router'; 8 | import { Location } from '@angular/common'; 9 | import { Setting } from '../shared/setting.model'; 10 | import { SettingService } from '../shared/setting.service'; 11 | import { StorageDetail } from '../../shared/storage/storage-detail'; 12 | 13 | /** 14 | * 設定の詳細を表示するコンポーネントです。 15 | */ 16 | @Component({ 17 | selector: 'app-setting-detail', 18 | templateUrl: './setting-detail.component.html', 19 | styleUrls: ['./setting-detail.component.css'] 20 | }) 21 | export class SettingDetailComponent extends StorageDetail { 22 | /** 設定 */ 23 | @Input() model: Setting; 24 | @Input() edit: Setting; 25 | 26 | /** 27 | * オブジェクトを構築する。 28 | * 29 | * @param service サービス 30 | * @param route ルート 31 | * @param location ロケーション 32 | */ 33 | constructor( 34 | public service: SettingService, 35 | public route: ActivatedRoute, 36 | public location: Location 37 | ) { 38 | super(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/viewer/select-dialog/select-dialog.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component, Inject } from '@angular/core'; 7 | import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; 8 | import { SelectDialogData } from './select-dialog-data'; 9 | 10 | /** 11 | * セレクトダイアログのコンポーネントです。 12 | */ 13 | @Component({ 14 | selector: 'app-select-dialog', 15 | templateUrl: './select-dialog.component.html', 16 | styleUrls: ['./select-dialog.component.css'] 17 | }) 18 | export class SelectDialogComponent { 19 | /** 20 | * オブジェクトを構築する。 21 | * 22 | * @param dialogRef ダイアログのリファレンス 23 | * @param data ダイアログデータ 24 | */ 25 | constructor( 26 | public dialogRef: MatDialogRef, 27 | @Inject(MAT_DIALOG_DATA) public data: SelectDialogData 28 | ) {} 29 | 30 | /** 31 | * ダイアログのOkボタンが押下された際にコールバックされる。 32 | */ 33 | onOkClick(): void { 34 | const selected = this.data.selected; 35 | this.dialogRef.close(selected); 36 | } 37 | 38 | /** 39 | * ダイアログのCancelボタンが押下された際にコールバックされる。 40 | */ 41 | onCancelClick(): void { 42 | this.dialogRef.close(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AMR-IF-UI", 3 | "short_name": "AMR-IF-UI", 4 | "theme_color": "#1976d2", 5 | "background_color": "#fafafa", 6 | "display": "standalone", 7 | "scope": "/", 8 | "start_url": "/", 9 | "icons": [ 10 | { 11 | "src": "assets/icons/icon-72x72.png", 12 | "sizes": "72x72", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "assets/icons/icon-96x96.png", 17 | "sizes": "96x96", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "assets/icons/icon-128x128.png", 22 | "sizes": "128x128", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "assets/icons/icon-144x144.png", 27 | "sizes": "144x144", 28 | "type": "image/png" 29 | }, 30 | { 31 | "src": "assets/icons/icon-152x152.png", 32 | "sizes": "152x152", 33 | "type": "image/png" 34 | }, 35 | { 36 | "src": "assets/icons/icon-192x192.png", 37 | "sizes": "192x192", 38 | "type": "image/png" 39 | }, 40 | { 41 | "src": "assets/icons/icon-384x384.png", 42 | "sizes": "384x384", 43 | "type": "image/png" 44 | }, 45 | { 46 | "src": "assets/icons/icon-512x512.png", 47 | "sizes": "512x512", 48 | "type": "image/png" 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /src/app/shared/config/initializer.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Injectable, Injector } from '@angular/core'; 7 | import { HttpClient } from '@angular/common/http'; 8 | import { ConfigService } from './config.service'; 9 | import { environment } from 'src/environments/environment'; 10 | 11 | /** 12 | * イニシャライザーサービスです。 13 | */ 14 | @Injectable({ 15 | providedIn: 'root' 16 | }) 17 | export class InitializerService { 18 | /** インジェクター */ 19 | static injector: Injector; 20 | 21 | /** 22 | * オブジェクトを構築する。 23 | * 24 | * @param http HTTPクライアント 25 | */ 26 | constructor(private http: HttpClient) {} 27 | 28 | /** 29 | * イニシャライズする。 30 | * 31 | * 他のサービスのコンストラクタが呼ばれる前にconfig.jsonを読み込んで 32 | * environment.configに設定する。 33 | * 34 | * @return プロミス 35 | */ 36 | async initialize(): Promise { 37 | const response = await this.http 38 | .get('./assets/configs/config.json', { 39 | responseType: 'text' 40 | }) 41 | .toPromise(); 42 | const config = JSON.parse(response); 43 | // overwrite 44 | Object.assign(environment.config, config); 45 | 46 | // get configService from global injector 47 | const configService = InitializerService.injector.get(ConfigService); 48 | await configService.load(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 TOSHIBA Corporation. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /src/assets/configs/reset/cmd-lists.json: -------------------------------------------------------------------------------- 1 | { 2 | "array": [ 3 | { 4 | "name": "demo", 5 | "commands": [ 6 | { 7 | "command": { 8 | "nextid": [], 9 | "cmd": "run_nav", 10 | "params": [ 11 | "map_200206b" 12 | ], 13 | "id": "" 14 | }, 15 | "id": 0 16 | }, 17 | { 18 | "command": { 19 | "nextid": [], 20 | "cmd": "set_pose", 21 | "params": [ 22 | "map_200206b", 23 | "P000" 24 | ], 25 | "id": "" 26 | }, 27 | "id": 1 28 | }, 29 | { 30 | "command": { 31 | "nextid": [], 32 | "cmd": "goto", 33 | "params": [ 34 | "map_200206b", 35 | "P001" 36 | ], 37 | "id": "c2" 38 | }, 39 | "id": 2 40 | }, 41 | { 42 | "command": { 43 | "nextid": [ 44 | "c2", 45 | "" 46 | ], 47 | "cmd": "goto", 48 | "params": [ 49 | "map_200206b", 50 | "P000" 51 | ], 52 | "id": "" 53 | }, 54 | "id": 3 55 | } 56 | ], 57 | "id": 0, 58 | "mode": "custom" 59 | } 60 | ], 61 | "nextId": 1 62 | } 63 | -------------------------------------------------------------------------------- /src/app/cmd-lists/cmd-lists.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |

CmdLists

8 | 9 |
10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
IdNameCommands
{{ row.id }}{{ row.name }}{{ toStringBrief(row.commands, 80) }}
29 |
30 |
31 | 32 |
33 | 34 | 35 | 36 | 37 | New cmdList 38 | Type cmdList info 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 | -------------------------------------------------------------------------------- /src/app/cmd-lists/cmd-list-detail/cmd-list-detail.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component, Input } from '@angular/core'; 7 | import { ActivatedRoute } from '@angular/router'; 8 | import { Location } from '@angular/common'; 9 | import { CmdList } from '../shared/cmd-list.model'; 10 | import { CmdListService } from '../shared/cmd-list.service'; 11 | import { StorageDetail } from '../../shared/storage/storage-detail'; 12 | import { CommandService } from '../../commands/shared/command.service'; 13 | 14 | /** 15 | * コマンドリストの詳細を表示するコンポーネントです。 16 | */ 17 | @Component({ 18 | selector: 'app-cmd-list-detail', 19 | templateUrl: './cmd-list-detail.component.html', 20 | styleUrls: ['./cmd-list-detail.component.css'] 21 | }) 22 | export class CmdListDetailComponent extends StorageDetail { 23 | /** コマンドリスト */ 24 | @Input() 25 | model: CmdList; 26 | 27 | /** 28 | * オブジェクトを構築する。 29 | * 30 | * @param service サービス 31 | * @param route ルート 32 | * @param location ロケーション 33 | * @param commandService コマンドサービス 34 | */ 35 | constructor( 36 | public service: CmdListService, 37 | public route: ActivatedRoute, 38 | public location: Location, 39 | public commandService: CommandService 40 | ) { 41 | super(); 42 | } 43 | 44 | /** 45 | * 編集結果をアップデートする。 46 | * 47 | * 編集結果をモデルに反映させてストレージにストアして元の場所に戻る。 48 | * 49 | * @return プロミス 50 | */ 51 | async update(): Promise { 52 | this.edit.commands = this.commandService.getAll(); 53 | await super.update(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/app/robots/robots.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |

Robots

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 |
IdNameColorNamespaceAddressPort
{{ row.id }}{{ row.name }}{{ row.color }}{{ row.namespace }}{{ row.address }}{{ row.port }}
35 |
36 |
37 | 38 |
39 | 40 | 41 | 42 | 43 | New robot 44 | Type robot info 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
53 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { NgModule } from '@angular/core'; 7 | import { RouterModule, Routes } from '@angular/router'; 8 | import { HomeComponent } from './home/home.component'; 9 | import { RobotsComponent } from './robots/robots.component'; 10 | import { RobotDetailComponent } from './robots/robot-detail/robot-detail.component'; 11 | import { SettingsComponent } from './settings/settings.component'; 12 | import { PosesComponent } from './poses/poses.component'; 13 | import { PoseDetailComponent } from './poses/pose-detail/pose-detail.component'; 14 | import { SettingDetailComponent } from './settings/setting-detail/setting-detail.component'; 15 | import { CmdListsComponent } from './cmd-lists/cmd-lists.component'; 16 | import { CmdListDetailComponent } from './cmd-lists/cmd-list-detail/cmd-list-detail.component'; 17 | 18 | const routes: Routes = [ 19 | { path: '', redirectTo: '/', pathMatch: 'full' }, 20 | { path: '', component: HomeComponent }, 21 | { path: 'robots', component: RobotsComponent }, 22 | { path: 'robot/detail/:id', component: RobotDetailComponent }, 23 | { path: 'poses', component: PosesComponent }, 24 | { path: 'pose/detail/:id', component: PoseDetailComponent }, 25 | { path: 'cmd-lists', component: CmdListsComponent }, 26 | { path: 'cmd-list/detail/:id', component: CmdListDetailComponent }, 27 | { path: 'settings', component: SettingsComponent }, 28 | { path: 'setting/detail/:id', component: SettingDetailComponent }, 29 | { path: '**', component: HomeComponent } 30 | ]; 31 | 32 | @NgModule({ 33 | imports: [RouterModule.forRoot(routes)], 34 | exports: [RouterModule] 35 | }) 36 | export class AppRoutingModule { } 37 | -------------------------------------------------------------------------------- /src/app/settings/settings.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component, Renderer2 } from '@angular/core'; 7 | import { Router } from '@angular/router'; 8 | import { Setting } from './shared/setting.model'; 9 | import { ConfigService } from '../shared/config/config.service'; 10 | import { SettingService } from './shared/setting.service'; 11 | import { StorageComponent } from '../shared/storage/storage-component'; 12 | import { StoreService } from '../shared/store/store.service'; 13 | 14 | /** 15 | * セッティングのコンポーネントです。 16 | */ 17 | @Component({ 18 | selector: 'app-settings', 19 | templateUrl: './settings.component.html', 20 | styleUrls: ['./settings.component.css'] 21 | }) 22 | export class SettingsComponent extends StorageComponent { 23 | /** ダウンロードプレフィックスネーム */ 24 | downloadPrefixName = 'settings'; 25 | 26 | /** 27 | * オブジェクトを構築する。 28 | * 29 | * @param service セッティングサービス 30 | * @param storeService ストアサービス 31 | * @param configService コンフィグサービス 32 | * @param renderer2 レンダラー2 33 | * @param router ルーター 34 | */ 35 | constructor( 36 | public service: SettingService, 37 | public storeService: StoreService, 38 | private configService: ConfigService, 39 | public renderer2: Renderer2, 40 | private router: Router 41 | ) { 42 | super(); 43 | } 44 | 45 | /** 46 | * データをクリアする。 47 | */ 48 | async clear(): Promise { 49 | await super.clear(); 50 | await this.service.addSettings(); 51 | } 52 | 53 | /** 54 | * データをすべて消去し再ロードする。 55 | * 56 | * @return プロミス 57 | */ 58 | async reset(): Promise { 59 | await this.configService.removeAll(); 60 | await this.router.navigate(['./']); 61 | window.location.reload(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/app/shared/material-color-options.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { MaterialColor } from './material-color'; 7 | 8 | /** 9 | * マテリアルカラーオプションです。 10 | */ 11 | export class MaterialColorOptions { 12 | /** 候補として表示するカラーのオプション */ 13 | static colorOptions: MaterialColor[] = [ 14 | { 15 | name: 'Red 500', 16 | value: '#F4433680' 17 | }, 18 | { 19 | name: 'Pink 500', 20 | value: '#E91E6380' 21 | }, 22 | { 23 | name: 'Purple 500', 24 | value: '#9C27B080' 25 | }, 26 | { 27 | name: 'Deep Purple 500', 28 | value: '#673AB780' 29 | }, 30 | { 31 | name: 'Indigo 500', 32 | value: '#3F51B580' 33 | }, 34 | { 35 | name: 'Blue 500', 36 | value: '#2196F380' 37 | }, 38 | { 39 | name: 'Light Blue 500', 40 | value: '#03A9F480' 41 | }, 42 | { 43 | name: 'Cyan 500', 44 | value: '#00BCD480' 45 | }, 46 | { 47 | name: 'Teal 500', 48 | value: '#00968880' 49 | }, 50 | { 51 | name: 'Green 500', 52 | value: '#4CAF5080' 53 | }, 54 | { 55 | name: 'Light Green 500', 56 | value: '#8BC34A80' 57 | }, 58 | { 59 | name: 'Lime 500', 60 | value: '#CDDC3980' 61 | }, 62 | { 63 | name: 'Yellow 500', 64 | value: '#FFEB3B80' 65 | }, 66 | { 67 | name: 'Amber 500', 68 | value: '#FFC10780' 69 | }, 70 | { 71 | name: 'Orange 500', 72 | value: '#FF980080' 73 | }, 74 | { 75 | name: 'Deep Orange 500', 76 | value: '#FF572280' 77 | }, 78 | { 79 | name: 'Brown 500', 80 | value: '#79554880' 81 | }, 82 | { 83 | name: 'Grey 500', 84 | value: '#9E9E9E80' 85 | }, 86 | { 87 | name: 'Blue Grey 500', 88 | value: '#607D8B80' 89 | } 90 | ]; 91 | } 92 | -------------------------------------------------------------------------------- /src/app/poses/poses.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |

Poses

8 | 9 |
10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 39 | 40 |
IdNameColorPositionOrientation
{{row.id}}{{row.name}}{{row.color}} 30 | ({{ row.rospose.position.x | number }}, 31 | {{ row.rospose.position.y | number }}, 32 | {{ row.rospose.position.z | number }}) 33 | 35 | ({{ row.rospose.orientation.y | number }}, 36 | {{ row.rospose.orientation.z | number }}, 37 | {{ row.rospose.orientation.w | number }}) 38 |
41 |
42 |
43 | 44 |
45 | 46 | 47 | 48 | 49 | New pose 50 | Type pose info 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
59 | -------------------------------------------------------------------------------- /src/app/viewer/shared/markers/ros-marker.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import 'pixi.js'; 7 | import { BaseViewer } from '../base-viewer'; 8 | import { BaseMarker } from './base-marker'; 9 | 10 | /** 11 | * Rosマーカーです。 12 | */ 13 | export class RosMarker extends BaseMarker { 14 | /** Rosワールドコンテナ */ 15 | rosWorld: PIXI.Container; 16 | 17 | /** スケーリングRosワールドコンテナ */ 18 | rosWorldScale: PIXI.Container; 19 | 20 | /** 21 | * オブジェクトを構築する。 22 | * 23 | * RosワールドコンテナをビューワーのRosワールドコンテナに追加する。 24 | * スケーリングRosワールドコンテナをビューワーのスケーリングRosワールドコンテナに追加する。 25 | * 26 | * コンテナの親子関係 27 | * Viewer.root -> BaseMarker.root 28 | * Viewer.rosWorld -> RosMarker.rosWorld 29 | * Viewer.rosWorldScale -> RosMarker.rosWorldScale 30 | * 31 | * @param viewer ビューワー 32 | */ 33 | constructor(viewer: BaseViewer) { 34 | super(viewer); 35 | this.rosWorld = new PIXI.Container(); 36 | this.viewer.rosWorld.addChild(this.rosWorld); 37 | this.rosWorldScale = new PIXI.Container(); 38 | this.viewer.rosWorldScale.addChild(this.rosWorldScale); 39 | } 40 | 41 | /** 42 | * 表示する。 43 | * 44 | * Rosワールドコンテナを表示する。 45 | * スケーリングRosワールドコンテナを表示する。 46 | */ 47 | show(): void { 48 | super.show(); 49 | this.rosWorld.visible = true; 50 | this.rosWorldScale.visible = true; 51 | } 52 | 53 | /** 54 | * 非表示にする。 55 | * 56 | * Rosワールドコンテナを非表示にする。 57 | * スケーリングRosワールドコンテナを非表示にする。 58 | */ 59 | hide(): void { 60 | super.hide(); 61 | this.rosWorld.visible = false; 62 | this.rosWorldScale.visible = false; 63 | } 64 | 65 | /** 66 | * 削除する。 67 | * 68 | * RosワールドコンテナをビューワーのRosワールドコンテナから削除する。 69 | * スケーリングRosワールドコンテナをビューワーのスケーリングRosワールドコンテナから削除する。 70 | */ 71 | remove(): void { 72 | super.remove(); 73 | this.viewer.rosWorld.removeChild(this.rosWorld); 74 | this.viewer.rosWorldScale.removeChild(this.rosWorldScale); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AMR-IF-UI", 3 | "version": "1.0.0", 4 | "license": "BSD-2-Clause", 5 | "description": "AMR-IF-UI is application that implements a common IF for AMR(Autonomous Mobile Robot)", 6 | "homepage": "https://github.com/AMR-IF/AMR-IF-UI", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/AMR-IF/AMR-IF-UI.git" 10 | }, 11 | "scripts": { 12 | "ng": "ng", 13 | "start": "ng serve", 14 | "build": "ng build", 15 | "lint": "ng lint" 16 | }, 17 | "private": true, 18 | "dependencies": { 19 | "@angular/animations": "~9.1.0", 20 | "@angular/cdk": "^9.2.0", 21 | "@angular/common": "~9.1.0", 22 | "@angular/compiler": "~9.1.0", 23 | "@angular/core": "~9.1.0", 24 | "@angular/forms": "~9.1.0", 25 | "@angular/material": "^9.2.0", 26 | "@angular/platform-browser": "~9.1.0", 27 | "@angular/platform-browser-dynamic": "~9.1.0", 28 | "@angular/pwa": "^0.901.0", 29 | "@angular/router": "~9.1.0", 30 | "@angular/service-worker": "~9.1.0", 31 | "ngx-build-plus": "^9.0.6", 32 | "pixi-layers": "^0.2.3", 33 | "pixi.js": "^5.2.1", 34 | "roslib": "^1.1.0", 35 | "rxjs": "~6.5.4", 36 | "tslib": "^1.11.1", 37 | "zone.js": "~0.10.3" 38 | }, 39 | "devDependencies": { 40 | "@angular-devkit/build-angular": "~0.901.0", 41 | "@angular/cli": "~9.1.0", 42 | "@angular/compiler-cli": "~9.1.0", 43 | "@angular/language-service": "~9.1.0", 44 | "@types/jasmine": "~3.5.10", 45 | "@types/jasminewd2": "~2.0.8", 46 | "@types/node": "^13.9.4", 47 | "@types/roslib": "^1.0.0", 48 | "codelyzer": "^5.2.1", 49 | "jasmine-core": "~3.5.0", 50 | "jasmine-spec-reporter": "~5.0.1", 51 | "karma": "~4.4.1", 52 | "karma-chrome-launcher": "~3.1.0", 53 | "karma-coverage-istanbul-reporter": "~2.1.1", 54 | "karma-jasmine": "~3.1.1", 55 | "karma-jasmine-html-reporter": "^1.5.3", 56 | "protractor": "^5.4.3", 57 | "ts-node": "~8.8.1", 58 | "tslint": "~6.1.0", 59 | "typescript": "~3.7.5" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | # AMR-IF-UI: インストール 2 | 3 | 4 | - [1. インストール](#1-インストール) 5 | - [1.1. コード取得](#11-コード取得) 6 | - [1.2. 依存パッケージのインストール](#12-依存パッケージのインストール) 7 | - [1.3. アクセステスト](#13-アクセステスト) 8 | 9 | 10 | 11 | ## 1. インストール 12 | 13 | ### 1.1. コード取得 14 | 15 | 本AMR-IFは、githubリポジトリからダウンロードするなどして適当なディレクトリに展開してください。 16 | 17 | ```shell 18 | $ git clone https://github.com/robo-marc/AMR-IF-UI 19 | $ cd AMR-IF-UI 20 | $ ls 21 | LICENSE doc package-lock.json tsconfig.spec.json 22 | LICENSE_HEADER img package.json tslint.json 23 | README.md karma.conf.js src 24 | angular.json ngsw-config.json tsconfig.app.json 25 | browserslist node_modules tsconfig.json 26 | ``` 27 | 28 | ### 1.2. 依存パッケージのインストール 29 | 30 | ここで、npm ci コマンドで依存パッケージをインストールします。 31 | 32 | ```shell 33 | $ sudo npm ci 34 | : 35 | > @angular/cli@9.1.0 postinstall /home/n-ando/work/AMR-IF-UI/node_modules/@angular/cli 36 | > node ./bin/postinstall/script.js 37 | 38 | added 1476 packages in 31.457s 39 | $ sudo npm install -g @angular/cli 40 | ``` 41 | 42 | ```shell 43 | $ ng serve 44 | : 45 | 少し時間がかかります。 46 | : 47 | Date: 2021-09-23T08:04:01.730Z - Hash: 98b0164d374b20b98c5c 48 | 5 unchanged chunks 49 | 50 | Time: 542ms 51 | : Compiled successfully. 52 | ``` 53 | 54 | このとき、以下のように、GoogleのAngularチームと使用状況データを共有するか、以下のように確認のメッセージが表示されますので、特に問題がなければ "y" を入力して先に進みます。(そういうのを好まない場合は "n" でも結構です。) 55 | 56 | ```shell 57 | ? Would you like to share anonymous usage data with the Angular Team at Google u 58 | nder Google’s Privacy Policy at https://policies.google.com/privacy? 59 | For more details and how to change this setting, see http://angular.io/analytics. 60 | ``` 61 | 62 | "ng serve" コマンド実行後は、コマンドプロンプトは帰ってきませんが、これは裏でHTTPサービスが動作しているためです。次のアクセステストに進み、Webサービスにアクセスできるかテストします。 63 | 64 | ### 1.3. アクセステスト 65 | 66 | ブラウザから、http://localhost:4200 にアクセスしてみてください。 67 | 68 | - [http://localhost:4200](http://localhost:4200) 69 | 70 | 以下のような画面が表示されるはずです。 71 | 72 | 73 | 74 | ポートは以下のように "--port" オプションで変更可能です。 75 | 76 | ```shell 77 | $ ng server --port 8080 78 | ``` 79 | 1023番以下のポートにする場合は root 権限が必要ですので、コマンドの前に sudo などが必要です。 80 | -------------------------------------------------------------------------------- /docs/deploy.md: -------------------------------------------------------------------------------- 1 | # AMR-IF-UI: デプロイ 2 | 3 | 4 | 5 | - [1. デプロイメント](#1-デプロイメント) 6 | - [1.1. ビルド](#11-ビルド) 7 | - [1.2. ベースURLの変更](#12-ベースurlの変更) 8 | 9 | 10 | 11 | ## 1. デプロイメント 12 | 13 | AMR-IF-UI は、Angularフレームワークで構築されており、 14 | AOT (Ahead-of-time) コンパイラで事前にトランスパイル (ビルドする) することで、 15 | ブラウザでのロード速度を向上させることができる事ができ、 16 | nodejs/npm 等がインストールされていないWebサーバ上へデプロイ(配置)することができます。 17 | (ただし、AMR-IF-UI自体は ROS/Robot Web Tools に依存していますので、 18 | 必要なパッケージは別途インストールする必要があります。) 19 | 20 | ### 1.1. ビルド 21 | 22 | AMR-IF-UI のプロジェクトルートディレクトリで ng build コマンドを入力します。 23 | 24 | ```shell 25 | $ ls 26 | LICENSE README.md browserslist img 27 | ngsw-config.json package-lock.json src tsconfig.json 28 | tslint.json LICENSE_HEADER angular.json karma.conf.js 29 | package.json tsconfig.app.json tsconfig.spec.json 30 | $ ng build 31 | Compiling @angular/cdk/keycodes : es2015 as esm2015 32 | Compiling @angular/animations : es2015 as esm2015 33 | : 34 | 中略 35 | : 36 | chunk {vendor} vendor-es5.js, vendor-es5.js.map (vendor) 7.42 MB [initial] [rendered] 37 | Date: 2021-09-23T14:35:03.634Z - Hash: 36137cafdae0110f8877 - Time: 101231ms 38 | 39 | ``` 40 | 41 | dist/AMR-IF-UI というディレクトリが作成されており、 42 | その中にデプロイ可能なパッケージが生成されています。 43 | 44 | ```shell 45 | $ cd dist/AMR-IF-UI/ 46 | $ ls 47 | assets main-es2015.js main-es5.js manifest.webmanifest 48 | : 49 | styles-es2015.js styles-es5.js vendor-es2015.js vendor-es5.js 50 | $ 51 | ``` 52 | 53 | このディレクトリ内のファイルをHTTPサーバの公開可能なディレクトリへコピーすることで、 54 | このプロジェクトを公開可能です。 55 | 56 | 仮に、いま "/var/www/data" が空であり、 apache の Document Root であると仮定すると 57 | ```shell 58 | $ sudo cp * /var/www/data/ 59 | ``` 60 | 61 | apacheが動作していれば、http://localhost にアクセスすると以下の画面が表示されます。 62 | 63 | - [http://localhost](http://localhost) 64 | 65 | 66 | 67 | ### 1.2. ベースURLの変更 68 | 69 | Webサーバのドキュメントルートで公開するのではなく、 70 | 場合によっては特定の階層下のURLで公開したいことがあります。 71 | 72 | その場合は、build するときに、--base-href= オプションを使用して 73 | 公開する階層のパスを指定します。 74 | 75 | ```shell 76 | $ cd 77 | $ ng build --base-href=/AMR-IF-UI/ 78 | $ cd dist 79 | $ sudo cp -r AMR-IF-UI /var/www/data/ 80 | ``` 81 | 82 | こうすると、AMR-IF-UI を http://localhost/AMR-IF-UI/ でアクセスすることができます。 83 | 84 | - [http://localhost/AMR-IF-UI/](http://localhost/AMR-IF-UI/) 85 | 86 | -------------------------------------------------------------------------------- /src/app/viewer/shared/markers/grid-marker.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import 'pixi.js'; 7 | import { BaseViewer } from '../base-viewer'; 8 | import { RosMarker } from './ros-marker'; 9 | 10 | /** 11 | * グリッドマーカーです。 12 | */ 13 | export class GridMarker extends RosMarker { 14 | /** コンテナ */ 15 | container: PIXI.Container; 16 | 17 | /** XYグリッド */ 18 | xyGrid: PIXI.Graphics; 19 | 20 | /** 21 | * オブジェクトを構築する。 22 | * 23 | * @param viewer ビューワー 24 | */ 25 | constructor(viewer: BaseViewer) { 26 | super(viewer); 27 | 28 | this.container = new PIXI.Container(); 29 | this.xyGrid = new PIXI.Graphics(); 30 | this.container.addChild(this.xyGrid); 31 | this.container.parentGroup = this.viewer.gridGroup; 32 | this.rosWorld.addChild(this.container); 33 | 34 | this.redraw(); 35 | } 36 | 37 | /** 38 | * グリッドを描画する。 39 | */ 40 | redraw(): void { 41 | const pixelPerMeter = this.viewer.resolution; 42 | const gridSize = 100; 43 | const alpha = 0.5; 44 | const color = 0x000000; 45 | const lineWidth = 1; 46 | const width = pixelPerMeter * gridSize; 47 | const height = pixelPerMeter * gridSize; 48 | 49 | const xyGrid = this.xyGrid; 50 | xyGrid.clear(); 51 | xyGrid.lineStyle(lineWidth, color, alpha); 52 | 53 | // x-Grid 54 | xyGrid.moveTo(-width, 0); 55 | xyGrid.lineTo(width, 0); 56 | for (let y = pixelPerMeter; y < height; y += pixelPerMeter) { 57 | xyGrid.moveTo(-width, y); 58 | xyGrid.lineTo(width, y); 59 | } 60 | for (let y = -pixelPerMeter; y > -height; y -= pixelPerMeter) { 61 | xyGrid.moveTo(-width, y); 62 | xyGrid.lineTo(width, y); 63 | } 64 | 65 | // y-Grid 66 | xyGrid.moveTo(0, -height); 67 | xyGrid.lineTo(0, height); 68 | for (let x = pixelPerMeter; x < width; x += pixelPerMeter) { 69 | xyGrid.moveTo(x, -height); 70 | xyGrid.lineTo(x, height); 71 | } 72 | for (let x = -pixelPerMeter; x > -width; x -= pixelPerMeter) { 73 | xyGrid.moveTo(x, -height); 74 | xyGrid.lineTo(x, height); 75 | } 76 | } 77 | 78 | /** 79 | * ビューが変化した際にアップデートする。 80 | * 81 | * リドローする。 82 | */ 83 | update(): void { 84 | this.redraw(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "array-type": false, 5 | "arrow-parens": false, 6 | "deprecation": { 7 | "severity": "warn" 8 | }, 9 | "component-class-suffix": true, 10 | "contextual-lifecycle": true, 11 | "directive-class-suffix": true, 12 | "directive-selector": [ 13 | true, 14 | "attribute", 15 | "app", 16 | "camelCase" 17 | ], 18 | "component-selector": [ 19 | true, 20 | "element", 21 | "app", 22 | "kebab-case" 23 | ], 24 | "import-blacklist": [ 25 | true, 26 | "rxjs/Rx" 27 | ], 28 | "interface-name": false, 29 | "max-classes-per-file": false, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-consecutive-blank-lines": false, 47 | "no-console": [ 48 | true, 49 | "debug", 50 | "info", 51 | "time", 52 | "timeEnd", 53 | "trace" 54 | ], 55 | "no-empty": false, 56 | "no-inferrable-types": [ 57 | true, 58 | "ignore-params" 59 | ], 60 | "no-non-null-assertion": true, 61 | "no-redundant-jsdoc": true, 62 | "no-switch-case-fall-through": true, 63 | "no-var-requires": false, 64 | "object-literal-key-quotes": [ 65 | true, 66 | "as-needed" 67 | ], 68 | "object-literal-sort-keys": false, 69 | "ordered-imports": false, 70 | "quotemark": [ 71 | true, 72 | "single" 73 | ], 74 | "trailing-comma": false, 75 | "no-conflicting-lifecycle": true, 76 | "no-host-metadata-property": true, 77 | "no-input-rename": true, 78 | "no-inputs-metadata-property": true, 79 | "no-output-native": true, 80 | "no-output-on-prefix": true, 81 | "no-output-rename": true, 82 | "no-outputs-metadata-property": true, 83 | "template-banana-in-box": true, 84 | "template-no-negated-async": true, 85 | "use-lifecycle-interface": true, 86 | "use-pipe-transform-interface": true, 87 | "no-bitwise": false 88 | }, 89 | "rulesDirectory": [ 90 | "codelyzer" 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /src/app/viewer/shared/base-viewer-component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Renderer2, NgZone, ElementRef } from '@angular/core'; 7 | import { MatDialog } from '@angular/material/dialog'; 8 | import { SettingService } from '../../settings/shared/setting.service'; 9 | import { RosService } from '../../shared/ros/ros.service'; 10 | import { PoseService } from '../../poses/shared/pose.service'; 11 | import { RobotService } from '../../robots/shared/robot.service'; 12 | import { CommandService } from '../../commands/shared/command.service'; 13 | import { CmdListService } from '../../cmd-lists/shared/cmd-list.service'; 14 | 15 | /** 16 | * ベースビューワーコンポーネントです。 17 | */ 18 | export interface BaseViewerComponent { 19 | /** 20 | * キャンバスプレースホルダーエレメントリファレンスをゲットする。 21 | * 22 | * @return キャンバスプレースホルダーエレメントリファレンス 23 | */ 24 | getCanvasesPlaceholderElementRef(): ElementRef; 25 | 26 | /** 27 | * ビューワーエレメントリファレンスをゲットする。 28 | * 29 | * @return ビューワーエレメントリファレンス 30 | */ 31 | getViewerElementRef(): ElementRef; 32 | 33 | /** 34 | * セッティングサービスをゲットする。 35 | * 36 | * @return セッティングサービス 37 | */ 38 | getSettingService(): SettingService; 39 | 40 | /** 41 | * ROSサービスをゲットする。 42 | * 43 | * @return ROSサービス 44 | */ 45 | getRosService(): RosService; 46 | 47 | /** 48 | * ポーズサービスをゲットする。 49 | * 50 | * @return ポーズサービス 51 | */ 52 | getPoseService(): PoseService; 53 | 54 | /** 55 | * ロボットサービスをゲットする。 56 | * 57 | * @return ロボットサービス 58 | */ 59 | getRobotService(): RobotService; 60 | 61 | /** 62 | * コマンドサービスをゲットする。 63 | * 64 | * @return コマンドサービス 65 | */ 66 | getCommandService(): CommandService; 67 | 68 | /** 69 | * コマンドリストサービスをゲットする。 70 | * 71 | * @return コマンドリストサービス 72 | */ 73 | getCmdListService(): CmdListService; 74 | 75 | /** 76 | * インプットダイアログをゲットする。 77 | * 78 | * @return ダイアログ 79 | */ 80 | getInputDialog(): MatDialog; 81 | 82 | /** 83 | * セレクトダイアログをゲットする。 84 | * 85 | * @return ダイアログ 86 | */ 87 | getSelectDialog(): MatDialog; 88 | 89 | /** 90 | * レンダラーをゲットする。 91 | * 92 | * @return レンダラー 93 | */ 94 | getRenderer2(): Renderer2; 95 | 96 | /** 97 | * ゾーンをゲットする。 98 | * 99 | * @return ゾーン 100 | */ 101 | getNgZone(): NgZone; 102 | } 103 | -------------------------------------------------------------------------------- /src/app/shared/storage/storage-detail.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { OnInit } from '@angular/core'; 7 | import { ActivatedRoute } from '@angular/router'; 8 | import { Location } from '@angular/common'; 9 | import { PickableModel } from '../pickable-model'; 10 | import { StorageService } from './storage-service'; 11 | 12 | /** 13 | * ストレージモデルデータの詳細を表示するコンポーネントの抽象基底クラスです。 14 | */ 15 | export abstract class StorageDetail< 16 | M extends PickableModel, 17 | S extends StorageService 18 | > implements OnInit { 19 | /** 編集中のモデル */ 20 | edit: M; 21 | 22 | /** モデル */ 23 | abstract model: M; 24 | 25 | /** サービス */ 26 | abstract service: S; 27 | 28 | /** ルート */ 29 | abstract route: ActivatedRoute; 30 | 31 | /** ロケーション */ 32 | abstract location: Location; 33 | 34 | /** 35 | * オブジェクトを構築する。 36 | */ 37 | constructor() {} 38 | 39 | /** 40 | * コンポーネントを初期化する。 41 | * ルートパラメータからモデルのアイディーを取得し、サービスからモデルを取得する。 42 | * 取得したモデルのデータを編集中のモデルにアサインする。 43 | */ 44 | ngOnInit(): void { 45 | const id = this.route.snapshot.paramMap.get('id'); 46 | if (id != null) { 47 | const model = this.service.getById(+id); 48 | if (model != null) { 49 | this.model = model; 50 | this.edit = this.deepCopy(this.model); 51 | } else { 52 | this.location.back(); 53 | } 54 | } else { 55 | this.location.back(); 56 | } 57 | } 58 | 59 | /** 60 | * JSONに変換してパースすることでディープコピーオブジェクトを取得する。 61 | * 62 | * @param model モデル 63 | */ 64 | deepCopy(model: M): M { 65 | const json = JSON.stringify(model); 66 | return JSON.parse(json); 67 | } 68 | 69 | /** 70 | * 編集をキャンセルして元の場所に戻る。 71 | */ 72 | cancel(): void { 73 | this.location.back(); 74 | } 75 | 76 | /** 77 | * 編集結果をモデルに反映させてストレージにストアして元の場所に戻る。 78 | * 79 | * @return プロミス 80 | */ 81 | async update(): Promise { 82 | if (this.service.validate(this.edit)) { 83 | this.service.assign(this.model, this.edit); 84 | await this.service.store(); 85 | this.location.back(); 86 | } 87 | } 88 | 89 | /** 90 | * モデルを削除して削除できた場合は元のページに戻る。 91 | * 92 | * @return プロミス 93 | */ 94 | async delete(): Promise { 95 | const result = await this.service.remove(this.model); 96 | if (result) { 97 | this.location.back(); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/app/poses/pose-form/pose-form.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 |
9 | 10 | 16 | MyPose 17 | 18 | 19 | {{ option }} 20 | 21 | 22 | 23 | 24 | 30 | #000000FF 31 | 32 | 37 | {{ option.value }} [{{ option.name }})] 38 | 39 | 40 | 41 |
42 |
43 | 44 | 52 | x 53 | 54 | 55 | 63 | y 64 | 65 | 66 | 75 | yaw[degree] 76 | 77 |
78 |
79 |
80 | -------------------------------------------------------------------------------- /src/app/cmd-lists/cmd-lists.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component, Renderer2 } from '@angular/core'; 7 | import { CmdList } from './shared/cmd-list.model'; 8 | import { CmdListService } from './shared/cmd-list.service'; 9 | import { StorageComponent } from '../shared/storage/storage-component'; 10 | import { Command } from '../commands/shared/command.model'; 11 | import { CommandService } from '../commands/shared/command.service'; 12 | import { StoreService } from '../shared/store/store.service'; 13 | 14 | /** 15 | * コマンドリストのコンポーネントです。 16 | */ 17 | @Component({ 18 | selector: 'app-cmd-lists', 19 | templateUrl: './cmd-lists.component.html', 20 | styleUrls: ['./cmd-lists.component.css'] 21 | }) 22 | export class CmdListsComponent extends StorageComponent { 23 | /** ダウンロードプレフィックスネーム */ 24 | downloadPrefixName = 'cmd-lists'; 25 | 26 | /** 27 | * オブジェクトを構築する。 28 | * 29 | * @param service コマンドリストサービス 30 | * @param commandService コマンドサービス 31 | * @param renderer2 レンダラー2 32 | * @param storeService ストアサービス 33 | */ 34 | constructor( 35 | public service: CmdListService, 36 | public storeService: StoreService, 37 | public commandService: CommandService, 38 | public renderer2: Renderer2 39 | ) { 40 | super(); 41 | } 42 | 43 | /** 44 | * コマンドを文字列に変換する。 45 | * 46 | * @param commands コマンドリスト 47 | * @return コマンドリスト文字列表現 48 | */ 49 | toString(commands: Command[]): string { 50 | let text = ''; 51 | for (let i = 0; i < commands.length; i++) { 52 | if (i !== 0) { 53 | text += ', '; 54 | } 55 | text += commands[i].command.cmd; 56 | } 57 | return text; 58 | } 59 | 60 | /** 61 | * コマンドを指定文字以内の文字列に変換する。 62 | * 63 | * @param commands コマンドリスト 64 | * @param commands コマンドリスト 65 | * @return コマンドリスト文字列表現 66 | */ 67 | toStringBrief(commands: Command[], maxLength: number): string { 68 | let text = this.toString(commands); 69 | if (maxLength < text.length) { 70 | text = text.substring(0, maxLength - 3); 71 | text += '...'; 72 | } 73 | return text; 74 | } 75 | 76 | /** 77 | * フォームに入力された新規アイテムを追加する。 78 | * 79 | * 編集されたコマンドを取得して追加後にクリアする。 80 | */ 81 | async add(): Promise { 82 | this.edit.commands = this.commandService.getAll(); 83 | const result = await super.add(); 84 | await this.commandService.clear(); 85 | await this.commandService.setArray(this.edit.commands); 86 | return result; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/app/shared/storage/storage-service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Observable } from 'rxjs'; 7 | import { PickableService } from '../pickable-service'; 8 | import { PickableModel } from '../pickable-model'; 9 | import { StorageData } from './storage-data'; 10 | 11 | /** 12 | * ストレージサービスです。 13 | */ 14 | export interface StorageService 15 | extends PickableService { 16 | /** 17 | * 初期データを生成して返却する。 18 | * 19 | * @return 初期データ 20 | */ 21 | getInitialData(): StorageData; 22 | 23 | /** 24 | * 初期化する。 25 | * 26 | * @return プロミス 27 | */ 28 | initialize(): Promise; 29 | 30 | /** 31 | * ストレージからデータをリストアする。 32 | * 33 | * @return プロミス 34 | */ 35 | restoreFromStorage(): Promise; 36 | 37 | /** 38 | * JOSNからデータをリストアする。 39 | * 40 | * @param json JSON文字列 41 | */ 42 | restoreFromJSON(json: string): void; 43 | 44 | /** 45 | * オブジェクトからデータをリストアする。 46 | * 47 | * @param object オブジェクト 48 | */ 49 | restoreFromObject(object: any): void; 50 | 51 | /** 52 | * データをクリアする。 53 | * 54 | * @return プロミス 55 | */ 56 | clear(): Promise; 57 | 58 | /** 59 | * 関連データとデータをストレージにストアする。 60 | * 61 | * @return プロミス 62 | */ 63 | store(): Promise; 64 | 65 | /** 66 | * 全てのデータを取得する。 67 | * 68 | * @return 全てのデータ 69 | */ 70 | getAll(): M[]; 71 | 72 | /** 73 | * 対象モデルをデータに追加する。 74 | * 75 | * @param model 対象モデル 76 | * @return プロミス 77 | */ 78 | add(model: M): Promise; 79 | 80 | /** 81 | * 対象モデルをデータから削除する。 82 | * 83 | * @param model 対象モデル 84 | * @return プロミス 85 | */ 86 | remove(model: M): Promise; 87 | 88 | /** 89 | * データのオブザーバブルを取得する。 90 | * 91 | * @return データのオブザーバブル 92 | */ 93 | getObservable(): Observable>; 94 | 95 | /** 96 | * データの変更を通知する。 97 | */ 98 | notify(): void; 99 | 100 | /** 101 | * データのJSONを取得する。 102 | * 103 | * @return データのJSON 104 | */ 105 | getJSON(): string; 106 | 107 | /** 108 | * ソースモデルをターゲットモデルにアサインする。 109 | * 110 | * @param target ターゲットモデル 111 | * @param source ソースモデル 112 | */ 113 | assign(target: M, source: M): void; 114 | 115 | /** 116 | * 空のモデルを取得する。 117 | * 118 | * @return 空のモデル 119 | */ 120 | getEmptyModel(): M; 121 | 122 | /** 123 | * バリデートする。 124 | * 125 | * @param edit エディットされたモデル 126 | * @return バリデートリザルト 127 | */ 128 | validate(edit: M): boolean; 129 | } 130 | -------------------------------------------------------------------------------- /src/app/viewer/shared/base-viewer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Observable } from 'rxjs'; 7 | import { BaseViewerComponent } from './base-viewer-component'; 8 | import { Viewer } from './viewer'; 9 | import { Marker } from './markers/marker'; 10 | import { RobotMarker } from './markers/robot-marker'; 11 | import { Robot } from '../../robots/shared/robot.model'; 12 | import { ControlMarker } from './markers/control-marker'; 13 | 14 | /** 15 | * ベースビューアーです。 16 | */ 17 | export interface BaseViewer { 18 | /** ルート */ 19 | root: PIXI.Container; 20 | 21 | /** Rosワールド */ 22 | rosWorld: PIXI.Container; 23 | 24 | /** Rosワールドスケール */ 25 | rosWorldScale: PIXI.Container; 26 | 27 | /** コントロールマーカー */ 28 | controlMarker: ControlMarker; 29 | 30 | /** レゾリューション[pixel/m] */ 31 | resolution: number; 32 | 33 | /** ビューウィドゥス */ 34 | viewWidth: number; 35 | 36 | /** ビューハイト */ 37 | viewHeight: number; 38 | 39 | /** マップグループ */ 40 | mapsGroup: PIXI.display.Group; 41 | 42 | /** グリッドグループ */ 43 | gridGroup: PIXI.display.Group; 44 | 45 | /** ポーズグループ */ 46 | posesGroup: PIXI.display.Group; 47 | 48 | /** ロボットグループ */ 49 | robotsGroup: PIXI.display.Group; 50 | 51 | /** コントロールグループ */ 52 | controlGroup: PIXI.display.Group; 53 | 54 | /** 55 | * ビューワーをゲットする。 56 | */ 57 | getViewer(): BaseViewer; 58 | 59 | /** 60 | * コンポーネントをゲットする。 61 | * 62 | * @return コンポーネント 63 | */ 64 | getComponent(): BaseViewerComponent; 65 | 66 | /** 67 | * オブザーバブルビューを取得する。 68 | * 69 | * @return オブザーバブルビュー 70 | */ 71 | getObservableViwer(): Observable; 72 | 73 | /** 74 | * マーカーを追加する。 75 | * 76 | * @param marker マーカー 77 | */ 78 | addMarker(marker: Marker): void; 79 | 80 | /** 81 | * マーカーを削除する。 82 | * 83 | * @param marker マーカー 84 | */ 85 | removeMarker(marker: Marker): void; 86 | 87 | /** 88 | * ロボットマーカーを取得する。 89 | * 90 | * @param robot ロボット 91 | */ 92 | getRobotMarker(robot: Robot): RobotMarker | undefined; 93 | 94 | /** 95 | * コントロールマーカーを表示する。 96 | * 97 | * @param robotMarker ロボットマーカー 98 | */ 99 | showControlMarker(robotMarker: RobotMarker): void; 100 | 101 | /** 102 | * ビューの角度を取得する。 103 | * 104 | * @return ビューの角度 105 | */ 106 | getRotationRadian(): number; 107 | 108 | /** 109 | * Ros座標系のポイントをスクリーン座標系のポイントに変換する。 110 | * 111 | * @param rosPoint Ros座標系のポイント 112 | * @return スクリーン座標系のポイント 113 | */ 114 | rosToScreen(rosPoint: PIXI.Point): PIXI.Point; 115 | } 116 | -------------------------------------------------------------------------------- /docs/deploy_en.md: -------------------------------------------------------------------------------- 1 | # AMR-IF-UI: Deployment 2 | 3 | 4 | 5 | - [1. Deployment](#1-deployment) 6 | - [1.1. Build](#11-build) 7 | - [1.2. Chaging base URL](#12-chaging-base-url) 8 | 9 | 10 | 11 | ## 1. Deployment 12 | 13 | AMR-IF-UI is constructed on Angular framework, and it can improve the 14 | loading speed in the browser by transpiling (compiling) it in advance 15 | with the AOT (Ahead-of-time) compiler. And can be deployed on a web 16 | server that does not have nodejs/npm etc. installed. 17 | 18 | (However, AMR-IF-UI itself depends on ROS/Robot Web Tools, so you need 19 | to install the required packages separately.) 20 | 21 | 22 | ### 1.1. Build 23 | 24 | Enter the ng build command in the project root directory of the AMR-IF-UI. 25 | 26 | ```shell 27 | $ ls 28 | LICENSE README.md browserslist img 29 | ngsw-config.json package-lock.json src tsconfig.json 30 | tslint.json LICENSE_HEADER angular.json karma.conf.js 31 | package.json tsconfig.app.json tsconfig.spec.json 32 | $ ng build 33 | Compiling @angular/cdk/keycodes : es2015 as esm2015 34 | Compiling @angular/animations : es2015 as esm2015 35 | : 36 | 37 | : 38 | chunk {vendor} vendor-es5.js, vendor-es5.js.map (vendor) 7.42 MB [initial] [rendered] 39 | Date: 2021-09-23T14:35:03.634Z - Hash: 36137cafdae0110f8877 - Time: 101231ms 40 | 41 | ``` 42 | 43 | A directory such as "dist/AMR-IF-UI" will be created, in which 44 | deployable packages have been generated. 45 | 46 | ```shell 47 | $ cd dist/AMR-IF-UI/ 48 | $ ls 49 | assets main-es2015.js main-es5.js manifest.webmanifest 50 | : 51 | styles-es2015.js styles-es5.js vendor-es2015.js vendor-es5.js 52 | $ 53 | ``` 54 | 55 | You can publish this project by copying the files in this directory to a 56 | publicly available directory on your HTTP server. 57 | 58 | Let's assume that "/var/www/data" is empty and is the Apache's Document Root. 59 | 60 | ```shell 61 | $ sudo cp * /var/www/data/ 62 | ``` 63 | If apache is running, you will see the following screen when you access http://localhost. 64 | 65 | - [http://localhost](http://localhost) 66 | 67 | 68 | 69 | ### 1.2. Chaging base URL 70 | 71 | In some cases, you may want to publish at a URL below a specific 72 | directory instead of publishing at the document root of the web server. 73 | 74 | In that case, when building, use the "--base-href=option" to specify the 75 | path of the hierarchy to expose. 76 | 77 | 78 | ```shell 79 | $ cd 80 | $ ng build --base-href=/AMR-IF-UI/ 81 | $ cd dist 82 | $ sudo cp -r AMR-IF-UI /var/www/data/ 83 | ``` 84 | 85 | This will allow you to access the AMR-IF-UI at http://localhost/AMR-IF-UI/. 86 | 87 | - [http://localhost/AMR-IF-UI/](http://localhost/AMR-IF-UI/) 88 | 89 | -------------------------------------------------------------------------------- /docs/install_en.md: -------------------------------------------------------------------------------- 1 | # AMR-IF-UI: Installation 2 | 3 | 4 | - [1. Installation](#1-installation) 5 | - [1.1. Downloading source code](#11-downloading-source-code) 6 | - [1.2. Installation of dependent packages](#12-installation-of-dependent-packages) 7 | - [1.3. Access test](#13-access-test) 8 | 9 | 10 | 11 | ## 1. Installation 12 | 13 | ### 1.1. Downloading source code 14 | 15 | Please download this AMR-IF from the github repository and extract it to an appropriate directory. 16 | 17 | ```shell 18 | $ git clone https://github.com/robo-marc/AMR-IF-UI 19 | $ cd AMR-IF-UI 20 | $ ls 21 | LICENSE doc package-lock.json tsconfig.spec.json 22 | LICENSE_HEADER img package.json tslint.json 23 | README.md karma.conf.js src 24 | angular.json ngsw-config.json tsconfig.app.json 25 | browserslist node_modules tsconfig.json 26 | ``` 27 | 28 | ### 1.2. Installation of dependent packages 29 | 30 | Now install the dependent packages with the npm ci command. 31 | 32 | ```shell 33 | $ sudo npm ci 34 | : 35 | > @angular/cli@9.1.0 postinstall /home/n-ando/work/AMR-IF-UI/node_modules/@angular/cli 36 | > node ./bin/postinstall/script.js 37 | 38 | added 1476 packages in 31.457s 39 | $ sudo npm install -g @angular/cli 40 | ``` 41 | 42 | ```shell 43 | $ ng serve 44 | : 45 | It will take some time. 46 | : 47 | Date: 2021-09-23T08:04:01.730Z - Hash: 98b0164d374b20b98c5c 48 | 5 unchanged chunks 49 | 50 | Time: 542ms 51 | : Compiled successfully. 52 | ``` 53 | 54 | At this time, as shown below, a confirmation message will be displayed 55 | asking if you want to share usage data with Google's Angular team, so if 56 | there is no problem, enter "y" and proceed. (If you don't like that, you 57 | can use "n".) 58 | 59 | ```shell 60 | ? Would you like to share anonymous usage data with the Angular Team at 61 | Google under Google’s Privacy Policy at https://policies.google.com/privacy? 62 | For more details and how to change this setting, see http://angular.io/analytics. 63 | ``` 64 | 65 | After executing the "ng serve" command, the command prompt does not come 66 | back, because the HTTP service is running behind the process. Proceed to 67 | the next access test to test if you can access the web service. 68 | 69 | ### 1.3. Access test 70 | 71 | Try accessing http://localhost:4200 from your browser. 72 | 73 | 74 | - [http://localhost:4200](http://localhost:4200) 75 | 76 | The following page would be displayed. 77 | 78 | 79 | 80 | The port-number can be changed with "--port" option. 81 | 82 | ```shell 83 | $ ng server --port 8080 84 | ``` 85 | 86 | If you use under 1023 port, since root privilege is required, use sudo before the command. 87 | -------------------------------------------------------------------------------- /src/app/viewer/shared/markers/base-marker.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import 'pixi.js'; 7 | import { Subscription, Subject, Observable } from 'rxjs'; 8 | import { BaseViewer } from '../base-viewer'; 9 | import { Marker } from './marker'; 10 | 11 | /** 12 | * ベースマーカーです。 13 | */ 14 | export class BaseMarker implements Marker { 15 | /** ビューワー */ 16 | viewer: BaseViewer; 17 | 18 | /** ルートコンテナ */ 19 | root: PIXI.Container; 20 | 21 | /** サブスクリプション */ 22 | subscription: Subscription; 23 | 24 | /** ビジブル */ 25 | visible = true; 26 | 27 | /** ビジブルサブジェクト */ 28 | visibleSubject: Subject = new Subject(); 29 | 30 | /** ビジブルオブザーバブル */ 31 | visibleObservable: Observable = this.visibleSubject.asObservable(); 32 | 33 | /** 34 | * オブジェクトを構築する。 35 | * 36 | * ビューワーに自身のマーカーを追加する。 37 | * ルートコンテナをビューワーのルートコンテナに追加する。 38 | * ビューワーの変化をサブスクリプトして変化した場合にupdateをコールする。 39 | * 40 | * コンテナの親子関係 41 | * Viewer.root -> BaseMarker.root 42 | * 43 | * @param viewer ビューワー 44 | */ 45 | constructor(viewer: BaseViewer) { 46 | this.viewer = viewer; 47 | this.viewer.addMarker(this); 48 | this.root = new PIXI.Container(); 49 | this.viewer.root.addChild(this.root); 50 | this.subscription = this.viewer.getObservableViwer().subscribe(() => { 51 | this.update(); 52 | }); 53 | } 54 | 55 | /** 56 | * ビューが変化した際にアップデートする。 57 | */ 58 | update(): void {} 59 | 60 | /** 61 | * 表示する。 62 | * 63 | * ルートコンテナを表示する。 64 | */ 65 | show(): void { 66 | this.visible = true; 67 | this.root.visible = true; 68 | this.visibleSubject.next(this.visible); 69 | } 70 | 71 | /** 72 | * 非表示にする。 73 | * 74 | * ルートコンテナを非表示にする。 75 | */ 76 | hide(): void { 77 | this.visible = false; 78 | this.root.visible = false; 79 | this.visibleSubject.next(this.visible); 80 | } 81 | 82 | /** 83 | * 表示か非表示かを返却する。 84 | * 85 | * @return true(表示)/false(非表示) 86 | */ 87 | isVisibled(): boolean { 88 | return this.visible; 89 | } 90 | 91 | /** 92 | * 表示か非表示かのオブザーバーを取得する。 93 | * 94 | * @return オブザーバー 95 | */ 96 | getVisibleObservable(): Observable { 97 | return this.visibleObservable; 98 | } 99 | 100 | /** 101 | * 破棄する。 102 | * 103 | * サブスクリプションをアンサブスクライブする。 104 | */ 105 | dispose(): void { 106 | if (this.subscription) { 107 | this.subscription.unsubscribe(); 108 | } 109 | } 110 | 111 | /** 112 | * 削除する。 113 | * 114 | * ビューワーから削除する。 115 | * ルートコンテナからルートコンテナを削除する。 116 | */ 117 | remove(): void { 118 | this.dispose(); 119 | this.viewer.removeMarker(this); 120 | this.viewer.root.removeChild(this.root); 121 | } 122 | 123 | /** 124 | * ビューワーをゲットする。 125 | */ 126 | getViewer(): BaseViewer { 127 | return this.viewer; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/app/robots/robot-form/robot-form.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 |
9 | 10 | 16 | MyRobot 17 | 18 | 19 | {{ option }} 20 | 21 | 22 | 23 | 24 | 30 | #000000FF 31 | 32 | 37 | {{ option.value }} [{{ option.name }})] 38 | 39 | 40 | 41 |
42 |
43 | 44 | 50 | trr1 51 | 52 | 53 | {{ option }} 54 | 55 | 56 | 57 | 58 | 64 | localhost 65 | 66 | 67 | {{ option }} 68 | 69 | 70 | 71 | 72 | 79 | 80 | 81 | {{ option }} 82 | 83 | 84 | 9090 85 | 86 |
87 |
88 |
89 | -------------------------------------------------------------------------------- /src/app/poses/shared/pose.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import * as ROSLIB from 'roslib'; 7 | import { Injectable } from '@angular/core'; 8 | import { Pose } from './pose.model'; 9 | import { BaseStorage } from '../../shared/storage/base-storage'; 10 | import { StoreService } from '../../shared/store/store.service'; 11 | 12 | /** 13 | * ポーズサービスです。 14 | */ 15 | @Injectable({ 16 | providedIn: 'root' 17 | }) 18 | export class PoseService extends BaseStorage { 19 | /** ストレージキー */ 20 | static readonly STORAGE_KEY = 'amr-if-ui' + '-poses'; 21 | 22 | /** 23 | * オブジェクトを構築する。 24 | * 25 | * @param storeService ストアサービス 26 | */ 27 | constructor( 28 | public storeService: StoreService 29 | ) { 30 | super(PoseService.STORAGE_KEY, storeService.getKeyValueStore()); 31 | } 32 | 33 | /** 34 | * 空のモデルを取得する。 35 | * 36 | * @return 空のモデル 37 | */ 38 | getEmptyModel(): Pose { 39 | return { 40 | name: '', 41 | rospose: { 42 | position: { x: 0, y: 0, z: 0 }, 43 | orientation: { x: 0, y: 0, z: 0, w: 1 } 44 | } as ROSLIB.Pose, 45 | color: '' 46 | } as Pose; 47 | } 48 | 49 | /** 50 | * バリデートする。 51 | * 52 | * @param edit エディットされたモデル 53 | * @return バリデートリザルト 54 | */ 55 | validate(edit: Pose): boolean { 56 | // check empty fields 57 | if (!edit.name) { 58 | this.showErrorMessage('Failed validation. The name field is required.'); 59 | return false; 60 | } 61 | if ( 62 | edit.rospose.position.x == null || 63 | edit.rospose.position.y == null || 64 | edit.rospose.position.z == null 65 | ) { 66 | this.showErrorMessage('Failed validation. The position field is required.'); 67 | return false; 68 | } 69 | if ( 70 | edit.rospose.orientation.x == null || 71 | edit.rospose.orientation.y == null || 72 | edit.rospose.orientation.z == null || 73 | edit.rospose.orientation.w == null 74 | ) { 75 | this.showErrorMessage('Failed validation. The orientation field is required.'); 76 | return false; 77 | } 78 | if (!edit.color) { 79 | this.showErrorMessage('Failed validation. The color field is required.'); 80 | return false; 81 | } 82 | const regexColor = new RegExp(/^[#]{1}[0-9a-fA-F]{8}$/); 83 | if (!regexColor.test(edit.color)) { 84 | this.showErrorMessage('Failed validation. The color value is invalid.'); 85 | return false; 86 | } 87 | const unique = this.getByName(edit.name); 88 | if (unique) { 89 | if (unique.id !== edit.id) { 90 | this.showErrorMessage('Failed validation. The same name already exists.'); 91 | return false; 92 | } 93 | } 94 | return true; 95 | } 96 | 97 | /** 98 | * ネームが一致するデータを取得する。 99 | * 100 | * @param name ネーム 101 | * @return ネームが一致するデータ 102 | */ 103 | getByName(name: string): Pose | undefined { 104 | return this.getAll().find(element => element.name === name); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | /** 7 | * This file includes polyfills needed by Angular and is loaded before the app. 8 | * You can add your own extra polyfills to this file. 9 | * 10 | * This file is divided into 2 sections: 11 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 12 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 13 | * file. 14 | * 15 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 16 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 17 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 18 | * 19 | * Learn more in https://angular.io/guide/browser-support 20 | */ 21 | 22 | /*************************************************************************************************** 23 | * BROWSER POLYFILLS 24 | */ 25 | 26 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 27 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 28 | 29 | /** 30 | * Web Animations `@angular/platform-browser/animations` 31 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 32 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 33 | */ 34 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 35 | 36 | /** 37 | * By default, zone.js will patch all possible macroTask and DomEvents 38 | * user can disable parts of macroTask/DomEvents patch by setting following flags 39 | * because those flags need to be set before `zone.js` being loaded, and webpack 40 | * will put import in the top of bundle, so user need to create a separate file 41 | * in this directory (for example: zone-flags.ts), and put the following flags 42 | * into that file, and then add the following code before importing zone.js. 43 | * import './zone-flags.ts'; 44 | * 45 | * The flags allowed in zone-flags.ts are listed here. 46 | * 47 | * The following flags will work for all browsers. 48 | * 49 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 50 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 51 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 52 | * 53 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 54 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 55 | * 56 | * (window as any).__Zone_enable_cross_context_check = true; 57 | * 58 | */ 59 | 60 | /*************************************************************************************************** 61 | * Zone JS is required by default for Angular itself. 62 | */ 63 | import 'zone.js/dist/zone'; // Included with Angular CLI. 64 | 65 | 66 | /*************************************************************************************************** 67 | * APPLICATION IMPORTS 68 | */ 69 | import * as PIXI from 'pixi.js'; 70 | (window as any).PIXI = PIXI; 71 | -------------------------------------------------------------------------------- /src/app/robots/shared/robot.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Injectable } from '@angular/core'; 7 | import { Robot } from './robot.model'; 8 | import { BaseStorage } from '../../shared/storage/base-storage'; 9 | import { StoreService } from '../../shared/store/store.service'; 10 | 11 | /** 12 | * ロボットサービスです。 13 | */ 14 | @Injectable({ 15 | providedIn: 'root' 16 | }) 17 | export class RobotService extends BaseStorage { 18 | /** ストレージキー */ 19 | static readonly STORAGE_KEY = 'amr-if-ui' + '-robots'; 20 | 21 | /** 22 | * オブジェクトを構築する。 23 | * 24 | * @param storeService ストアサービス 25 | */ 26 | constructor( 27 | public storeService: StoreService 28 | ) { 29 | super(RobotService.STORAGE_KEY, storeService.getKeyValueStore()); 30 | } 31 | 32 | /** 33 | * 空のモデルを取得する。 34 | * 35 | * @return 空のモデル 36 | */ 37 | getEmptyModel(): Robot { 38 | return { 39 | name: '', 40 | namespace: '', 41 | color: '', 42 | address: '', 43 | port: 9090 44 | } as Robot; 45 | } 46 | 47 | /** 48 | * バリデートする。 49 | * 50 | * @param edit エディットされたモデル 51 | * @return バリデートリザルト 52 | */ 53 | validate(edit: Robot): boolean { 54 | // check empty fields 55 | if (!edit.name) { 56 | this.showErrorMessage('Failed validation. The name field is required.'); 57 | return false; 58 | } 59 | if (!edit.namespace) { 60 | this.showErrorMessage('Failed validation. The namespace field is required.'); 61 | return false; 62 | } 63 | if (!edit.color) { 64 | this.showErrorMessage('Failed validation. The color field is required.'); 65 | return false; 66 | } 67 | if (!edit.address) { 68 | this.showErrorMessage('Failed validation. The address field is required.'); 69 | return false; 70 | } 71 | if (edit.port == null) { 72 | this.showErrorMessage('Failed validation. The port field is required.'); 73 | return false; 74 | } 75 | if (edit.port < 0 || 65535 < edit.port) { 76 | this.showErrorMessage('Failed validation. The port is out of range.'); 77 | return false; 78 | } 79 | const regexColor = new RegExp(/^[#]{1}[0-9a-fA-F]{8}$/); 80 | if (!regexColor.test(edit.color)) { 81 | this.showErrorMessage('Failed validation. The color value is invalid.'); 82 | return false; 83 | } 84 | const unique = this.getByName(edit.name); 85 | if (unique) { 86 | if (unique.id !== edit.id) { 87 | this.showErrorMessage('Failed validation. The same name already exists.'); 88 | return false; 89 | } 90 | } 91 | 92 | return true; 93 | } 94 | 95 | /** 96 | * ネームが一致するデータを取得する。 97 | * 98 | * @param name ネーム 99 | * @return ネームが一致するデータ 100 | */ 101 | getByName(name: string): Robot | undefined { 102 | return this.getAll().find(element => element.name === name); 103 | } 104 | 105 | /** 106 | * ロボットを取得する。 107 | * 108 | * @return ロボットオプション 109 | */ 110 | getRobots(): string[] { 111 | const robots: string[] = []; 112 | this.getAll().forEach(robot => robots.push(robot.name)); 113 | return robots; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/app/cmd-lists/shared/cmd-list.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Injectable } from '@angular/core'; 7 | import { CmdList } from './cmd-list.model'; 8 | import { Command } from '../../commands/shared/command.model'; 9 | import { BaseStorage } from '../../shared/storage/base-storage'; 10 | import { CommandService } from '../../commands/shared/command.service'; 11 | import { StoreService } from '../../shared/store/store.service'; 12 | 13 | /** 14 | * コマンドリストサービスです。 15 | */ 16 | @Injectable({ 17 | providedIn: 'root' 18 | }) 19 | export class CmdListService extends BaseStorage { 20 | /** ストレージキー */ 21 | static readonly STORAGE_KEY = 'amr-if-ui' + '-cmd-lists'; 22 | 23 | /** 24 | * オブジェクトを構築する。 25 | * 26 | * @param storeService ストアサービス 27 | * @param commandService コマンドサービス 28 | */ 29 | constructor( 30 | public storeService: StoreService, 31 | private commandService: CommandService 32 | ) { 33 | super(CmdListService.STORAGE_KEY, storeService.getKeyValueStore()); 34 | } 35 | 36 | /** 37 | * 空のモデルを取得する。 38 | * 39 | * @return 空のモデル 40 | */ 41 | getEmptyModel(): CmdList { 42 | return { 43 | name: '', 44 | mode: 'custom', 45 | commands: [this.commandService.getEmptyModel()] 46 | } as CmdList; 47 | } 48 | 49 | /** 50 | * バリデートする。 51 | * 52 | * @param edit エディットされたモデル 53 | * @return バリデートリザルト 54 | */ 55 | validate(edit: CmdList): boolean { 56 | // check empty fields 57 | if (!edit.name) { 58 | this.showErrorMessage('Failed validation. The name field is required.'); 59 | return false; 60 | } 61 | for (const element of edit.commands) { 62 | if (!this.commandService.validate(element)) { 63 | return false; 64 | } 65 | } 66 | const unique = this.getByName(edit.name); 67 | if (unique) { 68 | if (unique.id !== edit.id) { 69 | this.showErrorMessage('Failed validation. The same name already exists.'); 70 | return false; 71 | } 72 | } 73 | return true; 74 | } 75 | 76 | /** 77 | * ネームが一致するデータを取得する。 78 | * 79 | * @param name ネーム 80 | * @return ネームが一致するデータ 81 | */ 82 | getByName(name: string): CmdList | undefined { 83 | return this.getAll().find(element => element.name === name); 84 | } 85 | 86 | /** 87 | * コマンドリストを取得する。 88 | * 89 | * @return コマンドリストオプション 90 | */ 91 | getCmdLists(): string[] { 92 | const cmdlists: string[] = []; 93 | this.getAll().forEach(cmdlist => cmdlists.push(cmdlist.name)); 94 | return cmdlists; 95 | } 96 | 97 | /** 98 | * JOSNからデータをリストアする。 99 | * オーバーライドして互換性のないデータを修復してから読み込むようにする。 100 | * 101 | * @param json JSON文字列 102 | */ 103 | restoreFromJSON(json: string): void { 104 | const object = JSON.parse(json); 105 | 106 | // convert 107 | object.array.forEach((element: CmdList) => { 108 | element.mode = 'custom'; 109 | element.commands 110 | .filter(command => command.command.cmd === 'goto') 111 | .forEach((command: Command) => { 112 | if (command.command.params.length === 2) { 113 | command.command.params.push(''); 114 | command.command.params.push(''); 115 | } 116 | }); 117 | }); 118 | 119 | this.restoreFromObject(object); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/app/poses/pose-form/pose-form.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component, OnInit, Input } from '@angular/core'; 7 | import { Pose } from '../shared/pose.model'; 8 | import { MaterialColor } from '../../shared/material-color'; 9 | import { MaterialColorOptions } from '../../shared/material-color-options'; 10 | 11 | /** 12 | * ポーズのフォームを表示するコンポーネントです。 13 | */ 14 | @Component({ 15 | selector: 'app-pose-form', 16 | templateUrl: './pose-form.component.html', 17 | styleUrls: ['./pose-form.component.css'] 18 | }) 19 | export class PoseFormComponent implements OnInit { 20 | /** ポーズ */ 21 | @Input() 22 | edit: Pose; 23 | 24 | /** ヨー */ 25 | yaw = 0; 26 | 27 | /** 候補として表示するネームのオプション */ 28 | nameOptions: string[] = []; 29 | 30 | /** 候補として表示するカラーのオプション */ 31 | colorOptions: MaterialColor[] = MaterialColorOptions.colorOptions; 32 | 33 | /** 34 | * オブジェクトを構築する 35 | */ 36 | constructor() {} 37 | 38 | /** 39 | * コンポーネントを初期化する。 40 | */ 41 | ngOnInit(): void { 42 | this.yaw = this.toDegree(this.toEulerAngle(this.edit.rospose.orientation).yaw); 43 | } 44 | 45 | /** 46 | * yawの値が変化した際にコールバックされる。 47 | */ 48 | onChangeYaw(): void { 49 | const eulerAngle = { 50 | roll: 0, 51 | pitch: 0, 52 | yaw: this.toEuler(this.yaw) 53 | }; 54 | this.edit.rospose.orientation = this.toQuaternion(eulerAngle); 55 | } 56 | 57 | /** 58 | * オイラーアングルをクォータニオンに変換する。 59 | * 60 | * @param eularAngle オイラーアングル 61 | * @return クォータニオン 62 | */ 63 | toQuaternion(eularAngle: { 64 | roll: number; 65 | pitch: number; 66 | yaw: number; 67 | }): ROSLIB.Quaternion { 68 | const cy = Math.cos(eularAngle.yaw * 0.5); 69 | const sy = Math.sin(eularAngle.yaw * 0.5); 70 | const cr = Math.cos(eularAngle.roll * 0.5); 71 | const sr = Math.sin(eularAngle.roll * 0.5); 72 | const cp = Math.cos(eularAngle.pitch * 0.5); 73 | const sp = Math.sin(eularAngle.pitch * 0.5); 74 | 75 | return { 76 | w: cy * cr * cp + sy * sr * sp, 77 | x: cy * sr * cp - sy * cr * sp, 78 | y: cy * cr * sp + sy * sr * cp, 79 | z: sy * cr * cp - cy * sr * sp 80 | } as ROSLIB.Quaternion; 81 | } 82 | 83 | /** 84 | * クォータニオンをオイラーアングルに変換する。 85 | * 86 | * @param q クォータニオン 87 | * @return オイラーアングル 88 | */ 89 | toEulerAngle( 90 | q: ROSLIB.Quaternion 91 | ): { 92 | roll: number; 93 | pitch: number; 94 | yaw: number; 95 | } { 96 | // roll (x-axis rotation) 97 | const sinr = +2.0 * (q.w * q.x + q.y * q.z); 98 | const cosr = +1.0 - 2.0 * (q.x * q.x + q.y * q.y); 99 | const roll = Math.atan2(sinr, cosr); 100 | let pitch; 101 | 102 | // pitch (y-axis rotation) 103 | const sinp = +2.0 * (q.w * q.y - q.z * q.x); 104 | if (Math.abs(sinp) >= 1) { 105 | pitch = 0 < sinp ? Math.PI / 2 : -Math.PI / 2; 106 | } else { 107 | pitch = Math.asin(sinp); 108 | } 109 | 110 | // yaw (z-axis rotation) 111 | const siny = +2.0 * (q.w * q.z + q.x * q.y); 112 | const cosy = +1.0 - 2.0 * (q.y * q.y + q.z * q.z); 113 | const yaw = Math.atan2(siny, cosy); 114 | 115 | return { 116 | roll, 117 | pitch, 118 | yaw 119 | }; 120 | } 121 | 122 | /** 123 | * ラジアンをディグリーに変換する。 124 | * 125 | * @param eular ラジアン 126 | * @return ディグリー 127 | */ 128 | toDegree(eular: number): number { 129 | return (eular * 180) / Math.PI; 130 | } 131 | 132 | /** 133 | * ディグリーをラジアンに変換する。 134 | * 135 | * @param degree ディグリー 136 | * @return ラジアン 137 | */ 138 | toEuler(degree: number): number { 139 | return (degree * Math.PI) / 180; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "AMR-IF-UI": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/AMR-IF-UI", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "tsconfig.app.json", 21 | "assets": [ 22 | "src/assets", 23 | "src/manifest.webmanifest" 24 | ], 25 | "styles": [ 26 | "./node_modules/@angular/material/prebuilt-themes/purple-green.css", 27 | "src/styles.css" 28 | ], 29 | "scripts": [] 30 | }, 31 | "configurations": { 32 | "production": { 33 | "fileReplacements": [ 34 | { 35 | "replace": "src/environments/environment.ts", 36 | "with": "src/environments/environment.prod.ts" 37 | } 38 | ], 39 | "optimization": true, 40 | "outputHashing": "all", 41 | "sourceMap": false, 42 | "extractCss": true, 43 | "namedChunks": false, 44 | "aot": true, 45 | "extractLicenses": true, 46 | "vendorChunk": false, 47 | "buildOptimizer": true, 48 | "budgets": [ 49 | { 50 | "type": "initial", 51 | "maximumWarning": "2mb", 52 | "maximumError": "5mb" 53 | } 54 | ], 55 | "serviceWorker": true, 56 | "ngswConfigPath": "ngsw-config.json" 57 | } 58 | } 59 | }, 60 | "serve": { 61 | "builder": "@angular-devkit/build-angular:dev-server", 62 | "options": { 63 | "browserTarget": "AMR-IF-UI:build" 64 | }, 65 | "configurations": { 66 | "production": { 67 | "browserTarget": "AMR-IF-UI:build:production" 68 | } 69 | } 70 | }, 71 | "extract-i18n": { 72 | "builder": "@angular-devkit/build-angular:extract-i18n", 73 | "options": { 74 | "browserTarget": "AMR-IF-UI:build" 75 | } 76 | }, 77 | "test": { 78 | "builder": "@angular-devkit/build-angular:karma", 79 | "options": { 80 | "main": "src/test.ts", 81 | "polyfills": "src/polyfills.ts", 82 | "tsConfig": "tsconfig.spec.json", 83 | "karmaConfig": "karma.conf.js", 84 | "assets": [ 85 | "src/assets", 86 | "src/manifest.webmanifest" 87 | ], 88 | "styles": [ 89 | "./node_modules/@angular/material/prebuilt-themes/purple-green.css", 90 | "src/styles.css" 91 | ], 92 | "scripts": [] 93 | } 94 | }, 95 | "lint": { 96 | "builder": "@angular-devkit/build-angular:tslint", 97 | "options": { 98 | "tsConfig": [ 99 | "tsconfig.app.json", 100 | "tsconfig.spec.json" 101 | ], 102 | "exclude": [ 103 | "**/node_modules/**" 104 | ] 105 | } 106 | } 107 | } 108 | } 109 | }, 110 | "defaultProject": "AMR-IF-UI" 111 | } 112 | -------------------------------------------------------------------------------- /src/app/viewer/shared/abstract-viewer-component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Renderer2, NgZone, ElementRef } from '@angular/core'; 7 | import { MatDialog } from '@angular/material/dialog'; 8 | import { BaseViewerComponent } from './base-viewer-component'; 9 | import { SettingService } from '../../settings/shared/setting.service'; 10 | import { RosService } from '../../shared/ros/ros.service'; 11 | import { PoseService } from '../../poses/shared/pose.service'; 12 | import { RobotService } from '../../robots/shared/robot.service'; 13 | import { CommandService } from '../../commands/shared/command.service'; 14 | import { CmdListService } from '../../cmd-lists/shared/cmd-list.service'; 15 | 16 | /** 17 | * アブストラクトビューワーコンポーネントです。 18 | */ 19 | export abstract class AbstractViewerComponent implements BaseViewerComponent { 20 | /** canvasを動的に追加するプレースホルダーとなる要素 */ 21 | abstract canvasesPlaceholderElementRef: ElementRef; 22 | 23 | /** ビューを追加する要素 */ 24 | abstract viewerElementRef: ElementRef; 25 | 26 | /** セッティングサービス */ 27 | abstract settingService: SettingService; 28 | 29 | /** ROSサービス */ 30 | abstract rosService: RosService; 31 | 32 | /** ポーズサービス */ 33 | abstract poseService: PoseService; 34 | 35 | /** ロボットサービス */ 36 | abstract robotService: RobotService; 37 | 38 | /** コマンドサービス */ 39 | abstract commandService: CommandService; 40 | 41 | /** コマンドリストサービス */ 42 | abstract cmdListService: CmdListService; 43 | 44 | /** インプットダイアログ */ 45 | abstract inputDialog: MatDialog; 46 | 47 | /** セレクトダイアログ */ 48 | abstract selectDialog: MatDialog; 49 | 50 | /** レンダラー2 */ 51 | abstract renderer2: Renderer2; 52 | 53 | /** ゾーン */ 54 | abstract ngZone: NgZone; 55 | 56 | /** 57 | * キャンバスプレースホルダーエレメントリファレンスをゲットする。 58 | * 59 | * @return キャンバスプレースホルダーエレメントリファレンス 60 | */ 61 | getCanvasesPlaceholderElementRef(): ElementRef { 62 | return this.canvasesPlaceholderElementRef; 63 | } 64 | 65 | /** 66 | * ビューワーエレメントリファレンスをゲットする。 67 | * 68 | * @return ビューワーエレメントリファレンス 69 | */ 70 | getViewerElementRef(): ElementRef { 71 | return this.viewerElementRef; 72 | } 73 | 74 | /** 75 | * セッティングサービスをゲットする。 76 | * 77 | * @return セッティングサービス 78 | */ 79 | getSettingService(): SettingService { 80 | return this.settingService; 81 | } 82 | 83 | /** 84 | * ROSサービスをゲットする。 85 | * 86 | * @return ROSサービス 87 | */ 88 | getRosService(): RosService { 89 | return this.rosService; 90 | } 91 | 92 | /** 93 | * ポーズサービスをゲットする。 94 | * 95 | * @return ポーズサービス 96 | */ 97 | getPoseService(): PoseService { 98 | return this.poseService; 99 | } 100 | 101 | /** 102 | * ロボットサービスをゲットする。 103 | * 104 | * @return ロボットサービス 105 | */ 106 | getRobotService(): RobotService { 107 | return this.robotService; 108 | } 109 | 110 | /** 111 | * コマンドサービスをゲットする。 112 | * 113 | * @return コマンドサービス 114 | */ 115 | getCommandService(): CommandService { 116 | return this.commandService; 117 | } 118 | 119 | /** 120 | * コマンドリストサービスをゲットする。 121 | * 122 | * @return コマンドリストサービス 123 | */ 124 | getCmdListService(): CmdListService { 125 | return this.cmdListService; 126 | } 127 | 128 | /** 129 | * インプットダイアログをゲットする。 130 | * 131 | * @return ダイアログ 132 | */ 133 | getInputDialog(): MatDialog { 134 | return this.inputDialog; 135 | } 136 | 137 | /** 138 | * セレクトダイアログをゲットする。 139 | * 140 | * @return ダイアログ 141 | */ 142 | getSelectDialog(): MatDialog { 143 | return this.selectDialog; 144 | } 145 | 146 | /** 147 | * レンダラーをゲットする。 148 | * 149 | * @return レンダラー 150 | */ 151 | getRenderer2(): Renderer2 { 152 | return this.renderer2; 153 | } 154 | 155 | /** 156 | * ゾーンをゲットする。 157 | * 158 | * @return ゾーン 159 | */ 160 | getNgZone(): NgZone { 161 | return this.ngZone; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/app/settings/shared/setting.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Injectable } from '@angular/core'; 7 | import { BaseStorage } from '../../shared/storage/base-storage'; 8 | import { Setting } from './setting.model'; 9 | import { StoreService } from '../../shared/store/store.service'; 10 | 11 | /** 12 | * セッティングサービスです。 13 | */ 14 | @Injectable({ 15 | providedIn: 'root' 16 | }) 17 | export class SettingService extends BaseStorage { 18 | /** ストレージキー */ 19 | static readonly STORAGE_KEY = 'amr-if-ui' + '-settings'; 20 | 21 | /** 22 | * オブジェクトを構築する。 23 | * 24 | * @param storeService ストアサービス 25 | */ 26 | constructor( 27 | public storeService: StoreService 28 | ) { 29 | super(SettingService.STORAGE_KEY, storeService.getKeyValueStore()); 30 | } 31 | 32 | /** 33 | * 空のモデルを取得する。 34 | * 35 | * @return 空のモデル 36 | */ 37 | getEmptyModel(): Setting { 38 | return { 39 | name: '', 40 | value: '' 41 | } as Setting; 42 | } 43 | 44 | /** 45 | * バリデートする。 46 | * 47 | * @param edit エディットされたモデル 48 | * @return バリデートリザルト 49 | */ 50 | validate(edit: Setting): boolean { 51 | // check empty fields 52 | if (!edit.name) { 53 | this.showErrorMessage('Failed validation. The name field is required.'); 54 | return false; 55 | } 56 | if (!edit.value) { 57 | this.showErrorMessage('Failed validation. The value field is required.'); 58 | return false; 59 | } 60 | return true; 61 | } 62 | 63 | /** 64 | * 指定したネームが存在する場合はその値を返却し、存在しない場合は指定したデフォルト値を返却する。 65 | * 66 | * @param name ネーム 67 | * @param defaultValue デフォルト値 68 | */ 69 | getValue(name: string, defaultValue: string): string { 70 | let value = defaultValue; 71 | this.data.array 72 | .filter(element => element.name === name) 73 | .forEach(element => { 74 | value = element.value; 75 | }); 76 | return value; 77 | } 78 | 79 | /** 80 | * セッティングが存在しない場合は追加する。 81 | * 82 | * @param setting セッティング 83 | */ 84 | async addIfNotExist(setting: Setting): Promise { 85 | const filtered = this.data.array.filter( 86 | element => element.name === setting.name 87 | ); 88 | if (filtered.length === 0) { 89 | await this.add(setting); 90 | } 91 | } 92 | 93 | /** 94 | * セッティングを追加する。 95 | */ 96 | async addSettings(): Promise { 97 | await this.addIfNotExist({ 98 | name: 'resolution', 99 | value: '100' 100 | } as Setting); 101 | 102 | await this.addIfNotExist({ 103 | name: 'fontSize', 104 | value: '32' 105 | } as Setting); 106 | 107 | await this.addIfNotExist({ 108 | name: 'poseMarkerSize', 109 | value: '48' 110 | } as Setting); 111 | 112 | await this.addIfNotExist({ 113 | name: 'rotation', 114 | value: '1' 115 | } as Setting); 116 | } 117 | 118 | /** 119 | * getValueをコールして戻り値を浮動小数点の数値としてパースして返却する。 120 | * 121 | * @param name ネーム 122 | * @param defaultValue デフォルト値 123 | */ 124 | getNumber(name: string, defaultValue: string): number { 125 | const value = this.getValue(name, defaultValue); 126 | return parseFloat(value); 127 | } 128 | 129 | /** 130 | * レゾリューションを取得する。 131 | * 132 | * @return レゾリューション(デフォルトは100) 133 | */ 134 | getResolution(): number { 135 | const value = this.getNumber('resolution', '100'); 136 | return value; 137 | } 138 | 139 | /** 140 | * フォントサイズを取得する。 141 | * 142 | * @return フォントサイズ(デフォルトは32) 143 | */ 144 | getFontSize(): number { 145 | const value = this.getNumber('fontSize', '32'); 146 | return value; 147 | } 148 | 149 | /** 150 | * ポーズマーカーサイズを取得する。 151 | * 152 | * @return ポーズマーカーサイズ(デフォルトは48) 153 | */ 154 | getPoseMarkerSize(): number { 155 | const value = this.getNumber('poseMarkerSize', '48'); 156 | return value; 157 | } 158 | 159 | /** 160 | * ローテーション取得する。 161 | * 162 | * @return ローテーション(デフォルトは1) 163 | */ 164 | getRotation(): number { 165 | let value = this.getNumber('rotation', '1'); 166 | if (value < 0 || value > 3) { 167 | value = 0; 168 | } 169 | return value; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/app/shared/config/config.service.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Injectable } from '@angular/core'; 7 | import { HttpClient } from '@angular/common/http'; 8 | import { RobotService } from '../../robots/shared/robot.service'; 9 | import { PoseService } from '../../poses/shared/pose.service'; 10 | import { SettingService } from '../../settings/shared/setting.service'; 11 | import { CmdListService } from '../../cmd-lists/shared/cmd-list.service'; 12 | import { CommandService } from '../../commands/shared/command.service'; 13 | import { StorageService } from '../storage/storage-service'; 14 | import { StoreService } from '../store/store.service'; 15 | 16 | /** 17 | * コンフィグサービスです。 18 | */ 19 | @Injectable({ 20 | providedIn: 'root' 21 | }) 22 | export class ConfigService { 23 | /** 24 | * オブジェクトを構築する。 25 | * 26 | * @param storeService ストアサービス 27 | * @param settingService セッティングサービス 28 | * @param robotService ロボットサービス 29 | * @param poseService ポーズサービス 30 | * @param commandService コマンドサービス 31 | * @param cmdListService コマンドリストサービス 32 | * @param http HTTPクライアント 33 | */ 34 | constructor( 35 | private storeService: StoreService, 36 | private settingService: SettingService, 37 | private robotService: RobotService, 38 | private poseService: PoseService, 39 | private commandService: CommandService, 40 | private cmdListService: CmdListService, 41 | private http: HttpClient 42 | ) {} 43 | 44 | /** 45 | * ロードする。 46 | * 47 | * @return プロミス 48 | */ 49 | async load(): Promise { 50 | // initialize 51 | await this.settingService.initialize(); 52 | await this.commandService.initialize(); 53 | const foundNull = await this.initialize(); 54 | 55 | // demo data for first 56 | if (foundNull) { 57 | await this.setResetData(); 58 | } 59 | 60 | await this.settingService.addSettings(); 61 | } 62 | 63 | /** 64 | * イニシャライズする。 65 | * 66 | * @return プロミス 67 | */ 68 | async initialize(): Promise { 69 | const storageServices: StorageService[] = []; 70 | 71 | // push 72 | storageServices.push(this.robotService); 73 | storageServices.push(this.poseService); 74 | storageServices.push(this.cmdListService); 75 | 76 | let foundNull = false; 77 | for (const element of storageServices) { 78 | const result = await element.initialize(); 79 | if (!result) { 80 | foundNull = true; 81 | } 82 | } 83 | return foundNull; 84 | } 85 | 86 | /** 87 | * 全てリムーブする。 88 | * 89 | * @return プロミス 90 | */ 91 | async removeAll(): Promise { 92 | const keyValueStore = this.storeService.getKeyValueStore(); 93 | await keyValueStore.remove(RobotService.STORAGE_KEY); 94 | await keyValueStore.remove(PoseService.STORAGE_KEY); 95 | await keyValueStore.remove(CmdListService.STORAGE_KEY); 96 | await keyValueStore.remove(SettingService.STORAGE_KEY); 97 | // clear 98 | localStorage.clear(); 99 | } 100 | 101 | /** 102 | * リセットデータをセットする。 103 | * 104 | * @return プロミス 105 | */ 106 | async setResetData(): Promise { 107 | await this.removeAll(); 108 | 109 | let response = await this.http 110 | .get('./assets/configs/reset/robots.json', { 111 | responseType: 'text' 112 | }) 113 | .toPromise(); 114 | this.robotService.restoreFromJSON(response); 115 | response = await this.http 116 | .get('./assets/configs/reset/poses.json', { 117 | responseType: 'text' 118 | }) 119 | .toPromise(); 120 | this.poseService.restoreFromJSON(response); 121 | response = await this.http 122 | .get('./assets/configs/reset/cmd-lists.json', { 123 | responseType: 'text' 124 | }) 125 | .toPromise(); 126 | this.cmdListService.restoreFromJSON(response); 127 | 128 | await this.storeAll(); 129 | } 130 | 131 | /** 132 | * 全てストアする。 133 | * 134 | * @return プロミス 135 | */ 136 | async storeAll(): Promise { 137 | await this.robotService.store(); 138 | await this.poseService.store(); 139 | await this.cmdListService.store(); 140 | await this.settingService.store(); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/app/viewer/shared/markers/orientation-marker.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import 'pixi.js'; 7 | import { BaseViewer } from '../base-viewer'; 8 | import { RosMarker } from './ros-marker'; 9 | 10 | /** 11 | * オリエンテーションマーカーです。 12 | */ 13 | export class OrientationMarker extends RosMarker { 14 | /** ロボットワールドコンテナ */ 15 | robotWorld: PIXI.Container; 16 | 17 | /** スケーリングロボットワールドコンテナ */ 18 | robotWorldScale: PIXI.Container; 19 | 20 | /** ポーズ */ 21 | rospose: ROSLIB.Pose; 22 | 23 | /** X座標の値 */ 24 | offsetx = 0; 25 | 26 | /** Y座標の値 */ 27 | offsety = 0; 28 | 29 | /** 角度 */ 30 | offsettheta = 0; 31 | 32 | /** 33 | * オブジェクトを構築する。 34 | * 35 | * ロボットワールドコンテナをRosワールドコンテナに追加する。 36 | * スケーリングロボットワールドコンテナをスケーリングRosワールドコンテナに追加する。 37 | * 38 | * コンテナの親子関係 39 | * Viewer.root -> BaseMarker.root 40 | * Viewer.rosWorld -> RosMarker.rosWorld -> OrientationMarker.robotWorld 41 | * Viewer.rosWorldScale -> RosMarker.rosWorldScale -> OrientationMarker.robotWorldScale 42 | * 43 | * @param viewer ビューワー 44 | */ 45 | constructor(viewer: BaseViewer) { 46 | super(viewer); 47 | this.robotWorld = new PIXI.Container(); 48 | this.rosWorld.addChild(this.robotWorld); 49 | this.robotWorldScale = new PIXI.Container(); 50 | this.rosWorldScale.addChild(this.robotWorldScale); 51 | } 52 | 53 | /** 54 | * ポーズを設定し、オフセットを構築して設定し、ロボットワールドをアップデートする。 55 | * 56 | * @param rospose ポーズ 57 | */ 58 | setRosPose(rospose: ROSLIB.Pose): void { 59 | if (rospose != null) { 60 | this.rospose = rospose; 61 | 62 | const poseX = rospose.position.x; 63 | const poseY = rospose.position.y; 64 | 65 | const q0 = rospose.orientation.w; 66 | const q1 = rospose.orientation.x; 67 | const q2 = rospose.orientation.y; 68 | const q3 = rospose.orientation.z; 69 | const poseTheta = Math.atan2( 70 | 2 * (q0 * q3 + q1 * q2), 71 | 1 - 2 * (q2 * q2 + q3 * q3) 72 | ); 73 | 74 | this.setOffset(poseX, poseY, poseTheta); 75 | this.updateContainer(); 76 | } 77 | } 78 | 79 | /** 80 | * オフセットをセットする。 81 | * 82 | * @param x X座標の値 83 | * @param y Y座標の値 84 | * @param theta 角度 85 | */ 86 | setOffset(x: number, y: number, theta: number): void { 87 | this.offsetx = x; 88 | this.offsety = y; 89 | this.offsettheta = theta; 90 | } 91 | 92 | /** 93 | * 現在のオフセットでコンテナをアップデートする。 94 | * 95 | * ルートコンテナの原点をオフセット位置(Ros座標からスクリーン座標に変換)に変更する。 96 | * Rosワールドコンテナの原点をオフセット位置(スケーリングではないためレゾリューションで位置を算出)に変更する。 97 | * スケーリングRosワールドコンテナの原点をオフセット位置に変更する。 98 | * 99 | * ルートコンテナは回転させない。(主にテキスト表示に使用するため) 100 | * スケーリングRosワールドコンテナ。 101 | */ 102 | updateContainer(): void { 103 | // for screen 104 | const rosPoint = new PIXI.Point(this.offsetx, this.offsety); 105 | const screenPoint = this.viewer.rosToScreen(rosPoint); 106 | this.root.x = screenPoint.x; 107 | this.root.y = screenPoint.y; 108 | 109 | // set ros position 110 | this.rosWorld.x = this.offsetx * this.viewer.resolution; 111 | this.rosWorld.y = this.offsety * this.viewer.resolution; 112 | this.rosWorldScale.x = this.offsetx; 113 | this.rosWorldScale.y = this.offsety; 114 | 115 | // set robot direction 116 | this.robotWorld.rotation = this.offsettheta; 117 | this.robotWorldScale.rotation = this.offsettheta; 118 | } 119 | 120 | /** 121 | * ビューが変化した際にアップデートする。 122 | * 123 | * コンテナをアップデートする。 124 | */ 125 | update(): void { 126 | super.update(); 127 | this.updateContainer(); 128 | } 129 | 130 | /** 131 | * 表示する。 132 | * 133 | * ロボットワールドコンテナを表示する。 134 | * スケーリングロボットワールドコンテナを表示する。 135 | */ 136 | show(): void { 137 | super.show(); 138 | this.robotWorld.visible = true; 139 | this.robotWorldScale.visible = true; 140 | } 141 | 142 | /** 143 | * 非表示にする。 144 | * 145 | * ロボットワールドコンテナを非表示にする。 146 | * スケーリングロボットワールドコンテナを非表示にする。 147 | */ 148 | hide(): void { 149 | super.hide(); 150 | this.robotWorld.visible = false; 151 | this.robotWorldScale.visible = false; 152 | } 153 | 154 | /** 155 | * 削除する。 156 | * 157 | * ロボットワールドコンテナをRosワールドコンテナから削除にする。 158 | * スケーリングロボットワールドコンテナをスケーリングRosワールドコンテナから削除にする。 159 | */ 160 | remove(): void { 161 | super.remove(); 162 | this.rosWorld.removeChild(this.robotWorld); 163 | this.rosWorldScale.removeChild(this.robotWorldScale); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { BrowserModule } from '@angular/platform-browser'; 7 | import { NgModule, APP_INITIALIZER, Injector } from '@angular/core'; 8 | 9 | import { AppComponent } from './app.component'; 10 | import { AppRoutingModule } from './app-routing.module'; 11 | import { FormsModule } from '@angular/forms'; 12 | import { HttpClientModule } from '@angular/common/http'; 13 | import { HomeComponent } from './home/home.component'; 14 | import { SidenavComponent } from './sidenav/sidenav.component'; 15 | import { SettingsComponent } from './settings/settings.component'; 16 | import { SettingDetailComponent } from './settings/setting-detail/setting-detail.component'; 17 | import { PosesComponent } from './poses/poses.component'; 18 | import { PoseDetailComponent } from './poses/pose-detail/pose-detail.component'; 19 | import { PoseFormComponent } from './poses/pose-form/pose-form.component'; 20 | import { RobotsComponent } from './robots/robots.component'; 21 | import { RobotDetailComponent } from './robots/robot-detail/robot-detail.component'; 22 | import { RobotFormComponent } from './robots/robot-form/robot-form.component'; 23 | import { CommandsComponent } from './commands/commands.component'; 24 | import { CmdListsComponent } from './cmd-lists/cmd-lists.component'; 25 | import { CmdListDetailComponent } from './cmd-lists/cmd-list-detail/cmd-list-detail.component'; 26 | import { CmdListFormComponent } from './cmd-lists/cmd-list-form/cmd-list-form.component'; 27 | import { ViewerComponent } from './viewer/viewer.component'; 28 | import { InputDialogComponent } from './viewer/input-dialog/input-dialog.component'; 29 | import { SelectDialogComponent } from './viewer/select-dialog/select-dialog.component'; 30 | import { InitializerService } from './shared/config/initializer.service'; 31 | import { ServiceWorkerModule } from '@angular/service-worker'; 32 | import { environment } from '../environments/environment'; 33 | import { NoopAnimationsModule } from '@angular/platform-browser/animations'; 34 | import { MatAutocompleteModule } from '@angular/material/autocomplete'; 35 | import { MatButtonModule } from '@angular/material/button'; 36 | import { MatCardModule } from '@angular/material/card'; 37 | import { MatDialogModule } from '@angular/material/dialog'; 38 | import { MatExpansionModule } from '@angular/material/expansion'; 39 | import { MatFormFieldModule } from '@angular/material/form-field'; 40 | import { MatIconModule } from '@angular/material/icon'; 41 | import { MatInputModule } from '@angular/material/input'; 42 | import { MatListModule } from '@angular/material/list'; 43 | import { MatSelectModule } from '@angular/material/select'; 44 | import { MatSidenavModule } from '@angular/material/sidenav'; 45 | 46 | @NgModule({ 47 | declarations: [ 48 | AppComponent, 49 | HomeComponent, 50 | SidenavComponent, 51 | SettingsComponent, 52 | SettingDetailComponent, 53 | PosesComponent, 54 | PoseDetailComponent, 55 | PoseFormComponent, 56 | RobotsComponent, 57 | RobotDetailComponent, 58 | RobotFormComponent, 59 | CommandsComponent, 60 | CmdListsComponent, 61 | CmdListDetailComponent, 62 | CmdListFormComponent, 63 | ViewerComponent, 64 | InputDialogComponent, 65 | SelectDialogComponent 66 | ], 67 | imports: [ 68 | BrowserModule, 69 | AppRoutingModule, 70 | ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }), 71 | FormsModule, 72 | HttpClientModule, 73 | NoopAnimationsModule, 74 | MatAutocompleteModule, 75 | MatButtonModule, 76 | MatCardModule, 77 | MatDialogModule, 78 | MatExpansionModule, 79 | MatFormFieldModule, 80 | MatIconModule, 81 | MatInputModule, 82 | MatListModule, 83 | MatSelectModule, 84 | MatSidenavModule 85 | ], 86 | entryComponents: [ 87 | InputDialogComponent, 88 | SelectDialogComponent 89 | ], 90 | providers: [ 91 | InitializerService, 92 | { 93 | provide: APP_INITIALIZER, 94 | useFactory: (initializerService: InitializerService) => () => 95 | initializerService.initialize(), 96 | deps: [InitializerService], 97 | multi: true 98 | } 99 | ], 100 | bootstrap: [AppComponent] 101 | }) 102 | export class AppModule { 103 | /** 104 | * オブジェクトを構築する。 105 | * インジェクターからサービスを取得する。 106 | * 107 | * @param injector インジェクター 108 | */ 109 | constructor(private injector: Injector) { 110 | InitializerService.injector = this.injector; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/app/commands/commands.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Component, Renderer2 } from '@angular/core'; 7 | import { Command } from './shared/command.model'; 8 | import { CommandService } from './shared/command.service'; 9 | import { SettingService } from '../settings/shared/setting.service'; 10 | import { StorageComponent } from '../shared/storage/storage-component'; 11 | import { StoreService } from '../shared/store/store.service'; 12 | 13 | /** 14 | * コマンドのコンポーネントです。 15 | */ 16 | @Component({ 17 | selector: 'app-commands', 18 | templateUrl: './commands.component.html', 19 | styleUrls: ['./commands.component.css'] 20 | }) 21 | export class CommandsComponent extends StorageComponent { 22 | /** ダウンロードプレフィックスネーム */ 23 | downloadPrefixName = 'commands'; 24 | 25 | /** 候補として表示するコマンドのオプション */ 26 | cmds: string[] = []; 27 | 28 | /** 29 | * オブジェクトを構築する。 30 | * 31 | * @param settingService 設定サービス 32 | * @param service コマンドサービス 33 | * @param storeService ストアサービス 34 | * @param renderer2 レンダラー2 35 | */ 36 | constructor( 37 | public settingService: SettingService, 38 | public service: CommandService, 39 | public storeService: StoreService, 40 | public renderer2: Renderer2 41 | ) { 42 | super(); 43 | this.cmds = this.service.getCmds(); 44 | } 45 | 46 | /** 47 | * 前に移動する。 48 | * 49 | * @param command コマンド 50 | */ 51 | movePrev(command: Command): void { 52 | this.service.movePrev(command); 53 | } 54 | 55 | /** 56 | * 後ろに移動する。 57 | * 58 | * @param command コマンド 59 | */ 60 | moveNext(command: Command): void { 61 | this.service.moveNext(command); 62 | } 63 | 64 | /** 65 | * 前にインサートする。 66 | * 67 | * @param command コマンド 68 | */ 69 | insertPrev(command: Command): void { 70 | const newModel = this.service.getEmptyModel(); 71 | this.service.insertPrev(newModel, command); 72 | } 73 | 74 | /** 75 | * 次にインサートする。 76 | * 77 | * @param command コマンド 78 | */ 79 | insertNext(command: Command): void { 80 | const newModel = this.service.getEmptyModel(); 81 | this.service.insertNext(newModel, command); 82 | } 83 | 84 | /** 85 | * リムーブする。 86 | * 87 | * @param command コマンド 88 | */ 89 | remove(command: Command): void { 90 | if (1 < this.service.getAll().length) { 91 | this.service.remove(command); 92 | } 93 | } 94 | 95 | /** 96 | * インデックスを取得する。 97 | * 98 | * @param command コマンド 99 | * @return インデックス 100 | */ 101 | indexOf(command: Command): number { 102 | return this.service.indexOf(command); 103 | } 104 | 105 | /** 106 | * ヒントを取得する。 107 | * 108 | * @param command コマンド 109 | * @param index インデックス 110 | * @return ヒント 111 | */ 112 | getHint(command: Command, index: number): string { 113 | return this.service.getHint(command.command.cmd, index); 114 | } 115 | 116 | /** 117 | * オートコンプリートを取得する。 118 | * 119 | * @param command コマンド 120 | * @param index インデックス 121 | * @return オートコンプリート 122 | */ 123 | getAutocomplete(command: Command, index: number): string[] { 124 | return this.service.getAutocomplete(command.command.cmd, index); 125 | } 126 | 127 | /** 128 | * データをクリアする。 129 | */ 130 | async clear(): Promise { 131 | await super.clear(); 132 | const newModel = this.service.getEmptyModel(); 133 | await this.service.add(newModel); 134 | } 135 | 136 | /** 137 | * 値が変化した際にコールバックされる。 138 | */ 139 | onChange(): void { 140 | this.service.store(); 141 | } 142 | 143 | /** 144 | * 値が変化した際にコールバックされる。 145 | * 146 | * @param event イベント 147 | * @param command コマンド 148 | */ 149 | onChangeCmd(event: any, command: Command): void { 150 | const commandsInfo = this.service 151 | .getCommandsInfo() 152 | .filter(element => element.cmd === event); 153 | if (commandsInfo && 0 < commandsInfo.length) { 154 | const count = commandsInfo[0].paramCount; 155 | command.command.params = []; 156 | for (let i = 0; i < count; i++) { 157 | const autocomplete = this.getAutocomplete(command, i); 158 | if (0 < autocomplete.length) { 159 | command.command.params.push(autocomplete[0]); 160 | } else { 161 | command.command.params.push(''); 162 | } 163 | } 164 | } else { 165 | command.command.params = []; 166 | } 167 | this.onChange(); 168 | } 169 | 170 | /** 171 | * インデックスでトラックする。 172 | * 173 | * @param index インデックス 174 | * @return トラック 175 | */ 176 | trackByFn(index: number): any { 177 | return index; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/app/shared/storage/storage-component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { OnInit, Renderer2, AfterViewInit, OnDestroy } from '@angular/core'; 7 | import { Subscription } from 'rxjs'; 8 | import { PickableModel } from '../pickable-model'; 9 | import { StorageService } from './storage-service'; 10 | import { KeyValueStore } from '../store/key-value-store'; 11 | import { StoreService } from '../store/store.service'; 12 | 13 | /** 14 | * ストレージの基底コンポーネントです。 15 | */ 16 | export abstract class StorageComponent< 17 | M extends PickableModel, 18 | S extends StorageService 19 | > implements OnInit, AfterViewInit, OnDestroy { 20 | /** サービス */ 21 | abstract service: S; 22 | 23 | /** ストアサービス */ 24 | abstract storeService: StoreService; 25 | 26 | /** レンダラー2 */ 27 | abstract renderer2: Renderer2; 28 | 29 | /** ダウンロードプレフィックスネーム */ 30 | abstract downloadPrefixName: string; 31 | 32 | /** サブスクリプション */ 33 | subscription: Subscription; 34 | 35 | /** アレイ */ 36 | array: M[] = []; 37 | 38 | /** 新規編集中データ */ 39 | edit: M; 40 | 41 | /** ストレージキー */ 42 | storageKey: string = 'amr-if-ui' + '-table-'; 43 | 44 | /** キーバリューストア */ 45 | keyValueStore: KeyValueStore; 46 | 47 | /** 48 | * オブジェクトを構築する。 49 | */ 50 | constructor() {} 51 | 52 | /** 53 | * コンポーネントを初期化する。 54 | * 55 | * データソースをサブスクライブして変化があればデータソースをアップデートする。 56 | * フィルタリングの設定をする。 57 | */ 58 | ngOnInit(): void { 59 | this.keyValueStore = this.storeService.getKeyValueStore(); 60 | this.edit = this.service.getEmptyModel(); 61 | 62 | this.array = this.service.getAll(); 63 | this.subscription = this.service.getObservable().subscribe(data => { 64 | this.array = data.array; 65 | }); 66 | } 67 | 68 | /** 69 | * コンポーネントのビューが初期化された後に処理を行う。 70 | */ 71 | ngAfterViewInit(): void { 72 | this.keyValueStore.get(this.getStorageKey()); 73 | } 74 | 75 | /** 76 | * コンポーネントを破棄する。 77 | * 78 | * データソースのサブスクリプションをアンサブスクライブする。 79 | */ 80 | ngOnDestroy(): void { 81 | if (this.subscription) { 82 | this.subscription.unsubscribe(); 83 | } 84 | } 85 | 86 | /** 87 | * ストレージキーを取得する。 88 | * 89 | * @return ストレージキー 90 | */ 91 | getStorageKey(): string { 92 | return this.storageKey + this.downloadPrefixName; 93 | } 94 | 95 | /** 96 | * フォームに入力された新規アイテムを追加する。 97 | * 98 | * 必須項目をチェックしてサービスでアイテムを追加し、フォームの値をクリアする。 99 | * 100 | * @return 追加されたかどうか 101 | */ 102 | async add(): Promise { 103 | if (!this.service.validate(this.edit)) { 104 | return false; 105 | } 106 | 107 | const newItem: M = this.service.getEmptyModel(); 108 | Object.assign(newItem, this.edit); 109 | await this.service.add(newItem); 110 | 111 | // clear form 112 | Object.assign(this.edit, this.service.getEmptyModel()); 113 | 114 | return true; 115 | } 116 | 117 | /** 118 | * データをファイルにエクスポートしてダウンロードする。 119 | */ 120 | export(): void { 121 | const fileName = this.downloadPrefixName + '.json'; 122 | const aElement = this.renderer2.createElement('a'); 123 | const file = new Blob([this.service.getJSON()], { 124 | type: 'application/json' 125 | }); 126 | const fileURL = window.URL.createObjectURL(file); 127 | aElement.href = fileURL; 128 | aElement.download = fileName; 129 | aElement.click(); 130 | } 131 | 132 | /** 133 | * データをファイルからインポートする。 134 | */ 135 | async import(): Promise { 136 | return new Promise(resolve => { 137 | const inputElement = this.renderer2.createElement('input'); 138 | inputElement.type = 'file'; 139 | inputElement.accept = 'application/json'; 140 | const that = this; 141 | inputElement.addEventListener( 142 | 'change', 143 | (event: any) => { 144 | const files = event.currentTarget.files; 145 | if (files.length > 0) { 146 | const file = files[0]; 147 | const reader = new FileReader(); 148 | reader.onload = (() => { 149 | return (e: any) => { 150 | try { 151 | const json = e.target.result; 152 | that.service.restoreFromJSON(json); 153 | that.service.store(); 154 | } finally { 155 | resolve(); 156 | } 157 | }; 158 | })(); 159 | reader.readAsText(file); 160 | } else { 161 | resolve(); 162 | } 163 | }, 164 | false 165 | ); 166 | inputElement.click(); 167 | }); 168 | } 169 | 170 | /** 171 | * データをクリアする。 172 | */ 173 | async clear(): Promise { 174 | await this.service.clear(); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/app/viewer/shared/markers/robot-status-marker.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Observable } from 'rxjs'; 7 | import { BaseViewer } from '../base-viewer'; 8 | import { BaseMarker } from './base-marker'; 9 | import { Robot } from '../../../robots/shared/robot.model'; 10 | import { TopicSubscriber } from '../../../shared/ros/topic-subscriber'; 11 | import { RoboCmd } from '../../../shared/ros/messages/robo-cmd.model'; 12 | import { RoboCmdList } from '../../../shared/ros/messages/robo-cmd-list.model'; 13 | import { RoboRes } from '../../../shared/ros/messages/robo-res.model'; 14 | import { RoboResList } from '../../../shared/ros/messages/robo-res-list.model'; 15 | import { environment } from 'src/environments/environment'; 16 | import { Protocol } from '../protocol/protocol.model'; 17 | import { ProtocolManager } from '../protocol/protocol-manager'; 18 | 19 | /** 20 | * ロボットステータスマーカーです。 21 | */ 22 | export class RobotStatusMarker extends BaseMarker { 23 | 24 | /** ロボット */ 25 | robot: Robot; 26 | 27 | /** ロボレスポンスリストトピック */ 28 | roboResListTopic: ROSLIB.Topic; 29 | 30 | /** プロトコルマネージャーアレイ */ 31 | protocolManagers: ProtocolManager[] = []; 32 | 33 | /** トピックサブスクライバーアレイ */ 34 | topicSubscribers: TopicSubscriber[] = []; 35 | 36 | /** 37 | * オブジェクトを構築する。 38 | * 39 | * @param viewer ビューワー 40 | * @param robot ロボット 41 | */ 42 | constructor(viewer: BaseViewer, robot: Robot) { 43 | super(viewer); 44 | this.robot = robot; 45 | const that = this; 46 | const config = environment.config; 47 | config.protocols.forEach((element: Protocol) => { 48 | let protocolManager; 49 | if ( 50 | element.request.messageType === 'trr_msgs/RoboCmdList' && 51 | element.response.messageType === 'trr_msgs/RoboResList' 52 | ) { 53 | protocolManager = new ProtocolManager(that, element); 54 | } else if ( 55 | element.request.messageType === 'trr_msgs/RoboCmd' && 56 | element.response.messageType === 'trr_msgs/RoboRes' 57 | ) { 58 | protocolManager = new ProtocolManager(that, element); 59 | } 60 | if (protocolManager) { 61 | that.protocolManagers.push(protocolManager); 62 | that.topicSubscribers.push(protocolManager); 63 | } 64 | }); 65 | } 66 | 67 | /** 68 | * トピックをサブスクライブする。 69 | */ 70 | subscribe(): void { 71 | this.topicSubscribers.forEach(element => element.subscribe()); 72 | } 73 | 74 | /** 75 | * トピックをアンサブスクライブする。 76 | */ 77 | unsubscribe(): void { 78 | this.topicSubscribers.forEach(element => element.unsubscribe()); 79 | } 80 | 81 | /** 82 | * レスポンストピックネームからプロトコルマネージャをゲットする。 83 | * 84 | * @param resTopicName レスポンストピックネーム 85 | */ 86 | getProtocolManagerByResTopicName( 87 | resTopicName: string 88 | ): ProtocolManager | undefined { 89 | return this.protocolManagers.find( 90 | element => element.protocol.response.topic === resTopicName 91 | ); 92 | } 93 | 94 | /** 95 | * レスポンスを削除する。 96 | * 97 | * @param resTopicName レスポンストピックネーム 98 | * @param id アイディー 99 | */ 100 | removeResponse(resTopicName: string, id: string) { 101 | const protocolManager = this.getProtocolManagerByResTopicName(resTopicName); 102 | if (protocolManager !== undefined) { 103 | protocolManager.removeResponse(id); 104 | } 105 | } 106 | 107 | /** 108 | * レスポンスオブザーバブルを取得する。 109 | * 110 | * @param resTopicName レスポンストピックネーム 111 | * @return レスポンスオブザーバブル 112 | */ 113 | getRoboResObservable(resTopicName: string): Observable | null { 114 | const protocolManager = this.getProtocolManagerByResTopicName(resTopicName); 115 | if (protocolManager !== undefined) { 116 | return protocolManager.getResponseObservable(); 117 | } else { 118 | return null; 119 | } 120 | } 121 | 122 | /** 123 | * レスポンスリストオブザーバブルを取得する。 124 | * 125 | * @param resTopicName レスポンスリストトピックネーム 126 | * @return レスポンスリストオブザーバブル 127 | */ 128 | getRoboResListObservable(resTopicName: string): Observable | null { 129 | const protocolManager = this.getProtocolManagerByResTopicName(resTopicName); 130 | if (protocolManager !== undefined) { 131 | return protocolManager.getResponseObservable(); 132 | } else { 133 | return null; 134 | } 135 | } 136 | 137 | /** 138 | * ロボットを取得する。 139 | * 140 | * @return ロボット 141 | */ 142 | getRobot(): Robot { 143 | return this.robot; 144 | } 145 | 146 | /** 147 | * トピックを取得する。 148 | * 149 | * @param options オプション 150 | * @return トピック 151 | */ 152 | getTopic(options: any): ROSLIB.Topic | null { 153 | return this.viewer.getComponent().getRosService().getTopic(this.robot, options); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/app/viewer/shared/markers/icon-marker.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Subscription, Subject, Observable } from 'rxjs'; 7 | import { BaseViewer } from '../base-viewer'; 8 | import { BaseMarker } from './base-marker'; 9 | import { ControlMarker } from './control-marker'; 10 | import { Icon } from './icon.model'; 11 | 12 | /** 13 | * アイコンマーカーです。 14 | */ 15 | export class IconMarker extends BaseMarker { 16 | /** コントロールマーカー */ 17 | controlMarker: ControlMarker; 18 | 19 | /** アイコン */ 20 | icon: Icon; 21 | 22 | /** アイコンラウンドラディウス */ 23 | radius = 2; 24 | 25 | /** Xアジャスト */ 26 | xAdjust = 0; 27 | 28 | /** Yアジャスト */ 29 | yAdjust = 0; 30 | 31 | /** コンテナ */ 32 | container: PIXI.Container; 33 | 34 | /** テキスト */ 35 | text: PIXI.Text; 36 | 37 | /** バックグラウンド */ 38 | background: PIXI.Graphics; 39 | 40 | /** テキストメトリクス */ 41 | textMetrics: PIXI.TextMetrics; 42 | 43 | /** イベントサブジェクト */ 44 | eventSubject: Subject = new Subject(); 45 | 46 | /** イベントオブザーバブル */ 47 | eventObservable: Observable = this.eventSubject.asObservable(); 48 | 49 | /** サブスクリプション */ 50 | subscription: Subscription; 51 | 52 | /** 53 | * オブジェクトを構築する。 54 | * 55 | * @param viewer ビューワー 56 | * @param icon アイコン 57 | * @param next サブスクライブコールバック 58 | */ 59 | constructor(viewer: BaseViewer, icon: Icon, next: (value: any) => void) { 60 | super(viewer); 61 | this.icon = icon; 62 | 63 | this.controlMarker = this.viewer.controlMarker; 64 | this.controlMarker.iconMarkers.push(this); 65 | 66 | this.container = new PIXI.Container(); 67 | this.controlMarker.container.addChild(this.container); 68 | 69 | this.background = new PIXI.Graphics(); 70 | this.container.addChild(this.background); 71 | 72 | const that = this; 73 | let style; 74 | const regex = new RegExp(/[a-zA-Z0-9]/); 75 | if (regex.test(icon.icon)) { 76 | style = new PIXI.TextStyle({ 77 | fontFamily: 'Meiryo UI Regular', 78 | fontWeight: 'normal', 79 | fill: this.controlMarker.activeColor, 80 | fontSize: this.controlMarker.iconFontSize - 4 81 | }); 82 | this.textMetrics = PIXI.TextMetrics.measureText(icon.icon, style); 83 | this.xAdjust = 84 | (this.controlMarker.iconFontSize - this.textMetrics.width) / 2; 85 | this.textMetrics.width = 24; 86 | this.yAdjust = 87 | (this.controlMarker.iconFontSize - this.textMetrics.height) / 2; 88 | this.textMetrics.height = 29; 89 | } else { 90 | style = new PIXI.TextStyle({ 91 | fontFamily: 'Material Icons', 92 | fontWeight: 'normal', 93 | fill: this.controlMarker.activeColor, 94 | fontSize: this.controlMarker.iconFontSize 95 | }); 96 | this.textMetrics = PIXI.TextMetrics.measureText(icon.icon, style); 97 | } 98 | this.text = new PIXI.Text(icon.icon, style); 99 | this.container.addChild(this.text); 100 | this.background.interactive = true; 101 | this.background 102 | .on('pointerup', (event: any) => { 103 | that.eventSubject.next(event); 104 | }); 105 | 106 | this.subscription = this.eventObservable.subscribe(next); 107 | } 108 | 109 | /** 110 | * オフセットをアップデートする。 111 | * 112 | * @param xOffset Xオフセット 113 | * @param yOffset Yオフセット 114 | */ 115 | updateOffset(xOffset: number, yOffset: number): void { 116 | if (this.background) { 117 | this.background.x = xOffset; 118 | this.background.y = yOffset; 119 | } 120 | if (this.text) { 121 | this.text.x = this.xAdjust + this.radius + xOffset; 122 | this.text.y = this.yAdjust + this.radius + yOffset; 123 | } 124 | this.redrawBackground(); 125 | } 126 | 127 | /** 128 | * バックグランドをリドローする。 129 | */ 130 | redrawBackground(): void { 131 | const g = this.background; 132 | g.clear(); 133 | if (this.textMetrics) { 134 | g.beginFill(0xa0a0a0, 0.5); 135 | const iconSize = this.textMetrics.width + this.radius * 2; 136 | g.drawRoundedRect(0, 0, iconSize, iconSize, this.radius * 4); 137 | g.endFill(); 138 | } 139 | } 140 | 141 | /** 142 | * テキストをセットする。 143 | * 144 | * @param text テキスト 145 | */ 146 | setText(text: string): void { 147 | if (this.text) { 148 | this.text.text = text; 149 | this.redrawBackground(); 150 | } 151 | } 152 | 153 | /** 154 | * フィルをセットする。 155 | * 156 | * @param fill フィル 157 | */ 158 | setFill(fill: number): void { 159 | if (this.text) { 160 | this.text.style.fill = fill; 161 | this.redrawBackground(); 162 | } 163 | } 164 | 165 | /** 166 | * ディスポーズする。 167 | */ 168 | dispose(): void { 169 | super.dispose(); 170 | if (this.subscription) { 171 | this.subscription.unsubscribe(); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/app/viewer/shared/protocol/protocol-manager.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import { Subject, Observable } from 'rxjs'; 7 | import { Protocol } from './protocol.model'; 8 | import { ProtocolAgent } from './protocol-agent'; 9 | import { RoboCmd } from '../../../shared/ros/messages/robo-cmd.model'; 10 | import { RoboRes } from '../../../shared/ros/messages/robo-res.model'; 11 | import { RoboResList } from '../../../shared/ros/messages/robo-res-list.model'; 12 | import { RoboCmdList } from '../../../shared/ros/messages/robo-cmd-list.model'; 13 | 14 | /** 15 | * プロトコルマネージャです。 16 | */ 17 | export class ProtocolManager< 18 | C extends RoboCmd | RoboCmdList, 19 | R extends RoboRes | RoboResList 20 | > { 21 | /** プロトコルエージェント */ 22 | protocolAgent: ProtocolAgent; 23 | 24 | /** プロトコル */ 25 | protocol: Protocol; 26 | 27 | /** リクエストトピック */ 28 | requestTopic: ROSLIB.Topic | null; 29 | 30 | /** リクエストアレイ */ 31 | request: C[] = []; 32 | 33 | /** リクエストサブジェクト */ 34 | requestSubject: Subject = new Subject(); 35 | 36 | /** リクエストオブザーバブル */ 37 | requestObservable: Observable = this.requestSubject.asObservable(); 38 | 39 | /** レスポンストピック */ 40 | responseTopic: ROSLIB.Topic | null; 41 | 42 | /** レスポンスアレイ */ 43 | response: R[] = []; 44 | 45 | /** レスポンスサブジェクト */ 46 | responseSubject: Subject = new Subject(); 47 | 48 | /** レスポンスオブザーバブル */ 49 | responseObservable: Observable = this.responseSubject.asObservable(); 50 | 51 | /** 52 | * オブジェクトを構築する。 53 | * 54 | * @param protocolAgent プロトコルエージェント 55 | * @param protocol プロトコル 56 | */ 57 | constructor(protocolAgent: ProtocolAgent, protocol: Protocol) { 58 | this.protocolAgent = protocolAgent; 59 | this.protocol = protocol; 60 | } 61 | 62 | /** 63 | * リクエストメッセージをプッシュする。 64 | * 65 | * @param message メッセージ 66 | */ 67 | pushRequestMessage(message: C): void { 68 | this.request.push(message); 69 | this.requestSubject.next(this.request); 70 | } 71 | 72 | /** 73 | * レスポンスメッセージをプッシュする。 74 | * 75 | * @param message メッセージ 76 | */ 77 | pushResponseMessage(message: R): void { 78 | this.response.push(message); 79 | this.responseSubject.next(this.response); 80 | } 81 | 82 | /** 83 | * レスポンスを削除する。 84 | * 85 | * @param id アイディー 86 | */ 87 | removeResponse(id: string) { 88 | this.response = this.response.filter(element => element.id !== id); 89 | } 90 | 91 | /** 92 | * レスポンスオブザーバブルを取得する。 93 | * 94 | * @return レスポンスオブザーバブル 95 | */ 96 | getResponseObservable(): Observable { 97 | return this.responseObservable; 98 | } 99 | 100 | /** 101 | * トピックをサブスクライブする。 102 | */ 103 | subscribe(): void { 104 | const that = this; 105 | 106 | // request 107 | if (this.requestTopic == null) { 108 | if (this.protocol.request) { 109 | if (this.protocol.request.topic !== '') { 110 | const requestTopicName = this.protocol.request.topic; 111 | const requestTopicMessageType = this.protocol.request.messageType; 112 | this.requestTopic = this.protocolAgent.getTopic({ 113 | name: this.protocolAgent.getRobot().namespace + '/' + requestTopicName, 114 | messageType: requestTopicMessageType 115 | }); 116 | if (this.requestTopic != null) { 117 | this.requestTopic.advertise(); 118 | this.requestTopic.subscribe((message: C) => { 119 | that.pushRequestMessage(message); 120 | }); 121 | } 122 | } 123 | } 124 | } 125 | 126 | // response 127 | if (this.responseTopic == null) { 128 | if (this.protocol.response) { 129 | if (this.protocol.response.topic !== '') { 130 | const responseTopicName = this.protocol.response.topic; 131 | const responseTopicMessageType = this.protocol.response.messageType; 132 | this.responseTopic = this.protocolAgent.getTopic({ 133 | name: this.protocolAgent.getRobot().namespace + '/' + responseTopicName, 134 | messageType: responseTopicMessageType 135 | }); 136 | if (this.responseTopic != null) { 137 | this.responseTopic.advertise(); 138 | this.responseTopic.subscribe((message: R) => { 139 | that.pushResponseMessage(message); 140 | }); 141 | } 142 | } 143 | } 144 | } 145 | } 146 | 147 | /** 148 | * トピックをアンサブスクライブする。 149 | */ 150 | unsubscribe(): void { 151 | if (this.requestTopic != null) { 152 | this.requestTopic.unadvertise(); 153 | this.requestTopic.unsubscribe(); 154 | this.requestTopic = null; 155 | } 156 | if (this.responseTopic != null) { 157 | this.responseTopic.unadvertise(); 158 | this.responseTopic.unsubscribe(); 159 | this.responseTopic = null; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/app/viewer/shared/markers/text-marker.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import 'pixi.js'; 7 | import { BaseViewer } from '../base-viewer'; 8 | import { OrientationMarker } from './orientation-marker'; 9 | import { TextPosition } from '../text-position.enum'; 10 | 11 | /** 12 | * テキストマーカーです。 13 | */ 14 | export class TextMarker extends OrientationMarker { 15 | /** メッセージ */ 16 | message: string; 17 | 18 | /** フィルカラー */ 19 | fillColor: number; 20 | 21 | /** コンテナ */ 22 | container: PIXI.Container; 23 | 24 | /** バックグラウンド */ 25 | background: PIXI.Graphics; 26 | 27 | /** テキスト */ 28 | text: PIXI.Text; 29 | 30 | /** テキストメトリクス */ 31 | textMetrics: PIXI.TextMetrics; 32 | 33 | /** ポジション */ 34 | position: TextPosition; 35 | 36 | /** 37 | * オブジェクトを構築する。 38 | * 39 | * @param viewer ビューワー 40 | */ 41 | constructor(viewer: BaseViewer) { 42 | super(viewer); 43 | 44 | this.message = ''; 45 | this.fillColor = 0; 46 | this.position = TextPosition.Above; 47 | 48 | this.container = new PIXI.Container(); 49 | this.background = new PIXI.Graphics(); 50 | this.container.addChild(this.background); 51 | this.rosWorld.addChild(this.container); 52 | 53 | this.updateTransform(); 54 | } 55 | 56 | /** 57 | * ポジションをセットする。 58 | * 59 | * @param position ポジション 60 | */ 61 | setTextPosition(position: TextPosition): void { 62 | if (this.position !== position) { 63 | this.updateText(); 64 | } 65 | this.position = position; 66 | } 67 | 68 | /** 69 | * メッセージを指定してテキストをセットしてアップデートする。 70 | * 71 | * @param message メッセージ 72 | */ 73 | setText(message: string): void { 74 | this.message = message; 75 | this.updateText(); 76 | } 77 | 78 | /** 79 | * テキストをアップデートする。 80 | */ 81 | updateText(): void { 82 | const message = this.message; 83 | if (message.length !== 0) { 84 | if (this.text) { 85 | this.container.removeChild(this.text); 86 | } 87 | 88 | const fontSize = this.viewer.getComponent().getSettingService().getFontSize(); 89 | const style = new PIXI.TextStyle({ 90 | fontFamily: 'Meiryo UI Regular', 91 | fontWeight: 'bold', 92 | fontSize, 93 | fill: this.fillColor 94 | }); 95 | 96 | const alpha = 0.5; 97 | this.text = new PIXI.Text(message, style); 98 | this.text.interactive = true; 99 | this.text.alpha = alpha; 100 | const textMetrics = PIXI.TextMetrics.measureText(message, style); 101 | this.textMetrics = textMetrics; 102 | 103 | const offset = fontSize; 104 | 105 | switch (this.position) { 106 | case TextPosition.Above: 107 | this.text.x = -textMetrics.width / 2; 108 | this.text.y = -textMetrics.height - offset; 109 | break; 110 | case TextPosition.Below: 111 | this.text.x = -textMetrics.width / 2; 112 | this.text.y = offset; 113 | break; 114 | case TextPosition.Left: 115 | this.text.x = -textMetrics.width - offset; 116 | this.text.y = -textMetrics.height / 2; 117 | break; 118 | case TextPosition.Right: 119 | this.text.x = offset; 120 | this.text.y = -textMetrics.height / 2; 121 | break; 122 | case TextPosition.Center: 123 | this.text.x = -textMetrics.width / 2; 124 | this.text.y = -textMetrics.height / 2; 125 | break; 126 | } 127 | 128 | this.container.addChild(this.text); 129 | this.redraw(); 130 | } 131 | } 132 | 133 | /** 134 | * フィルカラーをセットする。 135 | * 136 | * @param fillColor フィルカラー 137 | */ 138 | setFillColor(fillColor: number): void { 139 | this.fillColor = fillColor; 140 | if (this.text) { 141 | this.text.style.fill = this.fillColor; 142 | } 143 | } 144 | 145 | /** 146 | * バッググラウンドをリドローする。 147 | * 148 | * テキストのサイズにマージンを加えたサイズでバッググラウンドを描画する。 149 | */ 150 | redraw(): void { 151 | const backgroundColor = 0; 152 | const backgroundAlpha = 0; 153 | const backgroundMargin = 10; 154 | 155 | const g = this.background; 156 | g.clear(); 157 | g.beginFill(backgroundColor, backgroundAlpha); 158 | g.drawRect( 159 | this.text.x - backgroundMargin, 160 | this.text.y - backgroundMargin, 161 | this.textMetrics.width + 2 * backgroundMargin, 162 | this.textMetrics.height + 2 * backgroundMargin 163 | ); 164 | g.endFill(); 165 | } 166 | 167 | /** 168 | * ビューが変化した際にアップデートする。 169 | */ 170 | update(): void { 171 | super.update(); 172 | this.updateTransform(); 173 | } 174 | 175 | /** 176 | * トランスフォームにアップデートする。 177 | */ 178 | updateTransform(): void { 179 | let theta = this.viewer.getRotationRadian(); 180 | if (Math.abs(Math.sin(theta)) === 1) { 181 | theta += Math.PI; 182 | } 183 | this.container.setTransform(0, 0, 1, -1, theta, 0, 0, 0, 0); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/app/viewer/viewer.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import 'pixi.js'; 7 | import { 8 | Component, AfterViewInit, AfterViewChecked, OnDestroy, 9 | ViewChild, HostListener, Renderer2, NgZone, ElementRef 10 | } from '@angular/core'; 11 | import { MatDialog } from '@angular/material/dialog'; 12 | import { RosService } from '../shared/ros/ros.service'; 13 | import { RobotService } from '../robots/shared/robot.service'; 14 | import { PoseService } from '../poses/shared/pose.service'; 15 | import { SettingService } from '../settings/shared/setting.service'; 16 | import { CmdListService } from '../cmd-lists/shared/cmd-list.service'; 17 | import { CommandService } from '../commands/shared/command.service'; 18 | import { StoreService } from '../shared/store/store.service'; 19 | import { Viewer } from './shared/viewer'; 20 | import { KeyValueStore } from '../shared/store/key-value-store'; 21 | import { AbstractViewerComponent } from './shared/abstract-viewer-component'; 22 | 23 | /** 24 | * ビューワーコンポーネントです。 25 | */ 26 | @Component({ 27 | selector: 'app-viewer', 28 | templateUrl: './viewer.component.html', 29 | styleUrls: ['./viewer.component.css'] 30 | }) 31 | export class ViewerComponent extends AbstractViewerComponent 32 | implements AfterViewInit, AfterViewChecked, OnDestroy { 33 | /** ストレージキー */ 34 | storageKey: string = 'amr-if-ui' + '-view'; 35 | 36 | /** キーバリューストア */ 37 | keyValueStore: KeyValueStore; 38 | 39 | /** ビューワー */ 40 | viewer: Viewer; 41 | 42 | /** canvasを動的に追加するプレイスホルダーとなる要素 */ 43 | @ViewChild('canvasesPlaceholderElement', {static: false}) 44 | canvasesPlaceholderElementRef: ElementRef; 45 | 46 | /** ビューを追加する要素 */ 47 | @ViewChild('viewerElement', {static: false}) 48 | viewerElementRef: ElementRef; 49 | 50 | /** 51 | * オブジェクトを構築する。 52 | * 53 | * @param storeService ストアサービス 54 | * @param settingService セッティングサービス 55 | * @param rosService ROSサービス 56 | * @param poseService ポーズサービス 57 | * @param robotService ロボットサービス 58 | * @param commandService コマンドサービス 59 | * @param cmdListService コマンドリストサービス 60 | * @param inputDialog インプットダイアログ 61 | * @param selectDialog セレクトダイアログ 62 | * @param renderer2 レンダラー2 63 | * @param ngZone ゾーン 64 | */ 65 | constructor( 66 | public storeService: StoreService, 67 | public settingService: SettingService, 68 | public rosService: RosService, 69 | public poseService: PoseService, 70 | public robotService: RobotService, 71 | public commandService: CommandService, 72 | public cmdListService: CmdListService, 73 | public inputDialog: MatDialog, 74 | public selectDialog: MatDialog, 75 | public renderer2: Renderer2, 76 | public ngZone: NgZone 77 | ) { 78 | super(); 79 | this.keyValueStore = storeService.getKeyValueStore(); 80 | } 81 | 82 | /** 83 | * コンポーネントのビューが初期化された後に処理を行う。 84 | * 85 | * ビューを作成してビューのcanvas要素を追加する。 86 | */ 87 | ngAfterViewInit(): void { 88 | const that = this; 89 | this.ngZone.runOutsideAngular(() => { 90 | that.viewer = new Viewer(that); 91 | that.getViewerElementRef().nativeElement.appendChild(that.viewer.getView()); 92 | this.keyValueStore.get(that.storageKey).then(value => { 93 | const restoredView = JSON.parse(value); 94 | if (restoredView) { 95 | Object.assign(that.viewer.state, restoredView); 96 | that.viewer.restoreState(); 97 | } 98 | const robots = that.viewer.component.getRobotService().getAll(); 99 | if (0 < robots.length) { 100 | const robot = robots[0]; 101 | const robotMarker = that.viewer.getRobotMarker(robot); 102 | if (robotMarker !== undefined) { 103 | that.viewer.showControlMarker(robotMarker); 104 | } 105 | } 106 | }); 107 | }); 108 | } 109 | 110 | /** 111 | * コンポーネントのビューがチェックされた後に処理を行う。 112 | */ 113 | ngAfterViewChecked(): void { 114 | if ( 115 | this.viewer.viewWidth !== this.getViewerElementRef().nativeElement.offsetWidth || 116 | this.viewer.viewHeight !== this.getViewerElementRef().nativeElement.offsetHeight 117 | ) { 118 | this.viewer.onResize(); 119 | } 120 | } 121 | 122 | /** 123 | * コンポーネントを破棄する。 124 | * 125 | * ビューをアンサブスクライブする。 126 | */ 127 | ngOnDestroy(): void { 128 | this.storeState(); 129 | this.viewer.destroy(); 130 | } 131 | 132 | /** 133 | * ステートをストアする。 134 | */ 135 | storeState(): void { 136 | const stateJson = JSON.stringify(this.viewer.state); 137 | this.keyValueStore.set(this.storageKey, stateJson); 138 | } 139 | 140 | /** 141 | * ウィンドウリサイズのイベントをハンドリングする。 142 | */ 143 | @HostListener('window:resize', ['$event']) 144 | onResize(): void { 145 | this.viewer.onResize(); 146 | } 147 | 148 | /** 149 | * キーダウンのイベントをハンドリングする。 150 | * 151 | * @param event イベント 152 | */ 153 | @HostListener('document:keydown', ['$event']) 154 | onKeyDown(event: KeyboardEvent): void { 155 | this.viewer.onKeyDown(event); 156 | this.storeState(); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /docs/setup.md: -------------------------------------------------------------------------------- 1 | # AMR-IF-UI: 環境構築 2 | 3 | 4 | 5 | - [1. 環境構築](#1-環境構築) 6 | - [1.1. 環境](#11-環境) 7 | - [1.2. Google Chromeの インストール](#12-google-chromeの-インストール) 8 | - [1.3. node.js のインストール](#13-nodejs-のインストール) 9 | - [1.3.1. Ubuntu 18.04/ros melodic の場合](#131-ubuntu-1804ros-melodic-の場合) 10 | - [1.3.2. Home Brew (macOS)](#132-home-brew-macos) 11 | 12 | 13 | 14 | ## 1. 環境構築 15 | 16 | ### 1.1. 環境 17 | 本パッケージを動作させるには以下の環境が必要です。 18 | 19 | - node.js 12.16.1 で動作確認済み 20 | - OS: AMR-IFサーバ (node.js) を動作可能であれば何でも良い 21 | - Linux, Windows あるいは macOS 等 22 | - 本ドキュメントでは Linux 上で動作させる方法を説明します 23 | - httpサーバ (Apache等) 24 | - Google Chrome 25 | - Google Chromeで動作する JavaScriptのバージョンが重要 26 | - Angular 27 | - npmなどでインストール 28 | 29 | ### 1.2. Google Chromeの インストール 30 | 31 | Ubuntu Linux への Google Chrome も他のOS同様、 32 | ブラウザ(Ubuntuにデフォルトでインストールされている FireFox等を利用) 33 | で "chrome" と検索し、画面の指示に従いインストールするのが近道です。 34 | 35 | Ubuntuの場合、debパッケージ版のchromeがダウンロードされ、 36 | パッケージマネージャでインストールするところまでほぼ自動で行われます。 37 | 38 | インストール後は、左下の "アプリケーションを表示する" から 39 | Google Chromeを選択して起動するか、 40 | 41 | ```shell 42 | $ /opt/google/chrome/chrome & 43 | ``` 44 | として起動することもできます。 45 | 46 | 47 | ### 1.3. node.js のインストール 48 | 49 | ```shell 50 | $ sudo apt update 51 | $ sudo apt upgrade 52 | $ sudo apt install nodejs npm 53 | ``` 54 | 55 | 更に、npm で n package をインストールし、n package を使って新しい node をインストールします。 56 | 57 | 58 | ```shell 59 | $ sudo npm install n -g 60 | /usr/local/bin/n -> /usr/local/lib/node_modules/n/bin/n 61 | /usr/local/lib 62 | └── n@7.4.1 63 | 64 | $ sudo n stable 65 | installing : node-v14.17.6 66 | mkdir : /usr/local/n/versions/node/14.17.6 67 | fetch : https://nodejs.org/dist/v14.17.6/node-v14.17.6-linux-x64.tar.xz 68 | installed : v14.17.6 (with npm 6.14.15) 69 | 70 | Note: the node command changed location and the old location may be remembered in your current shell. 71 | old : /usr/bin/node 72 | new : /usr/local/bin/node 73 | To reset the command location hash either start a new shell, or execute PATH="$PATH" 74 | 75 | # n packageでは /usr/local/n の下に任意のバージョンのnodejsをインストールし管理 76 | # /usr/local/bin の下にシンボリックリンクを張って使用する。 77 | # したがって、/usr/local/bin の下にパスを通す必要がある。 78 | ``` 79 | 80 | aptで入れた nodejs/npm は古く、もう不要なので削除します。パッケージ削除 81 | 後にパスを更新しないと正常にアクセスできないようなので .bashrc を読み直 82 | しています。 83 | 84 | ```shell 85 | $ sudo apt purge nodejs npm 86 | $ source ~/.bashrc 87 | $ node -v 88 | v14.17.6 89 | $ npm -v 90 | 6.14.15 91 | $ which node 92 | /usr/local/bin/node 93 | $ which npm 94 | /usr/local/bin/npm 95 | ``` 96 | 97 | さらに、angular CLI をインストールします。 98 | 99 | ```shell 100 | $ npm install -g @angular/cli 101 | ``` 102 | 103 | #### 1.3.1. Ubuntu 18.04/ros melodic の場合 104 | 105 | rosがインストールされている状態から npm をインストールしようとすると、 106 | libssl1.0-dev の依存関係が解決されずインストールできません。 107 | 強制的に libssl1.0-dev をインストールすると 108 | ros関係のパッケージが大量に削除されるので注意が必要です。 109 | 110 | ##### libsslのインストールとログ保存 111 | 112 | 以下の手順で、一旦libssl1.0-dev/npmパッケージをインストールした上で、 113 | npm を最新に更新し、その後rosパッケージを再インストールする、 114 | という方法を取ります。 115 | 116 | ```shell 117 | # aptのログを退避 118 | $ sudo mv /var/log/apt/history.log /var/log/apt/history.log.org 119 | $ sudo touch /var/log/apt/history.log 120 | 121 | # npmのインストール 122 | $ sudo apt install libssl1.0-dev npm nodejs 123 | $ node -v 124 | v8.10.0 125 | $ npm -v 126 | v3.5.2 127 | 128 | # aptのログをコピーして保管 & もとに戻す 129 | $ cp /var/log/apt/history.log . 130 | $ sudo mv /var/log/apt/history.log.org /var/log/apt/history.log 131 | $ sudo cat history.log >> /var/log/apt/history.log 132 | ``` 133 | 134 | ##### n pakcageのインストール 135 | 136 | 更に、上記同様、npm で n package を使って node をインストールします。 137 | 138 | ```shell 139 | $ sudo npm install n -g 140 | $ sudo n stable 141 | ``` 142 | 143 | ##### deb package の nodejs/npm の削除 144 | apt で入れた nodejs/npm はもう不要なので削除します。 145 | パッケージ削除後にパスを更新しないと正常にアクセスできないようなので 146 | .bashrc を読み直しています。 147 | また、同様に angular CLI をインストールします。 148 | 149 | ```shell 150 | $ sudo apt purge nodejs npm 151 | $ source ~/.bashrc 152 | $ node -v 153 | v14.17.6 154 | $ npm -v 155 | 6.14.15 156 | $ which node 157 | /usr/local/bin/node 158 | $ which npm 159 | /usr/local/bin/npm 160 | 161 | # angular CLIをインストール 162 | $ npm install -g @angular/cli 163 | ``` 164 | 165 | ##### rosのパッケージを戻す 166 | 167 | libssl1.0-devをインストールするときに ROS のパッケージ群が削除されましたので、 168 | 最初に保存した history.log から削除されたパッケージを再インストールします。 169 | 170 | ```shell 171 | $ ls history.log 172 | $ cat history.log 173 | 174 | Start-Date: 2021-09-23 16:05:34 175 | Commandline: apt install libssl1.0-dev npm 176 | Requested-By: n-ando (1000) 177 | : 178 | 中略 179 | Remove: ros-melodic-image-proc:amd64 (1.15.0-1bionic.20210505.035446), ros-melod 180 | ``` 181 | 182 | history.log の Remove: から始まる行に削除されたパッケージ名がリストアップされています。 183 | このエントリーは 184 | 185 | ``` 186 | : (), ... 187 | ``` 188 | というフォーマットになっていますので、以下のようにして、 189 | パッケージ名だけ取り出して再度インストールし直します。 190 | 191 | ```shell 192 | $ grep Remove history.log | awk 'BEGIN{RS=",";}!/Remove/{sub("\:.*",""); print $1;}' |xargs sudo apt install --yes 193 | ``` 194 | 195 | #### 1.3.2. Home Brew (macOS) 196 | 197 | macOS では Home Brew を利用すると容易に環境を構築することができます。 198 | 199 | - [Home Brew](https://brew.sh/index_ja) 200 | 201 | ```shell 202 | $ brew update 203 | $ brew install nodejs npm 204 | $ npm install -g @angular/cli 205 | ``` 206 | 207 | -------------------------------------------------------------------------------- /src/app/viewer/shared/markers/path-marker.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TOSHIBA Corporation. 3 | * SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | import 'pixi.js'; 7 | import { BaseViewer } from '../base-viewer'; 8 | import { RosMarker } from './ros-marker'; 9 | import { Robot } from '../../../robots/shared/robot.model'; 10 | 11 | /** 12 | * パスマーカーです。 13 | */ 14 | export class PathMarker extends RosMarker { 15 | /** ロボット */ 16 | robot: Robot; 17 | 18 | /** リザルトトピック */ 19 | resultTopic: ROSLIB.Topic | null; 20 | 21 | /** リザルトトピック(mbf) */ 22 | resultTopicMbf: ROSLIB.Topic | null; 23 | 24 | /** パストピック */ 25 | pathTopic: ROSLIB.Topic | null; 26 | 27 | /** パストピック(mbf) */ 28 | pathTopicMbf: ROSLIB.Topic | null; 29 | 30 | /** パス */ 31 | path: any; 32 | 33 | /** コンテナ */ 34 | container: PIXI.Container; 35 | 36 | /** パスシェイプ */ 37 | pathShape: PIXI.Graphics; 38 | 39 | /** 40 | * オブジェクトを構築する。 41 | * 42 | * @param viewer ビューワー 43 | * @param robot ロボット 44 | */ 45 | constructor(viewer: BaseViewer, robot: Robot) { 46 | super(viewer); 47 | this.robot = robot; 48 | 49 | this.container = new PIXI.Container(); 50 | this.pathShape = new PIXI.Graphics(); 51 | this.container.addChild(this.pathShape); 52 | 53 | this.rosWorldScale.addChild(this.container); 54 | 55 | this.redraw(); 56 | } 57 | 58 | /** 59 | * パスをクリアする。 60 | */ 61 | clearPath(): void { 62 | this.setPath(null); 63 | } 64 | 65 | /** 66 | * パスをセットする。 67 | * 68 | * @param path パス 69 | */ 70 | setPath(path: any): void { 71 | this.path = path; 72 | this.redraw(); 73 | } 74 | 75 | /** 76 | * リドローする。 77 | */ 78 | redraw(): void { 79 | const strokeSize = 0.4; 80 | const strokeColor = parseInt(this.robot.color.substring(1, 7), 16); 81 | const alpha = parseInt(this.robot.color.substring(7, 9), 16) / 255.0; 82 | 83 | this.pathShape.clear(); 84 | 85 | if (this.path !== null && typeof this.path !== 'undefined') { 86 | const g = this.pathShape; 87 | g.lineStyle(strokeSize, strokeColor, alpha); 88 | for (let i = 0; i < this.path.poses.length; ++i) { 89 | const rosPoint = new PIXI.Point( 90 | this.path.poses[i].pose.position.x, 91 | this.path.poses[i].pose.position.y 92 | ); 93 | if (i === 0) { 94 | g.moveTo(rosPoint.x, rosPoint.y); 95 | } else { 96 | g.lineTo(rosPoint.x, rosPoint.y); 97 | g.moveTo(rosPoint.x, rosPoint.y); 98 | } 99 | } 100 | } 101 | } 102 | 103 | /** 104 | * トピックをサブスクライブする。 105 | */ 106 | subscribe(): void { 107 | const that = this; 108 | 109 | if (this.resultTopic == null) { 110 | this.resultTopic = this.viewer.getComponent().getRosService().getTopic(this.robot, { 111 | name: this.robot.namespace + '/move_base/result', 112 | messageType: 'move_base_msgs/MoveBaseActionResult' 113 | }); 114 | if (this.resultTopic != null) { 115 | this.resultTopic.subscribe(() => { 116 | that.hide(); 117 | }); 118 | } 119 | } 120 | if (this.resultTopicMbf == null) { 121 | this.resultTopicMbf = this.viewer.getComponent().getRosService().getTopic(this.robot, { 122 | name: this.robot.namespace + '/move_base_flex/exe_path/result', 123 | messageType: 'mbf_msgs/ExePathActionResult' 124 | }); 125 | if (this.resultTopicMbf != null) { 126 | this.resultTopicMbf.subscribe(() => { 127 | that.hide(); 128 | }); 129 | } 130 | } 131 | 132 | if (this.pathTopic == null) { 133 | this.pathTopic = this.viewer.getComponent().getRosService().getTopic(this.robot, { 134 | name: this.robot.namespace + '/move_base/NavfnROS/plan', 135 | messageType: 'nav_msgs/Path' 136 | }); 137 | if (this.pathTopic != null) { 138 | this.pathTopic.subscribe(path => { 139 | that.setPath(path); 140 | that.show(); 141 | }); 142 | } 143 | } 144 | if (this.pathTopicMbf == null) { 145 | this.pathTopicMbf = this.viewer.getComponent().getRosService().getTopic(this.robot, { 146 | name: this.robot.namespace + '/move_base_flex/get_path/result', 147 | messageType: 'mbf_msgs/GetPathActionResult' 148 | }); 149 | if (this.pathTopicMbf != null) { 150 | this.pathTopicMbf.subscribe((getPathActionResult: any) => { 151 | that.setPath(getPathActionResult.result.path); 152 | that.show(); 153 | }); 154 | } 155 | } 156 | } 157 | 158 | /** 159 | * トピックをアンサブスクライブする。 160 | */ 161 | unsubscribe(): void { 162 | if (this.resultTopic != null) { 163 | this.resultTopic.unsubscribe(); 164 | this.resultTopic = null; 165 | } 166 | if (this.resultTopicMbf != null) { 167 | this.resultTopicMbf.unsubscribe(); 168 | this.resultTopicMbf = null; 169 | } 170 | 171 | if (this.pathTopic != null) { 172 | this.pathTopic.unsubscribe(); 173 | this.pathTopic = null; 174 | } 175 | if (this.pathTopicMbf != null) { 176 | this.pathTopicMbf.unsubscribe(); 177 | this.pathTopicMbf = null; 178 | } 179 | 180 | this.clearPath(); 181 | } 182 | 183 | /** 184 | * 非表示にする。 185 | * 186 | * 次回表示のために非表示時にクリアする。 187 | */ 188 | hide(): void { 189 | super.hide(); 190 | this.clearPath(); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/app/commands/commands.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |

Commands

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 36 | 37 | 55 | 56 | 106 | 107 | 116 | 117 | 126 | 127 | 144 | 145 |
OrderIdCmdParamsOK_nextidNG_nextidOperations
24 | {{ indexOf(row) }} 25 | 28 | 29 | 34 | 35 | 38 | 39 | 45 | 46 | 50 | {{ option }} 51 | 52 | 53 | 54 | 57 | 58 | 59 | 60 |
61 | 68 | 71 | 72 | 73 | 78 | {{ getHint(row, i) }} 79 | 80 | 81 | 82 | 83 | 89 | {{ getHint(row, i) }} 90 | 91 | 95 | {{ option }} 96 | 97 | 98 | 99 | 100 | 101 | 102 |
103 |
104 |
105 |
108 | 109 | 114 | 115 | 118 | 119 | 124 | 125 | 128 | 131 | 134 | 137 | 140 | 143 |
146 |
147 |
148 |
149 | --------------------------------------------------------------------------------