├── cloud ├── ml │ ├── inference │ │ ├── python │ │ ├── vm-startup-script.sh │ │ ├── set-startup-script.sh │ │ ├── .gitignore │ │ └── run.sh │ ├── training │ │ ├── python │ │ │ └── create_cloud_derby_tf_record.py │ │ ├── tensorboard.sh │ │ ├── .gitignore │ │ ├── setenv.sh │ │ ├── export-checkpoint.sh │ │ └── create-training-vm.sh │ ├── annotation │ │ └── upload.bat │ └── setenv-ml.sh ├── controller │ ├── js │ │ ├── .gcloudignore │ │ ├── vision-response.js │ │ ├── package.json │ │ ├── bounding-box.js │ │ ├── sensor-message.js │ │ ├── settings.js │ │ ├── tests │ │ │ ├── api_failure_tests.js │ │ │ └── api_success_tests.js │ │ ├── manual-driving.js │ │ ├── tools.js │ │ ├── simulation.js │ │ ├── vision.js │ │ ├── validate.js │ │ └── drive-message.js │ ├── README.md │ ├── logs.sh │ ├── test.sh │ └── run.sh └── voice │ └── package.json ├── car ├── simulator │ ├── js │ │ ├── simulation-images │ │ │ ├── .gitignore │ │ │ ├── image1.jpg │ │ │ └── image2.jpg │ │ ├── package.json │ │ └── sensor.js │ ├── driver.sh │ └── sensor.sh ├── test_hardware.py └── driver │ ├── run.sh │ └── py │ └── robotderbycar.py ├── setup ├── systemd │ ├── driver.env │ └── driver.service ├── create-resources.sh └── template-setenv-local.sh ├── third_party ├── tf-object-detection-sample │ └── python │ │ ├── templates │ │ ├── _formhelpers.html │ │ └── upload.html │ │ └── decorator.py └── dexter │ ├── LICENSE.md │ └── robotderbycar.py ├── .gitignore ├── admin ├── ml-image-updates │ ├── js │ │ ├── package.json │ │ ├── sort.js │ │ └── app.js │ ├── sort.sh │ └── run.sh ├── export-windows-vm-image.sh ├── export-inference-vm-image.sh ├── refresh-training-images.sh ├── README.md ├── backup.sh ├── setenv.sh ├── collect-images.sh ├── stop-vms.sh ├── delete-hackathon.sh └── create-hackathon.sh ├── README.md └── CONTRIBUTING.md /cloud/ml/inference/python: -------------------------------------------------------------------------------- 1 | ../../../third_party/tf-object-detection-sample/python/ -------------------------------------------------------------------------------- /cloud/controller/js/.gcloudignore: -------------------------------------------------------------------------------- 1 | ^(.*/)?\.sh$ 2 | ^(.*/)?\.out$ 3 | tmp/ 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /cloud/ml/training/python/create_cloud_derby_tf_record.py: -------------------------------------------------------------------------------- 1 | ../../../../third_party/tensorflow/create_cloud_derby_tf_record.py -------------------------------------------------------------------------------- /car/simulator/js/simulation-images/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitignore 2 | 3 | *.jpg 4 | !image1.jpg 5 | !image2.jpg -------------------------------------------------------------------------------- /car/simulator/js/simulation-images/image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleCloudPlatform/cloud-derby/HEAD/car/simulator/js/simulation-images/image1.jpg -------------------------------------------------------------------------------- /car/simulator/js/simulation-images/image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleCloudPlatform/cloud-derby/HEAD/car/simulator/js/simulation-images/image2.jpg -------------------------------------------------------------------------------- /setup/systemd/driver.env: -------------------------------------------------------------------------------- 1 | HOME=/home/pi/ 2 | PATH=/home/pi/google-cloud-sdk/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games 3 | -------------------------------------------------------------------------------- /cloud/controller/README.md: -------------------------------------------------------------------------------- 1 | ## Controller start 2 | To start the Driving Controller, run this command: 3 | 4 | ./run.sh 5 | 6 | ## Controller App Testing 7 | To test the controller, use this command (please note the Controller can not be already running): 8 | 9 | ./test.sh -------------------------------------------------------------------------------- /setup/systemd/driver.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=The driver run code 3 | 4 | [Service] 5 | User=pi 6 | Group=pi 7 | EnvironmentFile=-/etc/sysconfig/driver.env 8 | WorkingDirectory=/home/pi/cloud-derby/car/driver/ 9 | ExecStart=/bin/bash -c './run.sh --non-interactive' 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /third_party/tf-object-detection-sample/python/templates/_formhelpers.html: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | {% macro render_field(field) %} 4 |

{{ field.label }}

5 |

{{ field(**kwargs) | safe }}

6 | {% if field.errors %} 7 | 12 | {% endif %} 13 | {% endmacro %} 14 | -------------------------------------------------------------------------------- /car/simulator/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sensor-simulator", 3 | "description": "Node.js app for Cloud Derby game - simulating sensor data from a car", 4 | "version": "1.0.0", 5 | "main": "sensor.js", 6 | "author": "Google Inc.", 7 | "license": "Apache-2.0", 8 | "engines": { 9 | "node": ">=10.0.0" 10 | }, 11 | "scripts": { 12 | "start": "node sensor.js" 13 | }, 14 | "dependencies": { 15 | "@google-cloud/pubsub": "^0.12.0", 16 | "@google-cloud/storage": "^1.6.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitignore 2 | 3 | # ------------ Ignore 4 | .secrets 5 | project-id.sh* 6 | setenv-local.sh 7 | checkpoint_graph_def 8 | tensorflow-models 9 | **/node_modules 10 | **/target 11 | **/build 12 | **/tmp 13 | **/.idea 14 | **/*.pem 15 | **/*.log 16 | **/log.txt 17 | **/*.bak 18 | **/*.old 19 | **/nohup.out 20 | **/*.class 21 | **/package-lock.json 22 | **/*service-account.json 23 | **/app-generated.yml 24 | 25 | # ------------ Do not ignore 26 | !**/.gitignore 27 | !**/.cloudignore 28 | -------------------------------------------------------------------------------- /cloud/controller/logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright 2018 Google LLC 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | gcloud app logs tail --service default -------------------------------------------------------------------------------- /admin/ml-image-updates/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloud-derby-image-sort", 3 | "description": "Sorting images for additional ML training based on crowdsourced pictures", 4 | "version": "1.0.1", 5 | "main": "app.js", 6 | "author": "Google Inc.", 7 | "license": "Apache-2.0", 8 | "engines": { 9 | "node": ">=8" 10 | }, 11 | "scripts": { 12 | "start": "node app.js" 13 | }, 14 | "dependencies": { 15 | "@google-cloud/storage": "1.6.0", 16 | "body-parser": "1.17.2", 17 | "dotenv": "5.0.1", 18 | "request": "2.85.0", 19 | "safe-buffer": "5.1.1" 20 | }, 21 | "devDependencies": { 22 | "@google-cloud/nodejs-repo-tools": "^2.3.0", 23 | "uuid": "3.1.0" 24 | }, 25 | "cloud-repo-tools": { 26 | "requiresProjectId": true, 27 | "requiresKeyFile": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cloud/ml/training/tensorboard.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright 2018 Google LLC 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | set -u # This prevents running the script if any of the variables have not been set 20 | set -e # Exit if error is detected during pipeline execution 21 | 22 | source ./setenv.sh 23 | 24 | set_python_path 25 | 26 | echo "Starting Tensorboard..." 27 | nohup tensorboard --logdir=${GCS_ML_BUCKET} --port=$TF_HTTP_PORT & 28 | 29 | sleep 2 30 | 31 | tail -f nohup.out -------------------------------------------------------------------------------- /cloud/voice/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloud-derbydialogflow-webhook", 3 | "description": "Webhook for voice control of the Cloud Derby Car", 4 | "version": "0.0.1", 5 | "private": true, 6 | "license": "Apache Version 2.0", 7 | "author": "Google Inc.", 8 | "engines": { 9 | "node": ">=6.11.1" 10 | }, 11 | "scripts": { 12 | "lint": "eslint --fix \"**/*.js\"", 13 | "start": "firebase serve --only functions", 14 | "deploy": "firebase deploy --only functions", 15 | "test": "npm run lint" 16 | }, 17 | "dependencies": { 18 | "actions-on-google": "^1.5.1", 19 | "@google-cloud/pubsub": "^0.12.0", 20 | "firebase-admin": "^4.2.1", 21 | "firebase-functions": "^0.5.9", 22 | "particle-api-js": "^6.4.3" 23 | }, 24 | "devDependencies": { 25 | "eslint": "^3.19.0", 26 | "eslint-config-semistandard": "^11.0.0", 27 | "eslint-config-standard": "^10.2.1", 28 | "eslint-plugin-import": "^2.3.0", 29 | "eslint-plugin-node": "^5.0.0", 30 | "eslint-plugin-promise": "^3.5.0", 31 | "eslint-plugin-standard": "^3.0.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Google Cloud Derby 2 | ===== 3 | 4 | This project is designed to help you learn Google Cloud Platform (GCP) 5 | in a fun way by building a self driving robot car that plays ball game 6 | against other robots. We provide all the instructions to build hardware and software. With 7 | this project you will learn how to use various GCP services: 8 | 9 | - IoT Core 10 | - TensorFlow Object Detection API 11 | - Pub/Sub 12 | - Cloud Storage 13 | - Compute Engine 14 | - App Engine 15 | - Cloud Functions 16 | - DialogFlow 17 | - Security 18 | - Networking 19 | - IAM 20 | 21 | There are two subsystems in this project: (1) car, and (2) cloud. 22 | Software running on the cloud controls movement of the car. 23 | In order to do it, several interconnected modules work together and 24 | interact with the software running on the car via GCP PubSub and IoT Core. 25 | We use Raspberry Pi based GoPiGo cars made by Dexter for this game with wide angle camera 26 | and laser sensor. 27 | 28 | Architecture diagrams, contact information, and build instructions can be found on the 29 | [Project website](https://www.cloudderby.io). -------------------------------------------------------------------------------- /setup/create-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ################################################################################# 4 | # Create car to cloud communication resources - this is to be used by Dialogflow 5 | ################################################################################# 6 | 7 | # 8 | # Copyright 2018 Google LLC 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # https://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | set -u # This prevents running the script if any of the variables have not been set 24 | set -e # Exit if error is detected during pipeline execution 25 | 26 | source ./setenv-global.sh 27 | 28 | create_resources -------------------------------------------------------------------------------- /cloud/ml/inference/vm-startup-script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ########################################################### 4 | # Script to be run when Inference VM restarts 5 | # This will start REST inference endpoint and web app 6 | ########################################################### 7 | 8 | # 9 | # Copyright 2018 Google LLC 10 | # 11 | # Licensed under the Apache License, Version 2.0 (the "License"); 12 | # you may not use this file except in compliance with the License. 13 | # You may obtain a copy of the License at 14 | # 15 | # https://www.apache.org/licenses/LICENSE-2.0 16 | # 17 | # Unless required by applicable law or agreed to in writing, software 18 | # distributed under the License is distributed on an "AS IS" BASIS, 19 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | # See the License for the specific language governing permissions and 21 | # limitations under the License. 22 | # 23 | 24 | ACCOUNT="${USER}" 25 | #ACCOUNT="" # For example "user1team1pa" 26 | cd /home/${ACCOUNT}/cloud-derby/src/cloud/ml/inference 27 | echo "$(date) - starting inference app..." >> tmp/boot.log 28 | sudo -u ${ACCOUNT} ./run.sh -------------------------------------------------------------------------------- /cloud/ml/inference/set-startup-script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ########################################################### 4 | # Configure inference VM to start python inference app n restart 5 | ########################################################### 6 | 7 | # 8 | # Copyright 2018 Google LLC 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # https://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | source ./setenv.sh 24 | 25 | set -u # This prevents running the script if any of the variables have not been set 26 | set -e # Exit if error is detected during pipeline execution 27 | 28 | gcloud compute instances add-metadata ${ML_VM} --metadata-from-file startup-script=vm-startup-script.sh -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google.com/conduct/). -------------------------------------------------------------------------------- /cloud/controller/js/vision-response.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | /************************************************************ 20 | This object is the response from the Vision.class and 21 | contains resulting data from Object Detection API 22 | ************************************************************/ 23 | module.exports = class VisionResponse { 24 | 25 | constructor() { 26 | // List of bounding boxes for objects found by Object Detection 27 | this.bBoxes = []; 28 | } 29 | 30 | addBox(boundingBox) { 31 | this.bBoxes.push(boundingBox); 32 | } 33 | }; -------------------------------------------------------------------------------- /cloud/controller/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloud-derby-driving-controller", 3 | "description": "Driving Controller is a Node.js app that makes driving decisions for Cloud Derby", 4 | "version": "1.0.2", 5 | "main": "app.js", 6 | "author": "Google Inc.", 7 | "license": "Apache-2.0", 8 | "engines": { 9 | "node": ">=10.0.0" 10 | }, 11 | "scripts": { 12 | "start": "node app.js", 13 | "deploy": "gcloud app deploy", 14 | "postinstall": "npm install @google-cloud/debug-agent", 15 | "test": "mocha 'tests/*_tests.js'" 16 | }, 17 | "dependencies": { 18 | "@google-cloud/debug-agent": "^4.0.3", 19 | "@google-cloud/pubsub": "^0.12.0", 20 | "@google-cloud/storage": "^3.3.0", 21 | "body-parser": "^1.19.0", 22 | "cors": "^2.8.5", 23 | "dotenv": "^8.1.0", 24 | "express": "^4.17.1", 25 | "pug": "^2.0.4", 26 | "request": "^2.88.0", 27 | "safe-buffer": "^5.2.0" 28 | }, 29 | "devDependencies": { 30 | "@google-cloud/nodejs-repo-tools": "^3.3.0", 31 | "ava": "^2.4.0", 32 | "chai": "^4.2.0", 33 | "mocha": "^6.2.0", 34 | "semistandard": "^14.2.0", 35 | "supertest": "^4.0.2", 36 | "uuid": "^3.3.3" 37 | }, 38 | "cloud-repo-tools": { 39 | "requiresProjectId": true, 40 | "requiresKeyFile": true 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /cloud/controller/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright 2018 Google LLC 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################### 20 | # Car Driving Controller Test 21 | ############################################### 22 | 23 | set -u # This prevents running the script if any of the variables have not been set 24 | set -e # Exit if error is detected during pipeline execution 25 | 26 | source ../../setenv-global.sh 27 | 28 | ############################################### 29 | # MAIN 30 | ############################################### 31 | print_header "Testing Driving Controller" 32 | 33 | mkdir -p tmp 34 | CWD=$(pwd) 35 | 36 | cd js 37 | npm test 38 | cd $CWD 39 | 40 | print_footer "Driving Controller test completed." -------------------------------------------------------------------------------- /third_party/tf-object-detection-sample/python/decorator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # Copyright 2018 Google LLC 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | import os 20 | from functools import wraps 21 | from flask import request 22 | from flask import Response 23 | 24 | USERNAME = os.environ['INFERENCE_USER_NAME'] 25 | PASSWORD = os.environ['INFERENCE_PASSWORD'] 26 | 27 | 28 | def check_auth(username, password): 29 | return username == USERNAME and password == PASSWORD 30 | 31 | 32 | def authenticate(): 33 | return Response( 34 | 'You have to login with proper credentials', 401, 35 | {'WWW-Authenticate': 'Basic realm="Login Required"'}) 36 | 37 | 38 | def requires_auth(f): 39 | @wraps(f) 40 | def decorated(*args, **kwargs): 41 | auth = request.authorization 42 | if not auth or not check_auth(auth.username, auth.password): 43 | return authenticate() 44 | return f(*args, **kwargs) 45 | return decorated 46 | -------------------------------------------------------------------------------- /cloud/ml/annotation/upload.bat: -------------------------------------------------------------------------------- 1 | :: 2 | :: Copyright 2018 Google LLC 3 | :: 4 | :: Licensed under the Apache License, Version 2.0 (the "License"); 5 | :: you may not use this file except in compliance with the License. 6 | :: You may obtain a copy of the License at 7 | :: 8 | :: https://www.apache.org/licenses/LICENSE-2.0 9 | :: 10 | :: Unless required by applicable law or agreed to in writing, software 11 | :: distributed under the License is distributed on an "AS IS" BASIS, 12 | :: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | :: See the License for the specific language governing permissions and 14 | :: limitations under the License. 15 | :: 16 | 17 | :: -------------------------------------------------------------------- 18 | :: This script uploads user created annotations into GCS for future merge 19 | :: with images from other users. 20 | :: -------------------------------------------------------------------- 21 | 22 | :: Put unique GCS bucket name here - must be the same for all team members 23 | set GCS_BUCKET=annotated-images--version- 24 | 25 | :: Put file name here - different for each team member - DO NOT include "zip" extention in the name... 26 | set ZIP_FILE=userXXX 27 | 28 | cd C:\a-robot-images 29 | 30 | :: --- Create archive with user provided annotations and images 31 | "c:\Program Files\7-Zip\7z.exe" a -tzip -r %ZIP_FILE% *.xml *.jpg 32 | 33 | :: --- Upload annotations and images to GCS for transferred learning 34 | call gsutil cp %ZIP_FILE%.zip gs://%GCS_BUCKET% 35 | 36 | del %ZIP_FILE%.zip 37 | 38 | cd C:\cloud-derby\cloud\ml\annotation -------------------------------------------------------------------------------- /cloud/ml/inference/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *.cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # Jupyter Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # SageMath parsed files 79 | *.sage.py 80 | 81 | # dotenv 82 | .env 83 | 84 | # virtualenv 85 | .venv 86 | venv/ 87 | ENV/ 88 | 89 | # Spyder project settings 90 | .spyderproject 91 | .spyproject 92 | 93 | # Rope project settings 94 | .ropeproject 95 | 96 | # mkdocs documentation 97 | /site 98 | 99 | # mypy 100 | .mypy_cache/ -------------------------------------------------------------------------------- /third_party/dexter/LICENSE.md: -------------------------------------------------------------------------------- 1 | License Information 2 | =================== 3 | 4 | Software License 5 | ---------------- 6 | 7 | **All code (including all scripts, firmware, drivers, examples, and any other 8 | software) is released under the [MIT License].** 9 | 10 | [MIT License]: http://choosealicense.com/licenses/mit/ 11 | 12 | MIT License 13 | 14 | Copyright (c) 2017 Dexter Industries 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy 17 | of this software and associated documentation files (the "Software"), to deal 18 | in the Software without restriction, including without limitation the rights 19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | copies of the Software, and to permit persons to whom the Software is 21 | furnished to do so, subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in all 24 | copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | SOFTWARE. 33 | 34 | 35 | Hardware License 36 | ---------------- 37 | 38 | **The hardware designs are released under the [Creative Commons Attribution 39 | NonCommercial ShareAlike 4.0 License][CC4].** 40 | 41 | [CC4]: https://creativecommons.org/licenses/by-nc-sa/4.0/ -------------------------------------------------------------------------------- /cloud/ml/training/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | .idea/ 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # dotenv 85 | .env 86 | 87 | # virtualenv 88 | .venv 89 | venv/ 90 | ENV/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | .spyproject 95 | 96 | # Rope project settings 97 | .ropeproject 98 | 99 | # mkdocs documentation 100 | /site 101 | 102 | # mypy 103 | .mypy_cache/ 104 | -------------------------------------------------------------------------------- /cloud/ml/training/setenv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################################################### 4 | # Shared environment variables for Transferred Learning module 5 | ############################################################### 6 | 7 | # 8 | # Copyright 2018 Google LLC 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # https://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | set -u # This prevents running the script if any of the variables have not been set 24 | set -e # Exit if error is detected during pipeline execution 25 | 26 | source ../../../setenv-global.sh 27 | source ../setenv-ml.sh 28 | 29 | ### Shall we run training locally on this VM or on the Google Cloud ML Engine? 30 | LOCAL_TRAINING=true 31 | 32 | ### What version of Google CMLE to use for remote training. Local training uses whatever TF you install 33 | CMLE_RUNTIME_VERSION=1.9 34 | 35 | ### What model to use for training. Model zoo: https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md 36 | MODEL=faster_rcnn_resnet101_coco_2018_01_28 37 | 38 | ### Which pre-trained model to use 39 | MODEL_CONFIG=${MODEL}-cloud-derby.config 40 | 41 | ### Which dataset to use 42 | MODEL_CONFIG_PATH=$(pwd) 43 | 44 | export TF_HTTP_PORT=8081 -------------------------------------------------------------------------------- /cloud/controller/js/bounding-box.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | /************************************************************ 20 | Outline of the object as found by object detection API 21 | ************************************************************/ 22 | module.exports = class BoundingBox { 23 | constructor(label, x, y, w, h, score) { 24 | // Label of the object - aka 'red_ball', 'home_base', 'border', 'obstacle', etc. 25 | this.label = label; 26 | // x coordinate - lower left (in pixels on the image) 27 | this.x = x; 28 | // y coordinate - lower left (in pixels on the image) 29 | this.y = y; 30 | // object width in pixels - to the right of the x (in pixels on the image) 31 | this.w = w; 32 | // object height in pixels - above of the y (in pixels on the image) 33 | this.h = h; 34 | // object detection probability - between 0 and 1 35 | this.score = score; 36 | } 37 | 38 | // Returns YY coordinate (top border of the object) 39 | yy() { 40 | return this.y + this.h; 41 | } 42 | 43 | // Returns XX coordinate (right border of the object) 44 | xx() { 45 | return this.x + this.w; 46 | } 47 | }; -------------------------------------------------------------------------------- /admin/ml-image-updates/sort.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################################### 4 | # Sort images into subfolders (after run.sh is completed) 5 | # additional images 6 | ############################################### 7 | # 8 | # Copyright 2018 Google LLC 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # https://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | 22 | set -u # This prevents running the script if any of the variables have not been set 23 | set -e # Exit if error is detected during pipeline execution 24 | # set -x # Print trace of commands after their arguments are expanded 25 | 26 | source ../../setenv-global.sh 27 | 28 | # This is the original bucket with images to be processed 29 | export SOURCE_BUCKET="derby-images-auto-sorted" 30 | # This is where all sorted images will be copied 31 | export DESTINATION_BUCKET="derby-images-sorted-by-score" 32 | 33 | ############################################### 34 | # MAIN 35 | ############################################### 36 | print_header "Starting image sort process by score" 37 | 38 | mkdir -p tmp 39 | 40 | # The service account is needed to get permissions to create resources 41 | gcloud auth activate-service-account --key-file=$SERVICE_ACCOUNT_SECRET 42 | 43 | cd js 44 | 45 | export GOOGLE_APPLICATION_CREDENTIALS=$SERVICE_ACCOUNT_SECRET 46 | nohup node sort.js & 47 | 48 | print_footer "Image sort by score has completed OK." -------------------------------------------------------------------------------- /car/simulator/driver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright 2018 Google LLC 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################### 20 | # Car driving receiver simulator 21 | ############################################### 22 | 23 | set -u # This prevents running the script if any of the variables have not been set 24 | set -e # Exit if error is detected during pipeline execution 25 | 26 | source ../../setenv-global.sh 27 | 28 | print_header "Driver simulator" 29 | 30 | TEST_COMMAND_SUBSCRIPTION=simulator_driving_command_subscription 31 | echo "Listen for messages from the driving controller..." 32 | 33 | if gcloud pubsub subscriptions list | grep $TEST_COMMAND_SUBSCRIPTION; then 34 | echo_my "Subscription '$TEST_COMMAND_SUBSCRIPTION' already exists for topic '$COMMAND_TOPIC'..." 35 | else 36 | echo_my "Creating a subscription '$TEST_COMMAND_SUBSCRIPTION' to topic '$COMMAND_TOPIC'..." 37 | gcloud pubsub subscriptions create $TEST_COMMAND_SUBSCRIPTION --topic $COMMAND_TOPIC | true 38 | fi 39 | 40 | MAX_MSGS=20 41 | 42 | RESULT="blah" 43 | while [ "$RESULT" != "" ]; do 44 | RESULT="$( gcloud beta pubsub subscriptions pull --auto-ack --limit=10 $TEST_COMMAND_SUBSCRIPTION )" 45 | echo "$RESULT" 46 | done 47 | 48 | # In some cases you may need to drop the subscription and re-create it 49 | # gcloud pubsub subscriptions delete $TEST_COMMAND_SUBSCRIPTION 50 | 51 | print_footer "Driver simulator completed - no more messages from the Driving Controller in the Subscription." 52 | -------------------------------------------------------------------------------- /admin/export-windows-vm-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright 2018 Google LLC 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################### 20 | # Export Windows VM annotation image into the GCS bucket so others can use it 21 | ############################################################################### 22 | 23 | set -u # This prevents running the script if any of the variables have not been set 24 | set -e # Exit if error is detected during pipeline execution 25 | 26 | source ./setenv.sh 27 | 28 | ### GCS Bucket destination for the export 29 | EXPORT_BUCKET=windows-vm-image-${ADMIN_PROJECT_ID} 30 | 31 | ### Name of the Windows Image to be exported 32 | WINDOWS_IMAGE_NAME="windows-image-labeling-image" 33 | 34 | ############################################################################### 35 | # MAIN 36 | ############################################################################### 37 | print_header "Windows Image export started." 38 | 39 | if gsutil ls -p ${ADMIN_PROJECT_ID} | grep ${EXPORT_BUCKET}; then 40 | echo_my "Bucket ${EXPORT_BUCKET} found OK" 41 | else 42 | echo_my "Create GCS bucket for backup: '${EXPORT_BUCKET}'..." 43 | gsutil mb -p ${ADMIN_PROJECT_ID} gs://${EXPORT_BUCKET} 44 | fi 45 | 46 | gcloud compute images export --destination-uri gs://${EXPORT_BUCKET}/${WINDOWS_IMAGE_NAME} --image ${WINDOWS_IMAGE_NAME} --project ${ADMIN_PROJECT_ID} 47 | 48 | print_footer "SUCCESS: Export is complete and can be found in the GCS bucket '$EXPORT_BUCKET'." -------------------------------------------------------------------------------- /admin/export-inference-vm-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright 2018 Google LLC 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################### 20 | # Export Windows VM annotation image into the GCS bucket so others can use it 21 | ############################################################################### 22 | 23 | set -u # This prevents running the script if any of the variables have not been set 24 | set -e # Exit if error is detected during pipeline execution 25 | 26 | source ./setenv.sh 27 | 28 | # GCS Bucket destination for the export 29 | EXPORT_BUCKET=inference-vm-image-${ADMIN_PROJECT_ID} 30 | 31 | # Name of the Windows Image to be exported 32 | IMAGE_NAME="inference-image" 33 | 34 | # Name of the source project with the VM image 35 | SOURCE_PROJECT="roman-test-oct9" 36 | 37 | ############################################################################### 38 | # MAIN 39 | ############################################################################### 40 | print_header "Inference Image export started." 41 | 42 | if gsutil ls -p ${ADMIN_PROJECT_ID} | grep ${EXPORT_BUCKET}; then 43 | echo_my "Bucket ${EXPORT_BUCKET} found OK" 44 | else 45 | echo_my "Create GCS bucket for backup: '${EXPORT_BUCKET}'..." 46 | gsutil mb -p ${ADMIN_PROJECT_ID} gs://${EXPORT_BUCKET} 47 | fi 48 | 49 | gcloud compute images export --destination-uri gs://${EXPORT_BUCKET}/${IMAGE_NAME} --image ${IMAGE_NAME} --project ${SOURCE_PROJECT} 50 | 51 | print_footer "SUCCESS: Export is complete and can be found in the GCS bucket '$EXPORT_BUCKET'." -------------------------------------------------------------------------------- /car/simulator/sensor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright 2018 Google LLC 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################### 20 | # Car sensor simulator 21 | # 22 | # Simulates laser and other sensors by publishing data into the well known topic 23 | # from which driving controller reads the data 24 | ############################################### 25 | 26 | set -u # This prevents running the script if any of the variables have not been set 27 | set -e # Exit if error is detected during pipeline execution 28 | 29 | source ../../setenv-global.sh 30 | print_header "Sensor simulator" 31 | 32 | TEMP_DATA=$(pwd)/tmp 33 | INSTALL_FLAG=$TEMP_DATA/install.marker # Location where the install flag is set to avoid repeated installs 34 | mkdir -p $TEMP_DATA 35 | 36 | if [[ ! -f "$INSTALL_FLAG" ]]; then 37 | install_node 38 | touch ${INSTALL_FLAG} 39 | fi 40 | 41 | create_gcs_camera_bucket 42 | 43 | if [ $# -eq 1 ]; then 44 | export TEST_IMAGE_FILE=$1 45 | else 46 | export TEST_IMAGE_FILE="" 47 | fi 48 | 49 | export GOOGLE_APPLICATION_CREDENTIALS=${SERVICE_ACCOUNT_SECRET} 50 | 51 | # Local directory with test images 52 | export TEST_IMAGE_FOLDER=simulation-images 53 | # Delay in seconds to send test messages 54 | export DELAY=1 55 | # How many test messages to send - do not forget to have proper number of test images, otherwise it will circle around and repeat many times 56 | export NUM_ITERATIONS=1 57 | 58 | echo "TEST_IMAGE_FILE='$TEST_IMAGE_FILE'" 59 | cd js 60 | npm start 61 | 62 | print_footer "Sensor simulator has completed sending test messages." -------------------------------------------------------------------------------- /admin/refresh-training-images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright 2018 Google LLC 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ########################################################################################## 20 | # This script updates annotated images for training in the Admin project 21 | ########################################################################################## 22 | set -u # This prevents running the script if any of the variables have not been set 23 | set -e # Exit if error is detected during pipeline execution 24 | 25 | ############################################# 26 | # MAIN 27 | ############################################# 28 | echo "Starting refresh of training images..." 29 | 30 | TMP="$(pwd)/tmp/image-refresh" 31 | if [ -d "$TMP" ]; then 32 | rm -rf $TMP 33 | fi 34 | 35 | mkdir -p $TMP 36 | cd $TMP 37 | 38 | echo "Download latest set of images that resulted from user merge.sh command..." 39 | gsutil cp gs://update-this-${PROJECT}-images-for-training-v-${VERSION}/* ./ 40 | 41 | unzip annotations.zip 42 | unzip images-for-training.zip 43 | rm annotations.zip 44 | rm images-for-training.zip 45 | 46 | echo "Create fresh set of training images..." 47 | zip -r provided-images * 48 | 49 | echo "Upload new set of training images to shared GCS..." 50 | gsutil cp provided-images.zip gs://$GCS_SOURCE_IMAGES 51 | 52 | rm provided-images.zip 53 | 54 | echo "Make a backup copy of training images..." 55 | NOW=$(date +%Y-%m-%d-%H-%M-%S) 56 | gsutil cp gs://$GCS_SOURCE_IMAGES/provided-images.zip gs://robot-derby-backup/${NOW}-provided-images.zip 57 | 58 | echo "Training image refresh complete." -------------------------------------------------------------------------------- /admin/ml-image-updates/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################################### 4 | # Process images for re-training to expand the dataset based on additional images 5 | ############################################### 6 | # 7 | # Copyright 2018 Google LLC 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # https://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | 21 | set -u # This prevents running the script if any of the variables have not been set 22 | set -e # Exit if error is detected during pipeline execution 23 | # set -x # Print trace of commands after their arguments are expanded 24 | 25 | source ../../setenv-global.sh 26 | 27 | # This is the original bucket with images to be processed 28 | #export CLOUD_BUCKET="robot-derby-backup" 29 | export CLOUD_BUCKET="images-crowdsourcing" 30 | # This is where all sorted images will be copied 31 | export DESTINATION_BUCKET="derby-images-auto-sorted" 32 | 33 | # This REST endpoint will be used for making inferences on images for classification purpose 34 | export INFERENCE_IP="104.197.196.4" 35 | 36 | ############################################### 37 | # MAIN 38 | ############################################### 39 | print_header "Starting image sort process" 40 | 41 | mkdir -p tmp 42 | CWD=$(pwd) 43 | # Location where the install flag is set to avoid repeated installs 44 | INSTALL_FLAG=$CWD/tmp/install.marker 45 | 46 | if [ -f "$INSTALL_FLAG" ]; then 47 | echo_my "File '$INSTALL_FLAG' was found = > no need to do the install since it already has been done." 48 | else 49 | install_node 50 | touch $INSTALL_FLAG 51 | fi 52 | 53 | # The service account is needed to get permissions to create resources 54 | gcloud auth activate-service-account --key-file=$SERVICE_ACCOUNT_SECRET 55 | 56 | cd $CWD/js 57 | 58 | #if [ -f "nohup.out" ] ; then 59 | # rm -rf nohup.out 60 | #fi 61 | 62 | export GOOGLE_APPLICATION_CREDENTIALS=$SERVICE_ACCOUNT_SECRET 63 | npm start 64 | 65 | print_footer "Image sort process has completed OK." -------------------------------------------------------------------------------- /cloud/controller/js/sensor-message.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | /************************************************************************** 20 | sensorMessage object 21 | - carId - unique ID of the car that sent this message 22 | - balls - how many balls this car has collected so far 23 | - obstacle - is there an obstacle or not (boolean) 24 | - battery - percentage of the battery left (0-100) 25 | - laserDistance - front laser measured distance to the nearest obstacle 26 | - cameraImgPath - GCS path (gs://) to the image file from the front camera 27 | - timestampMs - timestamp in ms when this message was generated by the car 28 | Example of a message: 29 | { "carId": 1, 30 | "timestampMs": 1519065026429, 31 | "carState": { 32 | "ballsCollected": 1, 33 | "batteryLeft": 80 34 | }, 35 | "sensors": { 36 | "frontLaserDistanceMm": 60, 37 | "frontCameraImagePath": "https://storage.googleapis.com/my-camera-1/images/image3.jpg" 38 | "frontCameraImagePathGCS": "gs://my-camera-1/images/image3.jpg" 39 | } 40 | } 41 | **************************************************************************/ 42 | module.exports = class SensorMessage { 43 | constructor(carId, balls, obstacle, battery, laserDistance, cameraImgPath, cameraImgPathGCS, color) { 44 | // Timestamp is generated at the time of creation of the message, not at the time of sending it 45 | this.timestampMs = new Date().getTime(); 46 | this.carId = carId; 47 | this.carState = {}; 48 | this.carState.obstacleFound = obstacle; 49 | this.carState.ballsCollected = balls; 50 | this.carState.color = color; 51 | this.carState.batteryLeft = battery; 52 | this.sensors = {}; 53 | this.sensors.frontLaserDistanceMm = laserDistance; 54 | this.sensors.frontCameraImagePath = cameraImgPath; 55 | this.sensors.frontCameraImagePathGCS = cameraImgPathGCS; 56 | } 57 | }; -------------------------------------------------------------------------------- /car/test_hardware.py: -------------------------------------------------------------------------------- 1 | # THIS SCRIPT WRITTEN FOR TESTING GOOGLE CLOUD DERBY HARDWARE 2 | # COPYRIGHT DEXTER INDUSTRIES, SEP 2019 3 | 4 | import gopigo3 5 | import time 6 | import easygopigo3 as easy 7 | import sys 8 | import atexit 9 | import urllib2 10 | import os.path 11 | 12 | 13 | gpg = easy.EasyGoPiGo3() 14 | atexit.register(gpg.stop) 15 | 16 | gpg.reset_all() 17 | 18 | def test_motors(): 19 | print("Warning: The robot is about to move forward. ") 20 | time.sleep(1) # let's give the reset_all() some time to finish 21 | gpg.set_speed(300) 22 | 23 | print("Motor Test: test motor power.") 24 | gpg.forward() 25 | time.sleep(0.25) 26 | gpg.backward() 27 | time.sleep(0.25) 28 | gpg.stop() 29 | print("Motor Test: end motor power tests.") 30 | 31 | print ("Both motors moving Forward with Dex Eyes On") 32 | gpg.open_eyes() 33 | gpg.drive_cm(5) 34 | print ("Both motors moving back with blinkers On") 35 | gpg.blinker_on(1) 36 | gpg.blinker_on(0) 37 | gpg.drive_cm(-5) 38 | print("Motor Test: Encoders are ok!") 39 | 40 | def test_dist_sensor(): 41 | my_distance_sensor = gpg.init_distance_sensor() 42 | # Directly print the values of the sensor. 43 | for i in range(0,5): 44 | print("Distance Sensor Reading (mm): " + str(my_distance_sensor.read_mm())) 45 | time.sleep(1) 46 | 47 | print("Distance sensor test complete!") 48 | 49 | def servo_test(): 50 | for i in range(1000, 2001): # count from 1000 to 2000 51 | gpg.set_servo(gpg.SERVO_1, i) 52 | gpg.set_servo(gpg.SERVO_2, 3000-i) 53 | time.sleep(0.001) 54 | for i in range(1000, 2001): # count from 1000 to 2000 55 | gpg.set_servo(gpg.SERVO_2, i) 56 | gpg.set_servo(gpg.SERVO_1, 3000-i) 57 | time.sleep(0.001) 58 | 59 | def test_camera(): 60 | print("Starting Camera Test Now.") 61 | # camera = PiCamera() 62 | from picamera import PiCamera 63 | from time import sleep 64 | 65 | camera = PiCamera() 66 | camera.start_preview() 67 | sleep(5) 68 | camera.capture('/home/pi/image.jpg') 69 | camera.stop_preview() 70 | fname = '/home/pi/image.jpg' 71 | if os.path.isfile(fname): 72 | print("Camera Worked OK!") 73 | else: 74 | print("ERROR WITH CAMERA CHECK!") 75 | 76 | 77 | def internet_on(): 78 | try: 79 | urllib2.urlopen('http://216.58.192.142', timeout=1) 80 | return True 81 | except urllib2.URLError as err: 82 | return False 83 | 84 | def test_internet(): 85 | print("Testing Internet!") 86 | if internet_on(): 87 | print("ON!") 88 | else: 89 | print("NOT ON!") 90 | 91 | 92 | test_camera() 93 | test_motors() 94 | test_dist_sensor() 95 | servo_test() 96 | test_internet() 97 | gpg.reset_all() # Clean it up, turn it all off. 98 | -------------------------------------------------------------------------------- /cloud/controller/js/settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | const process = require('process'); // Required for mocking environment variables 19 | 20 | /************************************************************ 21 | Settings for the car robot 22 | ************************************************************/ 23 | module.exports = { 24 | // How many balls need to be collected to win the game 25 | BALLS_NEEDED: 3, 26 | 27 | // Diameter of the ball 28 | BALL_SIZE_MM: 60.64, 29 | 30 | // Width of the Home Base sign (letter size is 216mm, but the letter is not printed on 100% of the paper) 31 | HOME_WIDTH_MM: 200, 32 | 33 | // Height of the Home Base sign (letter size is 280mm, but the letter is not printed on 100% of the paper) 34 | HOME_HEIGHT_MM: 250, 35 | 36 | // Various labels returned by vision API, such as - aka "red_ball" 37 | BALL_LABEL_SUFFIX: process.env.BALL_LABEL_SUFFIX, 38 | 39 | // Various labels returned by vision API, such as - aka "red_home" 40 | HOME_LABEL_SUFFIX: process.env.HOME_LABEL_SUFFIX, 41 | 42 | // Distance from the camera to the ball in a fully captured position - this is defined by the location of the 43 | // camera when it is mounted on the ball gripper 44 | MIN_DISTANCE_TO_CAMERA_MM: 21, 45 | 46 | // Max car speed (wheel rotation degrees per second) 47 | MAX_SPEED: 1000, 48 | 49 | // Here is the camera model used in the car: 50 | // https://www.amazon.com/gp/product/B00RMV53Z2 51 | camera: { 52 | 53 | // Horizontal field of view for the camera mounted on the car - degrees out of 360 54 | H_FIELD_OF_VIEW: 120.0, 55 | 56 | // Size of the camera sensor is 1/4 inch - see more details: https://en.wikipedia.org/wiki/Image_sensor_format 57 | SENSOR_HEIGHT_MM: 2.7, 58 | SENSOR_WIDTH_MM: 3.6, 59 | 60 | // Focal length of the camera - it is adjustable, so we need to calibrate it before using this camera for navigation 61 | FOCAL_LENGTH_MM: 2.594, 62 | 63 | // Horizontal resolution 64 | HORIZONTAL_RESOLUTION_PIXELS: process.env.HORIZONTAL_RESOLUTION_PIXELS, 65 | VERTICAL_RESOLUTION_PIXELS: process.env.VERTICAL_RESOLUTION_PIXELS 66 | } 67 | }; -------------------------------------------------------------------------------- /cloud/controller/js/tests/api_failure_tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var app = require('../app'); 18 | var chai = require('chai'); 19 | var request = require('supertest'); 20 | 21 | const BALL_COLORS = require('../validate').BALL_COLORS; 22 | const DRIVING_MODES = require('../validate').DRIVING_MODES; 23 | 24 | var expect = chai.expect; 25 | 26 | describe('API Failure Tests (/api)', function() { 27 | 28 | it('GET /unknown: should return 404', function(done) { 29 | request(app) 30 | .get('/api/unknown') 31 | .end(function(err, res) { 32 | expect(res.statusCode).to.be.equal(404); 33 | expect(res.headers).to.have.property('access-control-allow-origin'); 34 | done(); 35 | }); 36 | }); 37 | 38 | it('PUT /config: should return 400 for a bad payload', function(done) { 39 | request(app) 40 | .put('/api/config') 41 | .send({'abc': 123}) 42 | .end(function(err, res) { 43 | ///console.log(res); 44 | expect(res.body.success).to.be.false; 45 | expect(res.body.status).to.be.equal(400); 46 | expect(res.statusCode).to.be.equal(400); 47 | expect(res.headers).to.have.property('access-control-allow-origin'); 48 | done(); 49 | }); 50 | }); 51 | 52 | it('POST /messages/debug: should return 400 for a bad payload', function(done) { 53 | request(app) 54 | .post('/api/messages/debug') 55 | .send({'abc': 123}) 56 | .end(function(err, res) { 57 | expect(res.body.success).to.be.false; 58 | expect(res.body.status).to.be.equal(400); 59 | expect(res.statusCode).to.be.equal(400); 60 | expect(res.headers).to.have.property('access-control-allow-origin'); 61 | done(); 62 | }); 63 | }); 64 | 65 | it('POST /messages/driving: should return 400 for a bad payload', function(done) { 66 | request(app) 67 | .post('/api/messages/driving') 68 | .send({'abc': 123}) 69 | .end(function(err, res) { 70 | expect(res.body.success).to.be.false; 71 | expect(res.body.status).to.be.equal(400); 72 | expect(res.statusCode).to.be.equal(400); 73 | expect(res.headers).to.have.property('access-control-allow-origin'); 74 | done(); 75 | }); 76 | }); 77 | 78 | }); 79 | -------------------------------------------------------------------------------- /cloud/ml/setenv-ml.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################################################################# 4 | # Shared environment variables for Machine Learning Module 5 | ############################################################################# 6 | 7 | # 8 | # Copyright 2018 Google LLC 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # https://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | set -u # This prevents running the script if any of the variables have not been set 24 | set -e # Exit if error is detected during pipeline execution 25 | 26 | ### Name of the GCE VM that runs local ML training job and inference 27 | ML_VM=ml-training-inference-$VERSION 28 | 29 | ### Where to store all training data in flight for ML 30 | export GCS_ML_BUCKET=gs://${PROJECT}-ml-$VERSION 31 | 32 | ### Where to export the final inference graph for predictions 33 | export ML_EXPORT_BUCKET=gs://${PROJECT}-ml-export-$VERSION 34 | export FROZEN_INFERENCE_GRAPH_GCS=$ML_EXPORT_BUCKET/frozen_inference_graph.pb 35 | 36 | ### Where to export automatically generated label map - from training into predictions 37 | export LABEL_MAP=cloud_derby_label_map.pbtxt 38 | export LABEL_MAP_GCS=$ML_EXPORT_BUCKET/$LABEL_MAP 39 | 40 | ### Version of TensorFlow to use 41 | ### Also used as parameter for Cloud Machine Learning, see https://cloud.google.com/ml-engine/docs/tensorflow/runtime-version-list 42 | export TF_VERSION=1.10 43 | 44 | ### Model configuration 45 | # How many objects of the same class to be found in the image - Default is 100 46 | max_detections_per_class=90 47 | # How many total detections per image for all classes - Default is 300 48 | max_total_detections=250 49 | # Filter all objects with the confidence score lower than this 50 | score_threshold=0.0000001 51 | # How many proposals to have after the first stage - Default is 300 52 | first_stage_max_proposals=300 53 | 54 | ### TF settings 55 | export TF_PATH=~/tensorflow 56 | export TF_MODEL_DIR=$PROJECT_DIR/tensorflow-models 57 | export MODEL_BASE=$TF_MODEL_DIR/models/research 58 | export TMP=$(pwd)/tmp 59 | 60 | ################################################## 61 | # Setup Python path and check TF version 62 | ################################################## 63 | set_python_path() { 64 | echo_my "set_python_path()..." 65 | local CWD=$(pwd) 66 | cd $TF_MODEL_DIR/models/research 67 | export PYTHONPATH=$(pwd):$(pwd)/slim:$(pwd)/object_detection 68 | echo_my "PYTHONPATH=$PYTHONPATH" 69 | cd $CWD 70 | } 71 | -------------------------------------------------------------------------------- /cloud/ml/training/export-checkpoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ##################################################################################### 4 | # If your model failed for any reason or it is still training and has not reached the 5 | # final number of training steps, you can still export the checkpoint to experiment with it. 6 | # If the model is still training, you will need to create a separate VM to do the export 7 | # (same steps as you used to create this VM). In order to do the export, edit the script 8 | # export-checkpoint.sh and update the variable to the checkpoint number found in the GCS 9 | # bucket as shown above (the actual number will be different for your bucket): 10 | # export CHECKPOINT_NUMBER=1735 11 | ##################################################################################### 12 | 13 | # 14 | # Copyright 2018 Google LLC 15 | # 16 | # Licensed under the Apache License, Version 2.0 (the "License"); 17 | # you may not use this file except in compliance with the License. 18 | # You may obtain a copy of the License at 19 | # 20 | # https://www.apache.org/licenses/LICENSE-2.0 21 | # 22 | # Unless required by applicable law or agreed to in writing, software 23 | # distributed under the License is distributed on an "AS IS" BASIS, 24 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 | # See the License for the specific language governing permissions and 26 | # limitations under the License. 27 | # 28 | 29 | set -u # This prevents running the script if any of the variables have not been set 30 | set -e # Exit if error is detected during pipeline execution 31 | 32 | source ./setenv.sh 33 | 34 | CWD=$(pwd) 35 | TMP=$CWD/tmp 36 | 37 | ############################################# 38 | # MAIN 39 | ############################################# 40 | print_header "Exporting Frozen Inference Graph" 41 | 42 | set_python_path 43 | 44 | CHECKPOINT_NUMBER=$TRAINING_STEPS 45 | EXPORT_PATH=$TMP/export 46 | 47 | rm -rf ${EXPORT_PATH} | true # ignore if this does not exist yet 48 | mkdir -p ${EXPORT_PATH} 49 | cd $EXPORT_PATH 50 | 51 | gsutil cp ${GCS_ML_BUCKET}/train/model.ckpt-${CHECKPOINT_NUMBER}.* . 52 | 53 | echo_my "Processing checkpoint data..." 54 | python $TF_MODEL_DIR/models/research/object_detection/export_inference_graph.py \ 55 | --input_type image_tensor \ 56 | --pipeline_config_path $MODEL_CONFIG_PATH/${MODEL_CONFIG} \ 57 | --trained_checkpoint_prefix model.ckpt-${CHECKPOINT_NUMBER} \ 58 | --output_directory ./ 59 | 60 | echo_my "Prepare GCS bucket..." 61 | gsutil mb -l $REGION -c regional ${ML_EXPORT_BUCKET} | true # ignore if it is already there 62 | 63 | echo_my "Copy frozen inference graph to GCS '$FROZEN_INFERENCE_GRAPH_GCS' for reuse in Obj Detection API..." 64 | gsutil cp frozen_inference_graph.pb $FROZEN_INFERENCE_GRAPH_GCS 65 | 66 | echo_my "Copy label map to GCS '$LABEL_MAP_GCS' for reuse in Obj Detection API..." 67 | cd $TMP 68 | gsutil cp object_detection/annotations/$LABEL_MAP $LABEL_MAP_GCS 69 | 70 | print_footer "Inference Graph export has completed." -------------------------------------------------------------------------------- /third_party/tf-object-detection-sample/python/templates/upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 21 | 22 | Object Detection API 23 | 24 | 25 | 43 | 44 | 45 | 46 | 47 | {% from "_formhelpers.html" import render_field %} 48 |

Object Detection API

49 |

Upload a color photo file.

50 |
51 |
52 | {{ render_field(photo_form.input_photo) }} 53 |

54 |
55 |
56 | 57 |
58 | 59 |
60 | {% if result|length > 0 %} 61 |
original
62 |
63 |

64 | original 65 |
66 | {% for name, img in result.iteritems() %} 67 | {% if name != 'original' and name != 'response_msg' %} 68 | {{ name }} 70 |
71 | {% endif %} 72 | {% endfor %} 73 |

74 |
75 |

76 | {{ result['response_msg'] }} 77 |

78 |
79 | {% endif %} 80 |
81 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /setup/template-setenv-local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright 2018 Google LLC 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ################################################################################## 20 | # Environment settings that are unique to a development machine - these 21 | # do not get committed into repo and allow multi-user development. Example of this 22 | # can be found in "src/setup" folder. 23 | ################################################################################## 24 | 25 | set -u # This prevents running the script if any of the variables have not been set 26 | set -e # Exit if error is detected during pipeline execution 27 | 28 | echo "setenv-local.sh: start..." 29 | 30 | ### Automatically generate unique project ID for the first run and save it into a file. Later read it from file 31 | PROJECT_NAME_FILE="$PROJECT_DIR/project-id.sh" 32 | 33 | if [ -f "$PROJECT_NAME_FILE" ] ; then 34 | echo "Sourcing existing project file '$PROJECT_NAME_FILE'..." 35 | source $PROJECT_NAME_FILE 36 | else 37 | # Infer current project ID from the environment 38 | export PROJECT=$(gcloud info | grep "Project:" | sed -n -e "s/Project: \[//p" | sed -n -e "s/\]//p") 39 | fi 40 | 41 | echo "PROJECT='$PROJECT'" 42 | gcloud config set project "$PROJECT" 43 | 44 | ### Serial number of the car to distinguish it from all other cars possibly on the same project 45 | export CAR_ID=2 46 | 47 | ### How many training steps to take during TensorFlow model training 48 | TRAINING_STEPS=8000 49 | 50 | ### This controls which inference VM REST API will be used by the Driving Controller 51 | # true - use existing inference VM from the DEMO project 52 | # false - use inference VM within the same project as Driving Controller 53 | USE_DEMO_INFERENCE="false" 54 | 55 | ### Where do we want Driving Controller to be deployed? (current VM or App Engine) 56 | # true - deploy in local VM 57 | # false - deploy in App Engine 58 | DRIVING_CONTROLLER_LOCAL="true" 59 | 60 | ### This controls certain automated tasks and allows the script to create resources on behalf of the user 61 | FOUR_HOURS_HACKATHON="false" 62 | 63 | if $FOUR_HOURS_HACKATHON ; then 64 | AUTO_CREATE_IP="true" 65 | AUTO_CREATE_FIREWALL="true" 66 | SKIP_MANUAL_IMAGE_ANNOTATION="true" 67 | fi 68 | 69 | ### Used for multiple ML models to be deployed and compared against each other (this is added to VM names, IP names, GCS bucket names, etc.) 70 | export VERSION=50 71 | 72 | ### These are Region and Zone where you want to run your car controller - feel free to change as you see fit 73 | export REGION="us-central1" 74 | export ZONE="us-central1-f" 75 | export REGION_LEGACY="us-central" # there are corner cases where gcloud still references the legacy nomenclature 76 | 77 | echo "setenv-local.sh: done" -------------------------------------------------------------------------------- /cloud/ml/inference/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ################################################## 4 | # Run Object Detection based on the model trained earlier by "transferred-learning" 5 | # Based on https://github.com/GoogleCloudPlatform/tensorflow-object-detection-example 6 | # 7 | # This code will run on a special GCE VM $ML_VM 8 | ################################################## 9 | 10 | # 11 | # Copyright 2018 Google LLC 12 | # 13 | # Licensed under the Apache License, Version 2.0 (the "License"); 14 | # you may not use this file except in compliance with the License. 15 | # You may obtain a copy of the License at 16 | # 17 | # https://www.apache.org/licenses/LICENSE-2.0 18 | # 19 | # Unless required by applicable law or agreed to in writing, software 20 | # distributed under the License is distributed on an "AS IS" BASIS, 21 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | # See the License for the specific language governing permissions and 23 | # limitations under the License. 24 | # 25 | 26 | set -u # This prevents running the script if any of the variables have not been set 27 | set -e # Exit if error is detected during pipeline execution 28 | 29 | source ../../../setenv-global.sh 30 | source ../setenv-ml.sh 31 | 32 | export PATH_TO_LABELS=$MODEL_BASE/object_detection/data/$LABEL_MAP 33 | export PATH_TO_CKPT=$PROJECT_DIR/checkpoint_graph_def 34 | 35 | ################################################## 36 | # This changes TF inference model to be used for web app 37 | ################################################## 38 | set_inference_model() { 39 | echo_my "Changing inference model..." 40 | 41 | # Point to the trained model 42 | FROM=$CWD/tmp/import 43 | mkdir -p $FROM 44 | 45 | echo_my "Download label map..." 46 | gsutil cp $LABEL_MAP_GCS $FROM/$LABEL_MAP 47 | PATH_TO_LABELS=${MODEL_BASE}/object_detection/data/$LABEL_MAP 48 | sudo ln -sf $FROM/$LABEL_MAP $PATH_TO_LABELS 49 | echo_my "Label map is setup at '$PATH_TO_LABELS'" 50 | 51 | echo_my "Download frozen inference graph from GCS '$FROZEN_INFERENCE_GRAPH_GCS'..." 52 | gsutil cp $FROZEN_INFERENCE_GRAPH_GCS $FROM/frozen_inference_graph.pb 53 | 54 | local DESTINATION_GRAPH=$PATH_TO_CKPT/frozen_inference_graph.pb 55 | if ! [ -d $PATH_TO_CKPT ] ; then 56 | mkdir $PATH_TO_CKPT 57 | fi 58 | sudo ln -sf $FROM/frozen_inference_graph.pb $DESTINATION_GRAPH 59 | echo_my "Frozen inference graph is setup at '$DESTINATION_GRAPH'" 60 | } 61 | 62 | ############################################### 63 | # MAIN 64 | ############################################### 65 | print_header "Starting Object Detection App..." 66 | 67 | mkdir -p tmp 68 | CWD=$(pwd) 69 | INSTALL_FLAG=$CWD/tmp/install.marker 70 | 71 | if [ -f "$INSTALL_FLAG" ]; then 72 | echo_my "File '$INSTALL_FLAG' was found = > no need to do the install since it already has been done." 73 | else 74 | set_inference_model 75 | echo_my "Setting up web app..." 76 | sudo pip install --upgrade Flask==0.12.2 WTForms==2.1 Flask_WTF==0.14.2 Werkzeug==0.12.2 numpy google-cloud-storage 77 | touch $INSTALL_FLAG 78 | fi 79 | 80 | set_python_path 81 | cd $CWD/python 82 | 83 | if [ -f "*.jpg" ] ; then 84 | # Remove old jpg files 85 | rm *.jpg 86 | fi 87 | 88 | if [ -f "nohup.out" ] ; then 89 | rm -rf nohup.out 90 | fi 91 | 92 | echo_my "Running webapp: Change USERNAME and PASSWORD in decorator.py..." 93 | # -u disables line buffering in python and shows everything in the nohup.out 94 | nohup python -u ./app.py & 95 | 96 | tail -f nohup.out 97 | 98 | echo_my "To watch output of the app log, use command: " 99 | -------------------------------------------------------------------------------- /admin/README.md: -------------------------------------------------------------------------------- 1 | Cloud Derby Setup 2 | ===== 3 | 4 | Scripts in the `admin` folder are used to setup and run Cloud Derby event in your 5 | own GCP organization or inside of the cloudderby.io. 6 | 7 | ## Create new environment to run a public hackathon 8 | 9 | The event settings can be updated in the [setenv.sh](setenv.sh) file: 10 | 11 | - `NUM_TEAMS` - How many teams will participate in the workshop (between 1 and N). 12 | - `NUM_PEOPLE_PER_TEAM` - How many people per team (usually between 1 and 6). 13 | - `EVENT_NAME` - Name of the event (this is added to user and group names, so keep it short, such as "PIT", or "DC"). 14 | - `TOP_FOLDER` - Folder that holds all project sub-folders for users (use date of the event, such as "March-11-$EVENT_NAME"). 15 | - `DOMAIN` - Domain name (the org name - whatever your org name is, such as "cloudderby.io", or "acme.com"). 16 | 17 | In order to host Cloud Derby hackathon as a public event you need to run a hackathon setup script [create-hackathon.sh](create-hackathon.sh), which will do the following: 18 | 19 | - Generate user accounts and associated groups for teams. 20 | - Generate event folder and sub-folders for each team in the IAM structure. 21 | - Grant permissions to teams to their own folders. 22 | - Add groups to proper IAM policies to allow access to source repository and GCS bucket with annotated images. 23 | 24 | In order to run the script, you need to have GCloud SDK installed in Debian or Ubuntu bash command line and run the following: 25 | 26 | - `gcloud init` - this will initialize your admin credentials for the GCP Org. 27 | - `./create-hackathon.sh` - this will create the environment for you. 28 | 29 | After the completion of the script, you will have all users, groups and folders in the IAM structure and a file called users.csv in the `$HOME/cloud-derby/admin/tmp` subfolder. 30 | 31 | ## Collect user images after the hackathon 32 | 33 | During the hackathon your users will be taking photos and running robots around the room with cameras. This creates a 34 | great number of new images that can be used in subsequent training in future events to enhance model accuracy. In order 35 | to capture those user images you can use [collect-images.sh](collect-images.sh) script. This script will scan all user 36 | created folders, projects and buckets and collect all images into a single bucket under the "Administration" project. 37 | 38 | In order to use those images, human being needs to: 39 | - Download those images from one buckets 40 | - Remove repetitive images 41 | - Organize images in a proper structure (see ["Annotation"](https://bit.ly/robotderby) section of the tutorial) 42 | - Annotate said images 43 | - Run training and check model accuracy against the previous model (same steps as in "ML Training" part of the tutorial) 44 | - If the step above improves the accuracy, then merge annotated images with the base set 45 | - Remove collected images from the central bucket to prepare for future events 46 | 47 | ## Cleanup after the hackathon 48 | 49 | After you host the event for your audience, you want to make sure that all users and groups are promptly removed from the IAM and all resources (VMs, buckets and projects) 50 | are deleted to avoid unncecessary charges. This cleanup process is fully automated. Here is what you need to do from the bash command line in your Debian or Ubuntu: 51 | 52 | - `gcloud init` - this will initialize your admin credentials for the GCP Org. 53 | - Verify that the settings in [setenv.sh](setenv.sh) match your environment (aka name of the event, number 54 | of teams and users). 55 | - `./delete-hackathon.sh $FOLDER_ID` - this will erase users, groups, all folders, VMs, projects, etc. under the `$FOLDER_ID` the folder you created while generating new hackathon event. 56 | for the event (for example "March-11-Denver" mapped into folder ID "123456789"). 57 | - If you do not want to remove users, groups and projects, you can run a script `./stop-vms.sh $FOLDER_ID` to stop all of the VMs in all nested sub-folders and projects under `$FOLDER_ID`. 58 | -------------------------------------------------------------------------------- /admin/backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright 2018 Google LLC 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################### 20 | # Make a backup copy of all critical data intofrom the Org Domain a separate 21 | # account in case the Org get compromized and / or removed 22 | ############################################################################### 23 | 24 | set -u # This prevents running the script if any of the variables have not been set 25 | set -e # Exit if error is detected during pipeline execution 26 | 27 | source ./setenv.sh 28 | 29 | ### Directory for temp data 30 | TMP="tmp" 31 | 32 | ### Service account key for backup 33 | BACKUP_SERVICE_ACCOUNT_SECRET="${PROJECT_DIR}/.secrets/backup-service-account-secret.json" 34 | 35 | ############################################################################### 36 | # Prepare backup destinations 37 | ############################################################################### 38 | setup() { 39 | echo_my "Setup backup destinations..." 40 | 41 | if gsutil ls -p ${BACKUP_PROJECT_ID} | grep ${BACKUP_BUCKET}; then 42 | echo_my "Bucket ${BACKUP_BUCKET} found OK" 43 | else 44 | echo_my "Create GCS bucket for backup: '${BACKUP_BUCKET}'..." 45 | gsutil mb -l eu -c coldline -p ${BACKUP_PROJECT_ID} gs://${BACKUP_BUCKET}/ 46 | fi 47 | } 48 | 49 | ############################################################################### 50 | # Make a copy of annotated images 51 | ############################################################################### 52 | backup_images() { 53 | local FOLDER=${BACKUP_BUCKET}/$NOW/images 54 | echo_my "Making a copy of annotated images from project '${ADMIN_PROJECT_ID}' bucket '${GCS_SOURCE_IMAGES}' into project '${BACKUP_PROJECT_ID}' bucket '${FOLDER}'" 55 | 56 | gsutil cp gs://${GCS_SOURCE_IMAGES}/* gs://${FOLDER}/ 57 | } 58 | 59 | ############################################################################### 60 | # Make a copy of a git repo as plain file structure 61 | ############################################################################### 62 | backup_source() { 63 | local FOLDER=${BACKUP_BUCKET}/${NOW} 64 | echo_my "Making a copy of source files from project '${ADMIN_PROJECT_ID}' into project '${BACKUP_PROJECT_ID}' bucket '${FOLDER}'" 65 | 66 | local CWD=$(pwd) 67 | local TMP=${HOME}/tmp/repo 68 | rm -rf "$TMP" | true # ignore if does not exist 69 | mkdir -p $TMP 70 | 71 | # Clone the repo into a temp directory 72 | git clone https://github.com/GoogleCloudPlatform/cloud-derby $TMP/cloud-derby-source 73 | 74 | cd $TMP/cloud-derby-source 75 | 76 | # We only need to save source files, not the large amount of git metadata 77 | rm -rf .git 78 | zip -r source . 79 | 80 | gsutil cp source.zip gs://${FOLDER}/ 81 | 82 | # Free up space 83 | cd $CWD 84 | rm -rf $TMP 85 | } 86 | 87 | ############################################################################### 88 | # MAIN 89 | ############################################################################### 90 | if [ -z ${1+x} ] ; then 91 | echo_my "BACKUP_PROJECT_ID not found. \n Usage: backup.sh [BACKUP_PROJECT_ID] \n Example: \n ./backup.sh cloud-derby-backup \n \n" $ECHO_ERROR 92 | exit 1 93 | fi 94 | 95 | BACKUP_PROJECT_ID=$1 96 | # Where to store backup data 97 | BACKUP_BUCKET="${BACKUP_PROJECT_ID}-bucket" 98 | 99 | NOW=$(date +%Y-%m-%d-%H-%M) 100 | print_header "Project backup started at $NOW." 101 | 102 | echo "Activating service account '${BACKUP_SERVICE_ACCOUNT_SECRET}'..." 103 | gcloud auth activate-service-account --key-file=${BACKUP_SERVICE_ACCOUNT_SECRET} 104 | 105 | setup 106 | 107 | backup_images 108 | 109 | backup_source 110 | 111 | print_footer "SUCCESS: Project backup complete." -------------------------------------------------------------------------------- /cloud/controller/js/manual-driving.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | const DriveMessage = require('./drive-message').DriveMessage; 19 | 20 | /************************************************************ 21 | Display Manual Driving form on Get 22 | Input: 23 | - inboundMsgHistory 24 | ************************************************************/ 25 | module.exports.manualDrivingForm = function (inboundMsgHistory) { 26 | let imageUrl; 27 | 28 | if (inboundMsgHistory.length > 0) { 29 | let msg = inboundMsgHistory[inboundMsgHistory.length - 1]; 30 | if ((!(msg.data === undefined)) && (!(JSON.parse(msg.data).sensors === undefined)) && (!(JSON.parse(msg.data).sensors.frontCameraImagePath === undefined))) { 31 | imageUrl = JSON.parse(msg.data).sensors.frontCameraImagePath; 32 | } 33 | } 34 | 35 | let form = `Home / Refresh page 36 |

Manual car control

37 |
38 | 39 | (wheel angle/sec) - from 1 to 1000 40 |
41 | 42 | (degrees) - positive for right, negative for left 43 |
44 | 45 | (wheel angle/sec) - from 1 to 1000 46 |
47 | 48 | (mm) - positive for forward, negative for backward 49 |
50 | 51 | 52 |
53 | 54 | 55 |
56 | 57 | 58 |
59 | 60 | 61 |

62 | 63 |
`; 64 | 65 | // Add an image to the form 66 | if (!(imageUrl === undefined)) { 67 | form = form + 'picture of the ball'; 68 | } 69 | 70 | return form; 71 | }; 72 | 73 | /************************************************************ 74 | Send manual driving command to the car 75 | ************************************************************/ 76 | module.exports.manualCommand = function (req) { 77 | let command = new DriveMessage(); 78 | 79 | command.setModeManual(); 80 | 81 | if (req.body.turn_speed_field) { 82 | command.setSpeed(req.body.turn_speed_field); 83 | } 84 | 85 | if (req.body.angle_field) { 86 | command.makeTurn(req.body.angle_field); 87 | } 88 | 89 | if (req.body.drive_speed_field) { 90 | command.setSpeed(req.body.drive_speed_field); 91 | } 92 | 93 | if (req.body.distance_field) { 94 | command.drive(req.body.distance_field); 95 | } 96 | 97 | if (req.body.ondemand_messages) { 98 | command.setOnDemandSensorRate(); 99 | command.takePhoto(); 100 | } 101 | 102 | if (req.body.gripper_open) { 103 | command.gripperOpen(); 104 | } 105 | 106 | if (req.body.gripper_close) { 107 | command.gripperClose(); 108 | } 109 | 110 | if (req.body.nonstop_messages) { 111 | command.setContinuousSensorRate(); 112 | command.takePhoto(); 113 | } 114 | 115 | command.sendSensorMessage(); 116 | 117 | return command; 118 | }; -------------------------------------------------------------------------------- /cloud/controller/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################################################### 4 | # Car Driving Controller 5 | # Can be run locally as node process or deployed into GAE 6 | ############################################################### 7 | 8 | # 9 | # Copyright 2018 Google LLC 10 | # 11 | # Licensed under the Apache License, Version 2.0 (the "License"); 12 | # you may not use this file except in compliance with the License. 13 | # You may obtain a copy of the License at 14 | # 15 | # https://www.apache.org/licenses/LICENSE-2.0 16 | # 17 | # Unless required by applicable law or agreed to in writing, software 18 | # distributed under the License is distributed on an "AS IS" BASIS, 19 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | # See the License for the specific language governing permissions and 21 | # limitations under the License. 22 | 23 | set -u # This prevents running the script if any of the variables have not been set 24 | set -e # Exit if error is detected during pipeline execution 25 | # set -x # Print trace of commands after their arguments are expanded 26 | 27 | source ../../setenv-global.sh 28 | 29 | TMP="$(pwd)/tmp" 30 | mkdir -p ${TMP} 31 | 32 | ### Defines the name of the App Engine Flex App and forms part of URL 33 | APP_NAME=driving-controller 34 | 35 | ### Configuration of the deployment for App Engine 36 | YAML_FILE=$(pwd)/js/app-generated.yml 37 | 38 | ############################################### 39 | # This generates proper YAML connfig for the app 40 | ############################################### 41 | generate_yaml() 42 | { 43 | echo_my "Generating YAML config..." 44 | # Create 'app.yaml' file for the deployment configuration 45 | cat << EOF > $YAML_FILE 46 | # This file is auto-generated - DO NOT edit it manually as it will be overriden 47 | # Docs: https://cloud.google.com/appengine/docs/standard/nodejs/config/appref 48 | 49 | # App Engine Standard 50 | runtime: nodejs10 51 | 52 | # App Engine Flex 53 | # runtime: nodejs 54 | 55 | # This makes it run in App Engine Flex 56 | # env: flex 57 | 58 | manual_scaling: 59 | instances: 1 60 | 61 | env_variables: 62 | SENSOR_SUBSCRIPTION: $SENSOR_SUBSCRIPTION 63 | COMMAND_TOPIC: $COMMAND_TOPIC 64 | INFERENCE_USER_NAME: $INFERENCE_USER_NAME 65 | INFERENCE_PASSWORD: $INFERENCE_PASSWORD 66 | INFERENCE_IP: $INFERENCE_IP 67 | INFERENCE_URL: $INFERENCE_URL 68 | HTTP_PORT: $HTTP_PORT 69 | CAR_ID: $CAR_ID 70 | BALL_LABEL_SUFFIX: $BALL_LABEL_SUFFIX 71 | HOME_LABEL_SUFFIX: $HOME_LABEL_SUFFIX 72 | BLUE_BALL_LABEL: $BLUE_BALL_LABEL 73 | RED_BALL_LABEL: $RED_BALL_LABEL 74 | YELLOW_BALL_LABEL: $YELLOW_BALL_LABEL 75 | GREEN_BALL_LABEL: $GREEN_BALL_LABEL 76 | BLUE_HOME_LABEL: $BLUE_HOME_LABEL 77 | RED_HOME_LABEL: $RED_HOME_LABEL 78 | YELLOW_HOME_LABEL: $YELLOW_HOME_LABEL 79 | GREEN_HOME_LABEL: $GREEN_HOME_LABEL 80 | EOF 81 | } 82 | 83 | ############################################### 84 | # MAIN 85 | ############################################### 86 | print_header "Starting application '$APP_NAME'" 87 | 88 | CWD=$(pwd) 89 | # Location where the install flag is set to avoid repeated installs 90 | INSTALL_FLAG=${TMP}/install.marker 91 | 92 | if [ -f "$INSTALL_FLAG" ]; then 93 | echo_my "File '$INSTALL_FLAG' was found = > no need to do the install since it already has been done." 94 | else 95 | install_node 96 | touch $INSTALL_FLAG 97 | fi 98 | 99 | create_resources 100 | 101 | # Lookup actual IP address for inference VM from the static reference 102 | if $USE_DEMO_INFERENCE ; then 103 | # Driving controller will be using the inference VM that has been stood up in advance in a different project 104 | export INFERENCE_IP=$(gcloud compute addresses describe $DEMO_INFERENCE_IP_NAME --region us-central1 --format="value(address)" --project $DEMO_PROJECT) 105 | else 106 | # Find the IP of the inference VM that was created in this project 107 | export INFERENCE_IP=$(gcloud compute addresses describe $ML_IP_NAME --region $REGION --format="value(address)") 108 | fi 109 | echo_my "INFERENCE_IP=$INFERENCE_IP" 110 | 111 | cd $CWD/js 112 | 113 | if $DRIVING_CONTROLLER_LOCAL ; 114 | then 115 | echo_my "DRIVING_CONTROLLER_LOCAL='$DRIVING_CONTROLLER_LOCAL' (set it to false to deploy on GCP) - running on local machine (use this for test and dev only)..." 116 | # The default credentials below are needed for the controller to run locally in unix or mac dev environment when deployed locally 117 | export GOOGLE_APPLICATION_CREDENTIALS=$SERVICE_ACCOUNT_SECRET 118 | npm start 119 | else 120 | generate_yaml 121 | URL=https://${APP_NAME}-dot-${PROJECT}.appspot.com/ 122 | echo_my "Deploying into Google App Engine using YAML file '${YAML_FILE}'. Current directory is '$(pwd)'..." 123 | yes | gcloud app deploy "${YAML_FILE}" --project ${PROJECT} 124 | # Ping the app to see if it is available 125 | curl -G "${URL}" 126 | echo_my "Running on GCP URL=$URL" 127 | fi 128 | 129 | print_footer "Driving Controller has been started." 130 | -------------------------------------------------------------------------------- /admin/setenv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright 2018 Google LLC 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ################################################################################## 20 | # Environment settings that are to be kept secret and not exposed to the GitHub repo 21 | ################################################################################## 22 | 23 | set -u # This prevents running the script if any of the variables have not been set 24 | set -e # Exit if error is detected during pipeline execution 25 | 26 | echo "setenv.sh: start..." 27 | 28 | source ../setenv-global.sh 29 | 30 | ### Billing accound ID used to pay for project resources 31 | BILLING_ACCOUNT_ID="" 32 | 33 | ### How many teams will participate in the workshop 34 | NUM_TEAMS=3 35 | 36 | # In case we need to add extra teams - start with some number, otherwise set it to 1 37 | TEAM_START_NUM=1 38 | 39 | ### How many people per team 40 | NUM_PEOPLE_PER_TEAM=2 41 | 42 | ### Name of the event - to be added to user and group names 43 | EVENT_NAME="preGA" 44 | 45 | ### Folder that holds all project sub-folders for users 46 | TOP_FOLDER="Oct-1-$EVENT_NAME" 47 | 48 | ### Directory for temp data 49 | TMP="tmp" 50 | 51 | ### Group that has all users and allows read only access to a whole bunch of shared resources 52 | ADMIN_READ_GROUP="read-only-group@$DOMAIN" 53 | 54 | ### GAM is a wonderful OSS tool to manage Users and Groups 55 | GAM="/home/${USER}/bin/gam/gam" 56 | 57 | ############################################################################### 58 | # Generate team name 59 | # Input: 60 | # 1 - team number 61 | ############################################################################### 62 | team_name() { 63 | echo "team${1}${EVENT_NAME}" 64 | } 65 | 66 | ############################################################################### 67 | # Generate user name 68 | # Input: 69 | # 1 - user number 70 | # 2 - team number 71 | ############################################################################### 72 | user_name() { 73 | echo "user${1}team${2}${EVENT_NAME}" 74 | } 75 | 76 | ############################################################################### 77 | # Generate name for team folder given team number 78 | # Input: 79 | # 1 - team # 80 | ############################################################################### 81 | team_folder_name() { 82 | echo "Team-${1}-resources" 83 | } 84 | 85 | ############################################################################### 86 | # Generate random password 87 | ############################################################################### 88 | generate_password() { 89 | local PASSWORD_LENGTH=10 90 | echo $(gpw 1 $PASSWORD_LENGTH) 91 | } 92 | 93 | ############################################################################### 94 | # Check prereqs and do install 95 | ############################################################################### 96 | setup() { 97 | mkdir -p $TMP 98 | INSTALL_FLAG=$TMP/install.marker 99 | 100 | if [ -f "$INSTALL_FLAG" ]; then 101 | echo_my "Marker file '$INSTALL_FLAG' was found = > no need to do the install." 102 | else 103 | echo_my "Marker file '$INSTALL_FLAG' was NOT found = > starting one time install." 104 | # Password generator 105 | sudo apt-get install gpw 106 | # GAM is an awesome GSuite management OSS tool: https://github.com/jay0lee/GAM/wiki 107 | bash <(curl -s -S -L https://git.io/install-gam) 108 | touch $INSTALL_FLAG 109 | fi 110 | } 111 | 112 | ############################################################################### 113 | # Find folder ID given its display name 114 | # Input: 115 | # 1 - folder display name 116 | ############################################################################### 117 | find_top_folder_id() { 118 | echo $(gcloud alpha resource-manager folders list --organization=$(lookup_org_id) \ 119 | --filter=" displayName=$1" | grep $1 | sed -n -e "s/.* //p") 120 | } 121 | 122 | ############################################################################### 123 | # Find folder ID given its display name 124 | # Input: 125 | # 1 - folder display name 126 | # 2 - parent folder ID 127 | ############################################################################### 128 | find_folder_id() { 129 | echo $(gcloud alpha resource-manager folders list --folder=$2 \ 130 | --filter=" displayName=$1" | grep $1 | sed -n -e "s/.* //p") 131 | } 132 | 133 | echo "setenv.sh: done" 134 | -------------------------------------------------------------------------------- /admin/ml-image-updates/js/sort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | console.log(`***Image sorting by score is starting up***`); 18 | 19 | // Imports 20 | const process = require('process'); // Required for mocking environment variables 21 | const request = require('request'); 22 | 23 | const Storage = require('@google-cloud/storage'); 24 | const storage = new Storage(process.env.PROJECT); 25 | 26 | const DESTINATION_BUCKET = process.env.DESTINATION_BUCKET; 27 | const SOURCE_BUCKET = process.env.SOURCE_BUCKET; 28 | 29 | // Global counter of processed files 30 | let progressCount = 0; 31 | let successCount = 0; 32 | 33 | /************************************************************ 34 | Process one file 35 | Input: 36 | - Source File name 37 | Output: 38 | - New file name 39 | ************************************************************/ 40 | function createNewName(fileName) { 41 | // Example input file name: 'count_10_high_7232_low_0522_file_4306.jpg' 42 | const keyword = '_low_'; 43 | 44 | // If file name does not contain '_low_' keyword, then simply return the same name 45 | const keywordIndex = fileName.indexOf(keyword); 46 | if (keywordIndex < 0) { 47 | return fileName; 48 | } 49 | 50 | const keywordLength = keyword.length; 51 | const lowScoreFirstDigit = fileName[keywordIndex + keywordLength]; 52 | const lastSlashPosition = fileName.lastIndexOf('/'); 53 | return fileName.substring(0, lastSlashPosition + 1) + lowScoreFirstDigit + '/' + fileName.slice(lastSlashPosition + 1) 54 | } 55 | 56 | /************************************************************ 57 | Copy file from one bucket into another 58 | Input: 59 | - source GCS URI 60 | - destination GCS URI 61 | ************************************************************/ 62 | async function gcsCopy(srcBucket, srcFile, destBucket, destFile) { 63 | // console.log('Copy from to '); 64 | await storage.bucket(srcBucket).file(srcFile).copy(storage.bucket(destBucket).file(destFile)) 65 | .catch(function (error) { 66 | console.error('!!!!!!!!!!!!! ERROR: Failed to copy a file: ' + destFile + ' with error: ' + error); 67 | }); 68 | } 69 | 70 | /************************************************************ 71 | Delete file from the bucket 72 | Input: 73 | - source GCS URI 74 | ************************************************************/ 75 | async function gcsDelete(bucket, file) { 76 | // console.log('Deleting file: '+file); 77 | storage.bucket(bucket).file(file).delete() 78 | .catch(function (error) { 79 | console.error("!!!!!!!!!!!! Failed to delete a file: " + error); 80 | }); 81 | } 82 | 83 | /************************************************************ 84 | Recursively process list of files 85 | Input: 86 | - List of files to be processed 87 | Output: 88 | - None 89 | ************************************************************/ 90 | async function processFilesAsync(files) { 91 | for (let file of files) { 92 | console.log('#' + progressCount); 93 | progressCount++; 94 | let newName = createNewName(file.name); 95 | 96 | // TODO - this needs to be async, but in batches so as to not overflow the memory for 80,000+ files 97 | await gcsCopy(SOURCE_BUCKET, file.name, DESTINATION_BUCKET, newName) 98 | .then(() => { 99 | gcsDelete(SOURCE_BUCKET, file.name); 100 | console.log('completed ' + successCount); 101 | // console.log('completed ' + successCount + ': ' + 'Copy from to' 102 | // + ' '); 103 | successCount++; 104 | }) 105 | .catch(function (error) { 106 | console.error('!!! Error processing file <' + file.name + '> with the error: ' + error); 107 | }); 108 | } 109 | } 110 | 111 | /************************************************************ 112 | MAIN 113 | ************************************************************/ 114 | console.log("Starting sorting by score..."); 115 | 116 | // let name = createNewName('BlueBall/count_10_high_7232_low_0522_file_4306.jpg'); 117 | // console.log('New name = ' + name); 118 | 119 | let bucket = storage.bucket(SOURCE_BUCKET); 120 | 121 | // bucket.getFiles({}, (err, files) => {console.log(err,files)}); 122 | bucket.getFiles({}, (err, files) => { 123 | if (err) { 124 | console.error('!!! ERROR listing of files in bucket <: ' + SOURCE_BUCKET + '>: ' + err); 125 | } else { 126 | console.log('Bucket <' + SOURCE_BUCKET + '> contains <' + files.length + '> files.'); 127 | processFilesAsync(files).then(() => { 128 | console.log('# of files processed successfully: ' + successCount); 129 | }) 130 | } 131 | }); -------------------------------------------------------------------------------- /admin/collect-images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright 2018 Google LLC 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################### 20 | # The script will collect all photos from any cloudderby event into a defined bucket under Source and Administration project 21 | # by scanning all folders and projects and buckets to download user images 22 | ############################################################################### 23 | 24 | set -u # This prevents running the script if any of the variables have not been set 25 | set -e # Exit if error is detected during pipeline execution 26 | 27 | source ./setenv.sh 28 | 29 | # Where do we want to copy all these images 30 | DESTINATION_BUCKET="crowd-sourced-images-archive" 31 | 32 | # How many GCS buckets were found 33 | BUCKETS_TOTAL=0 34 | 35 | # How many buckets were copied 36 | BUCKETS_COPIED=0 37 | 38 | # How many projects were found 39 | PROJECTS_FOUND=0 40 | 41 | # How many folders found 42 | FOLDERS_FOUND=0 43 | 44 | ############################################################################### 45 | # Scan all folders and projects under a designated folder ID 46 | # Input 47 | # 1 - folder ID under which all VMs to be stopped 48 | ############################################################################### 49 | scan_folders() { 50 | local PARENT_FOLDER_ID=$1 51 | local FOLDER_ID 52 | FOLDERS_FOUND=$((FOLDERS_FOUND+1)) 53 | 54 | echo_my "Scanning projects under folder id '$PARENT_FOLDER_ID'..." 55 | scan_projects "$PARENT_FOLDER_ID" 56 | 57 | local FOLDER_LIST=$(gcloud alpha resource-manager folders list --folder=$PARENT_FOLDER_ID --format="value(name)") 58 | 59 | while read -r FOLDER_ID; do 60 | if [[ ! -z "$FOLDER_ID" ]] ; then 61 | echo_my "Recursively processing folders under folder id '$FOLDER_ID'..." 62 | scan_folders "$FOLDER_ID" 63 | fi 64 | done <<< "$FOLDER_LIST" 65 | } 66 | 67 | ############################################################################### 68 | # Scan all projects under a given folder 69 | # Inputs 70 | # 1 - Folder ID 71 | ############################################################################### 72 | scan_projects() { 73 | echo_my "Scanning projects under folder '$1'..." 74 | local PROJECT_LIST=$(gcloud projects list --filter="parent.id=$1" --format="value(projectId)") 75 | local PROJ_ID 76 | 77 | while read -r PROJ_ID; do 78 | if [[ ! -z "$PROJ_ID" ]] ; then 79 | PROJECTS_FOUND=$((PROJECTS_FOUND+1)) 80 | echo_my "Processing project id '$PROJ_ID'..." 81 | scan_buckets $PROJ_ID 82 | fi 83 | done <<< "$PROJECT_LIST" 84 | } 85 | 86 | ############################################################################### 87 | # Scan and process all GCS buckets in a project 88 | # Inputs 89 | # 1 - project ID 90 | ############################################################################### 91 | scan_buckets() { 92 | local BUCKET 93 | echo_my "Scanning buckets for project '$1'..." 94 | 95 | local BUCKET_LIST=$(gsutil ls -p $1 gs://) 96 | 97 | if [ -z ${BUCKET_LIST+x} ] ; then 98 | return # No buckets found in this project 99 | fi 100 | 101 | while read -r BUCKET; do 102 | if [[ ! -z "$BUCKET" ]] ; then 103 | echo_my "Processing bucket '$BUCKET'" 104 | BUCKETS_TOTAL=$((BUCKETS_TOTAL+1)) 105 | 106 | if echo "$BUCKET" | grep -q "annotated-images" ; then 107 | BUCKETS_COPIED=$((BUCKETS_COPIED+1)) 108 | echo_my "Copy contents of the bucket '$BUCKET'..." 109 | gsutil -m cp $BUCKET*.zip gs://${DESTINATION_BUCKET}/annotated-images/$1/ | true # Ignore if error or empty 110 | fi 111 | 112 | if echo "$BUCKET" | grep -q "camera-" ; then 113 | BUCKETS_COPIED=$((BUCKETS_COPIED+1)) 114 | echo_my "Copy contents of the bucket '$BUCKET'..." 115 | gsutil -m cp $BUCKET*.jpg gs://${DESTINATION_BUCKET}/camera-images/$1/ | true # ignore if error or empty 116 | fi 117 | fi 118 | done <<< "$BUCKET_LIST" 119 | } 120 | 121 | ############################################################################### 122 | # MAIN 123 | ############################################################################### 124 | 125 | if [ -z ${1+x} ] ; then 126 | echo_my "NUMERIC_FOLDER_ID not found. \n Usage: collect-images.sh [NUMERIC_FOLDER_ID] \n Example: \n ./collect-images.sh 8763450985677 \n \n" $ECHO_ERROR 127 | exit 1 128 | fi 129 | 130 | # Process all projects and folders under this starting folder 131 | START_FOLDER=$1 132 | 133 | print_header "Collect all raw and annotated images from folder ID '$1'" 134 | 135 | scan_folders $START_FOLDER 136 | 137 | print_footer "SUCCESS: Found $PROJECTS_FOUND projects, $FOLDERS_FOUND folders, $BUCKETS_TOTAL buckets, including $BUCKETS_COPIED buckets with image content." -------------------------------------------------------------------------------- /cloud/controller/js/tools.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /************************************************************************** 18 | Various useful things 19 | **************************************************************************/ 20 | const fs = require('fs'); 21 | 22 | /************************************************************************** 23 | Generates random whole number between 0 and MAX 24 | Params: 25 | - Max - upper bound 26 | Returns: 27 | - Random whole number 28 | **************************************************************************/ 29 | function randomWholeNum(max) { 30 | return Math.floor(Math.random() * max); 31 | } 32 | 33 | /************************************************************************** 34 | Converts degrees to radians 35 | Params: 36 | - Angle in Degrees 37 | Returns: 38 | - Angle in radians 39 | **************************************************************************/ 40 | function toRadians(angle) { 41 | return angle * (Math.PI / 180); 42 | } 43 | 44 | function toDegrees(angle) { 45 | return angle * (180 / Math.PI); 46 | } 47 | 48 | /************************************************************************** 49 | Returns YYYYMMDD format of the date 50 | Params: 51 | - date 52 | Returns: 53 | - time in format YYYYMMDD 54 | **************************************************************************/ 55 | function yyyymmdd(date) { 56 | Date.prototype.yyyymmdd = function () { 57 | const mm = this.getMonth() + 1; // getMonth() is zero-based 58 | const dd = this.getDate(); 59 | 60 | return [this.getFullYear(), (mm > 9 ? '' : '0') + mm, (dd > 9 ? '' : '0') + dd].join(''); 61 | }; 62 | 63 | return new Date(date).yyyymmdd(); 64 | } 65 | 66 | /************************************************************************** 67 | Returns YYYYMMDDHH format of the date 68 | Params: 69 | - date 70 | Returns: 71 | - time in format YYYYMMDDHH 72 | **************************************************************************/ 73 | function yyyymmddhh(date) { 74 | return yyyymmdd(date) + date.toUTCString().substr(17, 2); 75 | } 76 | 77 | /************************************************************************** 78 | Returns HHMMSS format of the date 79 | Params: 80 | - date 81 | Returns: 82 | - time in format HHMMSS 83 | **************************************************************************/ 84 | function hhmmss(date) { 85 | return date.toUTCString().substr(17, 8).replace(":", "").replace(":", ""); 86 | } 87 | 88 | /************************************************************************** 89 | Generates random date between start and end 90 | Params: 91 | - Start date 92 | - End date 93 | Returns: 94 | - Random date between those two 95 | **************************************************************************/ 96 | function randomDate(start, end) { 97 | return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); 98 | } 99 | 100 | /************************************************************************** 101 | Capitalizes first letter of the string, or second letter if string starts with 102 | quotation mark 103 | Params: 104 | - String to process 105 | Returns: 106 | - String with the first capital letter 107 | **************************************************************************/ 108 | function capitalizeFirstLetter(string) { 109 | if (string.charAt(0) == "\"") { 110 | return "\"" + string.charAt(1).toUpperCase() + string.slice(2); 111 | } 112 | return string.charAt(0).toUpperCase() + string.slice(1); 113 | } 114 | 115 | /************************************************************************** 116 | Get the size of the file in bytes 117 | Params: 118 | - Name of the file 119 | Returns: 120 | - Size in bytes 121 | **************************************************************************/ 122 | function getFilesizeInBytes(filename) { 123 | const stats = fs.statSync(filename); 124 | return stats["size"]; 125 | } 126 | 127 | /************************************************************************** 128 | Convert millimiters to inches 129 | Params: 130 | - millimiters 131 | Returns: 132 | - inches 133 | **************************************************************************/ 134 | function mm2inches(mm) { 135 | return mm * 25.4; 136 | } 137 | 138 | /************************************************************************** 139 | Convert inches to millimiters 140 | Params: 141 | - inches 142 | Returns: 143 | - millimiters 144 | **************************************************************************/ 145 | function inches2mm(inches) { 146 | return inches / 25.4; 147 | } 148 | 149 | /************************************************************************** 150 | Export these functions so they can be used outside 151 | **************************************************************************/ 152 | module.exports = { 153 | randomWholeNum: randomWholeNum, 154 | toRadians: toRadians, 155 | toDegrees: toDegrees, 156 | mm2inches: mm2inches, 157 | inches2mm: inches2mm, 158 | yyyymmdd: yyyymmdd, 159 | yyyymmddhh: yyyymmddhh, 160 | hhmmss: hhmmss, 161 | randomDate: randomDate, 162 | capitalizeFirstLetter: capitalizeFirstLetter, 163 | getFilesizeInBytes: getFilesizeInBytes 164 | }; -------------------------------------------------------------------------------- /cloud/ml/training/create-training-vm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################################### 4 | # This script creates a GCE VM used for training using 5 | # transfer learning object detection with GPU. 6 | ############################################### 7 | 8 | # 9 | # Copyright 2018 Google LLC 10 | # 11 | # Licensed under the Apache License, Version 2.0 (the "License"); 12 | # you may not use this file except in compliance with the License. 13 | # You may obtain a copy of the License at 14 | # 15 | # https://www.apache.org/licenses/LICENSE-2.0 16 | # 17 | # Unless required by applicable law or agreed to in writing, software 18 | # distributed under the License is distributed on an "AS IS" BASIS, 19 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | # See the License for the specific language governing permissions and 21 | # limitations under the License. 22 | # 23 | 24 | set -u # This prevents running the script if any of the variables have not been set 25 | set -e # Exit if error is detected during pipeline execution 26 | 27 | source ./setenv.sh 28 | 29 | ############################################# 30 | # Create static external IP for the ML VM 31 | ############################################# 32 | create_static_inference_ip() 33 | { 34 | if gcloud compute addresses list | grep $ML_IP_NAME; then 35 | echo_my "Static IP address $ML_IP_NAME found OK" 36 | else 37 | if [ ! -z ${AUTO_CREATE_IP+x} ]; then 38 | echo "Creating static external IP for ML VM... '$ML_IP_NAME'" 39 | gcloud compute addresses create $ML_IP_NAME --region $REGION 40 | else 41 | echo "Skipping automatic creation of static IP because AUTO_CREATE_IP variable is not set" 42 | fi 43 | fi 44 | } 45 | 46 | ############################################### 47 | # Open proper ports on a firewall 48 | ############################################### 49 | configure_firewall() 50 | { 51 | # Only configure firewall if we are in automatic "fast path" mode - aka users are not creating these things by hand 52 | if [ ! -z ${AUTO_CREATE_FIREWALL+x} ]; then 53 | open_http_firewall_port $HTTP_PORT 54 | else 55 | echo "Skipping automatic creation of firewall because AUTO_CREATE_FIREWALL variable is not set" 56 | fi 57 | 58 | # Deep Learning VM has pre-installed Python Lab on port 8080 59 | open_http_firewall_port 8080 60 | 61 | # Open Tensorboard port 62 | open_http_firewall_port $TF_HTTP_PORT 63 | 64 | open_ssh_firewall_port 65 | } 66 | 67 | ############################################### 68 | # Open HTTP port on a firewall 69 | # Input: 70 | # 1 - HTTP port to open 71 | ############################################### 72 | open_http_firewall_port() 73 | { 74 | local PORT=$1 75 | if gcloud compute firewall-rules list --format='table(NAME,NETWORK,DIRECTION,PRIORITY,ALLOW,DENY)' | grep "allow-http-$PORT"; then 76 | echo_my "Firewall rule 'allow-http-$PORT' found OK" 77 | else 78 | echo_my "Create firewall rule for port '$PORT'..." 79 | gcloud compute --project="$PROJECT" firewall-rules create \ 80 | allow-http-$PORT --direction=INGRESS --priority=1000 \ 81 | --network=default --action=ALLOW --rules=tcp:$PORT \ 82 | --source-ranges=0.0.0.0/0 --target-tags=${HTTP_TAG} | true # Ignore if the firewall rule already exists 83 | fi 84 | } 85 | 86 | ############################################### 87 | # Open SSH port on a firewall 88 | ############################################### 89 | open_ssh_firewall_port() 90 | { 91 | if gcloud compute firewall-rules list --format='table(NAME,NETWORK,DIRECTION,PRIORITY,ALLOW,DENY)' | grep "allow-${SSH_TAG}"; then 92 | echo_my "Firewall rule for SSH was found" 93 | else 94 | echo_my "Create firewall rule for '$SSH_TAG'..." 95 | gcloud compute --project="$PROJECT" firewall-rules create \ 96 | allow-${SSH_TAG} --direction=INGRESS --priority=1000 \ 97 | --network=default --action=ALLOW --rules=tcp:22 \ 98 | --source-ranges=0.0.0.0/0 --target-tags=${SSH_TAG} | true # Ignore if the firewall rule already exists 99 | fi 100 | } 101 | 102 | ############################################### 103 | # Create a VM on GCE with a certain number of GPUs 104 | # Inputs: 105 | # 1 - name of the VM 106 | # 2 - number of GPUs 107 | ############################################### 108 | create_gpu_vm() 109 | { 110 | local VM_NAME=$1 111 | local GPU_COUNT=$2 112 | echo_my "Create VM instance '$VM_NAME' with '$GPU_COUNT' GPUs in a project '$PROJECT'..." 113 | # See docs: https://cloud.google.com/deep-learning-vm/docs/quickstart-cli 114 | # https://cloud.google.com/deep-learning-vm/docs/tensorflow_start_instance 115 | gcloud compute --project="$PROJECT" instances create $VM_NAME \ 116 | --zone $ZONE \ 117 | --image-family=tf-latest-gpu \ 118 | --image-project=deeplearning-platform-release \ 119 | --boot-disk-size=70GB \ 120 | --boot-disk-type=pd-ssd \ 121 | --machine-type n1-highmem-2 \ 122 | --accelerator="type=nvidia-tesla-v100,count=$GPU_COUNT" \ 123 | --service-account $ALLMIGHTY_SERVICE_ACCOUNT \ 124 | --maintenance-policy TERMINATE \ 125 | --restart-on-failure \ 126 | --subnet default \ 127 | --address $ML_IP_NAME \ 128 | --tags $HTTP_TAG,$SSH_TAG \ 129 | --metadata="install-nvidia-driver=True" \ 130 | --scopes=default,storage-rw,https://www.googleapis.com/auth/source.read_only 131 | 132 | echo_my "List of my instances..." 133 | gcloud compute --project="$PROJECT" instances list 134 | } 135 | 136 | ############################################# 137 | # MAIN 138 | ############################################# 139 | print_header "Create new Object Detection Training VM" 140 | 141 | configure_firewall 142 | 143 | create_static_inference_ip 144 | 145 | GPU_COUNT=1 146 | create_gpu_vm $ML_VM $GPU_COUNT 147 | 148 | print_footer "ML training VM Creation has completed." -------------------------------------------------------------------------------- /admin/stop-vms.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright 2018 Google LLC 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################### 20 | # This script traverses the org and stops all runnng VMs 21 | ############################################################################### 22 | 23 | set -u # This prevents running the script if any of the variables have not been set 24 | set -e # Exit if error is detected during pipeline execution 25 | 26 | source ./setenv.sh 27 | 28 | # How many total Vms were found 29 | VMS_TOTAL=0 30 | 31 | # How many running Vms were stopped 32 | VMS_RUNNING=0 33 | 34 | # How many projects were found 35 | PROJECTS_FOUND=0 36 | 37 | # How many folders found 38 | FOLDERS_FOUND=0 39 | 40 | ############################################################################### 41 | # Scan all folders and projects under a designated folder ID 42 | # Input 43 | # 1 - folder ID under which all VMs to be stopped 44 | ############################################################################### 45 | scan_folders() { 46 | local PARENT_FOLDER_ID=$1 47 | local FOLDER_ID 48 | FOLDERS_FOUND=$((FOLDERS_FOUND+1)) 49 | 50 | echo_my "Scanning projects under folder id '$PARENT_FOLDER_ID'..." 51 | scan_projects $PARENT_FOLDER_ID 52 | 53 | local FOLDER_LIST=$(gcloud alpha resource-manager folders list --folder=$PARENT_FOLDER_ID --format="value(name)") 54 | 55 | while read -r FOLDER_ID; do 56 | if [[ ! -z "$FOLDER_ID" ]] ; then 57 | echo_my "Recursively processing folders under folder id '$FOLDER_ID'..." 58 | scan_folders $FOLDER_ID 59 | fi 60 | done <<< "$FOLDER_LIST" 61 | } 62 | 63 | ############################################################################### 64 | # Scan all projects under a given folder 65 | # Inputs 66 | # 1 - Folder ID 67 | ############################################################################### 68 | scan_projects() { 69 | echo_my "Scanning projects under folder '$1'..." 70 | local PROJECT_LIST=$(gcloud projects list --filter="parent.id=$1" --format="value(projectId)") 71 | local PROJ_ID 72 | 73 | while read -r PROJ_ID; do 74 | if [[ ! -z "$PROJ_ID" ]] ; then 75 | PROJECTS_FOUND=$((PROJECTS_FOUND+1)) 76 | echo_my "Processing project id '$PROJ_ID'..." 77 | stop_vms $PROJ_ID 78 | fi 79 | done <<< "$PROJECT_LIST" 80 | } 81 | 82 | ############################################################################### 83 | # Stop all VMs in a project 84 | # Inputs 85 | # 1 - project ID 86 | ############################################################################### 87 | stop_vms() { 88 | local VM_ID 89 | local PROJECT_ID=$1 90 | echo_my "Scanning VMs for project '$PROJECT_ID'..." 91 | 92 | local VM_LIST=$(gcloud compute instances list --project $PROJECT_ID --format="value(name)") 93 | 94 | echo $VM_LIST 95 | 96 | while read -r VM_ID; do 97 | if [[ ! -z "$VM_ID" ]] ; then 98 | VMS_TOTAL=$((VMS_TOTAL+1)) 99 | # Get the zone of the instance 100 | local ZONE=$(gcloud compute instances list --filter="name:($VM_ID)" --project $PROJECT_ID --format="value(zone)") 101 | local STATUS=$(gcloud compute instances list --filter="name:($VM_ID)" --project $PROJECT_ID --format="value(status)") 102 | echo_my "Found VM id '$VM_ID' with status '$STATUS' in project '$PROJECT_ID'" 103 | if [ $STATUS = "RUNNING" ] ; then 104 | VMS_RUNNING=$((VMS_RUNNING+1)) 105 | if [ $COUNT_RUNNING_VM_ONLY = false ] ; then 106 | echo_my "Stopping VM id '$VM_ID' in project '$PROJECT_ID'..." 107 | yes | gcloud compute instances stop $VM_ID --project $PROJECT_ID --zone=$ZONE | true # Ignore if error and proceed 108 | fi 109 | fi 110 | else 111 | echo_my "No more VMs found in this project" 112 | fi 113 | done <<< "$VM_LIST" 114 | } 115 | 116 | ############################################################################### 117 | # MAIN 118 | ############################################################################### 119 | if [ -z ${1+x} ] ; then 120 | echo_my "NUMERIC_FOLDER_ID not found. \n Usage: stop-vms.sh [NUMERIC_FOLDER_ID] \n Example: \n ./stop-vms.sh 8763450985677 \n \n" $ECHO_ERROR 121 | exit 1 122 | fi 123 | 124 | # Process all projects and folders under this starting folder 125 | START_FOLDER=$1 126 | 127 | print_header "Stop all running VMs" 128 | 129 | # If this is true, then running VMs will be counted, but not stopped 130 | COUNT_RUNNING_VM_ONLY=false 131 | 132 | echo_my "\nATTENTION!!!!!!!!!!!\nATTENTION!!!!!!!!!!!\nATTENTION!!!!!!!!!!!\n" 133 | echo_my "This will stop all running VMs under the folder --- '$START_FOLDER' ---. Are you sure you want to proceed?" $ECHO_WARNING 134 | pause 135 | echo_my "\nAre you sure you want to stop all running VMs ???????" $ECHO_WARNING 136 | pause 137 | 138 | if [ $COUNT_RUNNING_VM_ONLY = false ] ; then 139 | echo_my "COUNT_RUNNING_VM_ONLY=$COUNT_RUNNING_VM_ONLY - this script will STOP all running VMs." 140 | else 141 | echo_my "COUNT_RUNNING_VM_ONLY=$COUNT_RUNNING_VM_ONLY - this script will COUNT all running VMs." 142 | fi 143 | 144 | scan_folders $START_FOLDER 145 | 146 | print_footer "SUCCESS: Found $PROJECTS_FOUND projects, $FOLDERS_FOUND folders, $VMS_TOTAL VMs, including $VMS_RUNNING running VMs." -------------------------------------------------------------------------------- /cloud/controller/js/simulation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | const DriveMessage = require('./drive-message'); 19 | const VisionResponse = require('./vision-response'); 20 | const BoundingBox = require('./bounding-box'); 21 | const Settings = require('./settings'); 22 | 23 | /************************************************************ 24 | Simulation for car commands. Pre-set messages being sent to the car regardless of input 25 | ************************************************************/ 26 | class DriveMessageSimulator { 27 | 28 | constructor() { 29 | // Are we simulating driving commands or not? 30 | this.simulate = false; 31 | // Iteration of the fake driving command 32 | this.index = 0; 33 | 34 | // These are fake driving commands 35 | this.COMMANDS = [{turn1: 0, setSpeed: 80, driveForward: 100, turn2: 0, driveBackward: 0}, { 36 | turn1: -90, 37 | setSpeed: 80, 38 | driveForward: 0, 39 | turn2: 180, 40 | driveBackward: 0 41 | }, {turn1: 0, setSpeed: 80, driveForward: 40, turn2: 0, driveBackward: 0}, { 42 | turn1: 45, 43 | setSpeed: 80, 44 | driveForward: 0, 45 | turn2: -12, 46 | driveBackward: 0 47 | }, {turn1: 0, setSpeed: 80, driveForward: 60, turn2: 0, driveBackward: 0}, { 48 | turn1: -10, 49 | setSpeed: 80, 50 | driveForward: 0, 51 | turn2: 0, 52 | driveBackward: 0 53 | }, {turn1: 0, setSpeed: 80, driveForward: 80, turn2: 0, driveBackward: 0}, { 54 | turn1: 25, 55 | setSpeed: 80, 56 | driveForward: 0, 57 | turn2: 0, 58 | driveBackward: 0 59 | }, {turn1: 0, setSpeed: 80, driveForward: 10, turn2: 0, driveBackward: 0}, { 60 | turn1: -5, 61 | setSpeed: 80, 62 | driveForward: 0, 63 | turn2: 0, 64 | driveBackward: 0 65 | }]; 66 | } 67 | 68 | /************************************************************ 69 | Iterate over multiple fake driving commands and add them to the car message 70 | Input: 71 | - none 72 | Output: 73 | - driving command to be sent to the car 74 | ************************************************************/ 75 | nextDrivingCommand() { 76 | let command = new DriveMessage(); 77 | let fake = this.COMMANDS[this.index]; 78 | command.makeTurn(fake.turn1); 79 | command.setSpeed(fake.setSpeed); 80 | command.driveForward(fake.driveForward); 81 | command.makeTurn(fake.turn2); 82 | command.driveBackward(fake.driveBackward); 83 | 84 | this.index++; 85 | if (this.index >= this.COMMANDS.length) { 86 | this.index = 0; 87 | } 88 | return command; 89 | } 90 | } 91 | 92 | /************************************************************ 93 | Simulation for vision API when Object Detection is turned off 94 | ************************************************************/ 95 | class VisionSimulator { 96 | 97 | constructor() { 98 | // Are we simulating vision responses or not? 99 | this.simulate = false; 100 | // Iteration of the fake vision responses 101 | this.index = 0; 102 | 103 | // These are fake vision responses 104 | this.COMMANDS = [[new BoundingBox("red_ball", 240, 230, 1, 1, 0.92), new BoundingBox("red_ball", 600, 200, 20, 20, 0.98), new BoundingBox("red_ball", 50, 10, 1, 1, 0.88), new BoundingBox("green_ball", 50, 70, 100, 200, 0.98), new BoundingBox("border", 10, 20, 10, 800, 0.92)], [new BoundingBox("blue_ball", 640, 30, 25, 25, 0.93)], [new BoundingBox("blue_ball", 40, 30, 25, 25, 0.97)], [new BoundingBox("blue_ball", 0, 30, 25, 25, 0.98)], [new BoundingBox("blue_ball", 4, 30, 25, 25, 0.96)], [new BoundingBox("yellow_ball", 440, 130, 30, 30, 0.92), new BoundingBox("green_ball", 50, 70, 100, 100, 0.91)], [new BoundingBox("blue_ball", 640, 30, 25, 25, 0.98)], [new BoundingBox("yellow_ball", 440, 130, 30, 30, 0.94), new BoundingBox("green_ball", 50, 70, 100, 100, 0.88)], [new BoundingBox("red_ball", 640, 30, 25, 25, 0.92)], [new BoundingBox("red_ball", 440, 130, 30, 30, 0.93), new BoundingBox("green_ball", 50, 70, 100, 100, 0.94)], // Ball occupies entire frame - testing capture method 105 | [new BoundingBox("red_ball", 0, 0, Settings.camera.HORIZONTAL_RESOLUTION_PIXELS, Settings.camera.VERTICAL_RESOLUTION_PIXELS, 0.92), new BoundingBox("blue_ball", 150, 170, 200, 200, 0.95)]]; 106 | } 107 | 108 | /************************************************************ 109 | Iterate over multiple fake driving commands and add them to the car message 110 | Input: 111 | - none 112 | Output: 113 | - vision response (taken from the hard-coded array above) 114 | ************************************************************/ 115 | nextVisionResponse() { 116 | 117 | let response = new VisionResponse(); 118 | let fake = this.COMMANDS[this.index]; 119 | // Randomly generate fake objects 120 | for (let i = fake.length; i--;) { 121 | response.addBox(fake[i]); 122 | } 123 | 124 | this.index++; 125 | if (this.index >= this.COMMANDS.length) { 126 | this.index = 0; 127 | } 128 | return response; 129 | } 130 | } 131 | 132 | /************************************************************************** 133 | Module exports 134 | **************************************************************************/ 135 | module.exports.DriveMessageSimulator = DriveMessageSimulator; 136 | module.exports.VisionSimulator = VisionSimulator; -------------------------------------------------------------------------------- /car/simulator/js/sensor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const process = require('process'); // Required for mocking environment variables 20 | var SensorMessage = require('../../../cloud/controller/js/sensor-message'); // Library for messages and sensors 21 | 22 | const SIMULATION_IMG_FOLDER = process.env.TEST_IMAGE_FOLDER; // Location of test images 23 | const BUCKET = process.env.CAR_CAMERA_BUCKET; // Bucket where images will be uploaded to 24 | const TOPIC = process.env.SENSOR_TOPIC; // Where to post messages 25 | const ITERATIONS = process.env.NUM_ITERATIONS; // How many messages to send 26 | const THINK_TIME = process.env.DELAY; // How long to wait between sending simulated messages to the topic 27 | const TEST_IMAGE_FILE = process.env.TEST_IMAGE_FILE; // If this is specified, it will be the only file to be tested 28 | const CAR_ID = process.env.CAR_ID; 29 | 30 | // Simulated messages content 31 | let data = [ 32 | { "car": CAR_ID, "laser": 90, "balls": 0, "battery": 99, "color": "Blue" }, 33 | { "car": CAR_ID, "laser": 80, "balls": 0, "battery": 90, "color": "Blue" }, 34 | { "car": CAR_ID, "laser": 70, "balls": 0, "battery": 85, "color": "Blue" }, 35 | { "car": CAR_ID, "laser": 60, "balls": 1, "battery": 80, "color": "Blue" }, 36 | { "car": CAR_ID, "laser": 50, "balls": 1, "battery": 75, "color": "Blue" }, 37 | { "car": CAR_ID, "laser": 40, "balls": 1, "battery": 70, "color": "Blue" }, 38 | { "car": CAR_ID, "laser": 30, "balls": 2, "battery": 65, "color": "Blue" }, 39 | { "car": CAR_ID, "laser": 20, "balls": 2, "battery": 60, "color": "Blue" }, 40 | { "car": CAR_ID, "laser": 11, "balls": 2, "battery": 55, "color": "Blue" }, 41 | { "car": CAR_ID, "laser": 10, "balls": 3, "battery": 50, "color": "Blue" }, 42 | ]; 43 | 44 | log(`START: Simulating car sensors... SIMULATION_IMG_FOLDER=${SIMULATION_IMG_FOLDER}, BUCKET=${BUCKET}, THINK_TIME=${THINK_TIME}, TOPIC=${TOPIC}`); 45 | 46 | // Pubsub client 47 | const PubSub = require('@google-cloud/pubsub'); 48 | const pubsub = PubSub(); 49 | const sensorTopic = pubsub.topic(TOPIC); 50 | 51 | // GCS client 52 | const Storage = require('@google-cloud/storage'); 53 | const storage = new Storage(); 54 | 55 | // Internal statistics variables 56 | let messagesSent = 0; 57 | let totalErrors = 0; 58 | let iteration = 0; 59 | 60 | /************************************************************************** 61 | Upload an image to GCS, for details see: 62 | https://github.com/googleapis/nodejs-storage/blob/master/samples/files.js 63 | **************************************************************************/ 64 | function uploadImage(image) { 65 | let imagePath = `${SIMULATION_IMG_FOLDER}/${image}`; 66 | log(`uploadImage(): Uploading ${imagePath}...`); 67 | storage 68 | .bucket(BUCKET) 69 | .upload(imagePath) 70 | .then(() => { 71 | log(`${imagePath} uploaded to ${BUCKET}.`); 72 | }) 73 | .catch(err => { 74 | console.error('ERROR:', err); 75 | process.exit(1); 76 | }); 77 | 78 | return `gs://${BUCKET}/${image}`; 79 | } 80 | 81 | /************************************************************************** 82 | Single iteration of simulator 83 | **************************************************************************/ 84 | function sendOneMessage() { 85 | let i = iteration; 86 | if (i >= data.length) { 87 | i = i % data.length; 88 | } 89 | log(`Iteration: ${iteration}, index: ${i}`); 90 | iteration++; 91 | 92 | let image; 93 | log("TEST_IMAGE_FILE='" + TEST_IMAGE_FILE+"'"); 94 | if (TEST_IMAGE_FILE.length == 0) { 95 | log("Using images from the subfolder..."); 96 | image = `image${i+1}.jpg`; 97 | } 98 | else { 99 | log("Using image " + TEST_IMAGE_FILE); 100 | image = TEST_IMAGE_FILE; 101 | } 102 | 103 | let imageGcsPath = uploadImage(`${image}`); 104 | // Make HTTP URL from the GCS one 105 | let imageUrl = "https://storage.googleapis.com/" + imageGcsPath.substr(5,imageGcsPath.length); 106 | 107 | // Make sure there is type checking and consistency if sensor message constructor is changed 108 | let message = JSON.stringify( 109 | new SensorMessage(data[i].car, data[i].balls, false, data[i].battery, data[i].laser, imageUrl, imageGcsPath, data[i].color)); 110 | 111 | // Using timeout to make sure all GCS upload processes have completed before we send the message 112 | const sleepTimeMs = 1000; 113 | setTimeout(function sendMessage() { 114 | if (message.length > 0) { 115 | sensorTopic.publish(message, (err) => { 116 | if (err) { 117 | log(err); 118 | totalErrors++; 119 | return; 120 | } 121 | messagesSent++; 122 | log(`Message #${messagesSent} sent to PubSub: <${message}>`); 123 | }); 124 | } 125 | else { 126 | log('Command is empty - Nothing to send'); 127 | } 128 | }, sleepTimeMs); 129 | 130 | } 131 | 132 | /************************************************************************** 133 | * Log function 134 | * Returns: nothing 135 | **************************************************************************/ 136 | function log(string) { 137 | console.log("simulator.js > " + string); 138 | } 139 | 140 | /************************************************************ 141 | * MAIN 142 | ************************************************************/ 143 | for (var i = 0; i < ITERATIONS; i++) { 144 | setTimeout(function() { 145 | sendOneMessage(); 146 | }, i * THINK_TIME * 1000); 147 | } 148 | 149 | log("Sensor simulation finished."); 150 | -------------------------------------------------------------------------------- /cloud/controller/js/vision.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | const process = require('process'); // Required for mocking environment variables 19 | const request = require('request'); 20 | 21 | // User credentials to authenticate to remote Inference VM service 22 | const INFERENCE_USER_NAME = process.env.INFERENCE_USER_NAME; 23 | const INFERENCE_PASSWORD = process.env.INFERENCE_PASSWORD; 24 | 25 | const APP_URL = `http://${process.env.INFERENCE_IP}`; 26 | const HTTP_PORT = process.env.HTTP_PORT; 27 | const INFERENCE_URL = process.env.INFERENCE_URL; 28 | const OBJECT_INFERENCE_API_URL = APP_URL + ':' + HTTP_PORT + INFERENCE_URL; 29 | 30 | require('dotenv').config(); 31 | const VisionResponse = require('./vision-response'); 32 | const BoundingBox = require('./bounding-box'); 33 | 34 | // Initialize simulation engine (it may be On or Off) 35 | const VisionSimulator = require('./simulation').VisionSimulator; 36 | let visionSimulator = new VisionSimulator(); 37 | 38 | /************************************************************************** 39 | Vision class calls Object Detection API to figure out where are all the 40 | balls in the image so navigation logic can use it for driving decisions 41 | **************************************************************************/ 42 | module.exports = class Vision { 43 | 44 | constructor() { 45 | // Whatever needs to be done here... 46 | } 47 | 48 | /************************************************************ 49 | Send image to ML and parse it 50 | Input: 51 | - sensorMessage - includes GCS URL to the Image (gs://...) 52 | Output: 53 | - VisionResponse - Coordinates of various objects that were recognized 54 | ************************************************************/ 55 | recognizeObjects(sensorMessage) { 56 | console.log("vision.recognizeObjects(): start..."); 57 | 58 | // Are we in a simulation mode? If so, return hard coded responses 59 | if (visionSimulator.simulate) { 60 | return Promise.resolve() 61 | .then(() => { 62 | console.log("vision.recognizeObjects(): returning a simulated response"); 63 | return visionSimulator.nextVisionResponse(); 64 | }); 65 | } 66 | 67 | // Call REST API - Object Detection - ML Engine or TensorFlow 68 | // this returns a Promise which when resolved returns the VisionResponse object 69 | return this.recognizeObjectAPIAsync(sensorMessage) 70 | .then((response) => { 71 | return Promise.resolve() 72 | .then(() => { 73 | return this.createVisionResponse(response); 74 | }); 75 | }) 76 | .catch((error) => { 77 | console.log("vision.recognizeObjects(): Error calling remote Object Detection API: " + error); 78 | // In case of an error, return empty response 79 | return new VisionResponse(); 80 | }); 81 | } 82 | 83 | /************************************************************ 84 | Generate response from the ML Vision 85 | Input: 86 | - jsonAPIResponse - response from the Vision API 87 | Output: 88 | - VisionResponse - Coordinates of various objects that were recognized 89 | ************************************************************/ 90 | createVisionResponse(jsonAPIResponse) { 91 | let response = new VisionResponse(); 92 | const objResponse = JSON.parse(jsonAPIResponse); 93 | 94 | for (let key in objResponse) { 95 | for (let i = 0; i < objResponse[key].length; i++) { 96 | //console.log("objResponse[key]["+i+"]: "+JSON.stringify(objResponse[key][i])); 97 | const bBox = new BoundingBox(key, objResponse[key][i]["x"], objResponse[key][i]["y"], objResponse[key][i]["w"], objResponse[key][i]["h"], objResponse[key][i]["score"]); 98 | response.addBox(bBox); 99 | } 100 | } 101 | return response; 102 | } 103 | 104 | /************************************************************ 105 | Generate response from the ML Vision 106 | Input: 107 | - sensorMessage - message from the car with sensor data 108 | Output: 109 | - 110 | ************************************************************/ 111 | recognizeObjectAPIAsync(sensorMessage) { 112 | return new Promise(function (resolve, reject) { 113 | const gcsURI = sensorMessage.sensors.frontCameraImagePathGCS; 114 | 115 | if (!gcsURI) { 116 | reject("Error: No gcURI found in sensorMessage"); 117 | 118 | } else if (!gcsURI.startsWith("gs://")) { 119 | reject("Error: gcsURI must start with gs://"); 120 | 121 | } else { 122 | // Example request for the inference VM: http://xx.xx.xx.xx:8082/v1/objectInference?gcs_uri=gs%3A%2F%2Fcamera-9-roman-test-oct9%2Fimage1.jpg 123 | const apiUrl = OBJECT_INFERENCE_API_URL + "?gcs_uri=" + encodeURIComponent(gcsURI); 124 | console.log("Vision API URL: " + apiUrl); 125 | // var visionResponse = new VisionResponse(); 126 | const auth = {user: INFERENCE_USER_NAME, pass: INFERENCE_PASSWORD}; 127 | 128 | // Measure the time it takes to call inference API 129 | const startTime = Date.now(); 130 | 131 | request({uri: apiUrl, auth: auth}, function (err, response, body) { 132 | if (err) { 133 | console.log("!!! ERROR !!! calling remote ML API: " + err + ". Please verify that your Inference VM and the App are up and running and proper HTTP port is open in the firewall."); 134 | reject(err); 135 | } else { 136 | console.log("Vision API call took " + (Date.now() - startTime) + " ms. Result: " + body); 137 | if (response.statusCode != 200) { 138 | reject("Error: Received " + response.statusCode + " from API"); 139 | 140 | } else { 141 | resolve(body); 142 | } 143 | } 144 | }); 145 | } 146 | }); 147 | } 148 | }; -------------------------------------------------------------------------------- /car/driver/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Copyright 2018 Google LLC 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################### 20 | # Control GoPiGo car by sending sensor messages 21 | # to the cloud and receiving driving commands 22 | ############################################### 23 | 24 | set -u # This prevents running the script if any of the variables have not been set 25 | set -e # Exit if error is detected during pipeline execution 26 | 27 | source ../../setenv-global.sh 28 | 29 | export GOOGLE_APPLICATION_CREDENTIALS="$SERVICE_ACCOUNT_SECRET" 30 | 31 | # This is subscription for the car to listen to incoming control messages 32 | export COMMAND_SUBSCRIPTION="driving-command-subscription-$CAR_ID" 33 | 34 | ### Obstacle avoidance - the number of millimeters to stop the car before hitting an object 35 | export BARRIER_DAMPENING="180" 36 | 37 | ### Car Camera position; UPSIDE DOWN=0; NORMAL=1 - this takes effect on the car as the image will be flipped before being sent to the cloud 38 | export CAR_CAMERA_NORMAL="0" 39 | 40 | ### What color ball this car will be playing (default value) 41 | export CAR_COLOR="red" 42 | 43 | ############################################### 44 | # This is run once after creating new environment 45 | ############################################### 46 | install() { 47 | echo_my "Installing Python, Pip and other libraries..." 48 | sudo apt-get install python 49 | sudo apt-get install python-pip 50 | sudo pip install --upgrade pip 51 | sudo pip install --upgrade google-cloud --ignore-installed six 52 | sudo pip install paho-mqtt 53 | sudo pip install --upgrade pip setuptools 54 | sudo pip install curtsies 55 | wget https://bootstrap.pypa.io/ez_setup.py -O - | sudo python 56 | sudo apt-get install build-essential libssl-dev libffi-dev python3-dev 57 | sudo pip install pyasn1 pyasn1-modules -U 58 | sudo pip install cryptography 59 | sudo pip install PyJWT 60 | sudo pip install Pillow 61 | wget https://pypi.python.org/packages/16/d8/bc6316cf98419719bd59c91742194c111b6f2e85abac88e496adefaf7afe/six-1.11.0.tar.gz#md5=d12789f9baf7e9fb2524c0c64f1773f8 62 | sudo tar -zxvf six-1.11.0.tar.gz 63 | sudo python ./six-1.11.0/setup.py install 64 | } 65 | 66 | ############################################### 67 | # One time car setup tasks 68 | ############################################### 69 | setup() { 70 | echo_my "Setting up car environment for the first run..." 71 | if which pip; then 72 | echo "Python and Pip are already installed, skipping this step." 73 | else 74 | install 75 | fi 76 | 77 | wget https://pki.google.com/roots.pem 78 | } 79 | 80 | ############################################### 81 | # Generate keys for the IOT device 82 | ############################################### 83 | generate_keys() { 84 | echo_my "Generating keys for the IOT device..." 85 | openssl req -x509 -newkey rsa:2048 -days 3650 -keyout rsa_private.pem -nodes -out \ 86 | rsa_cert.pem -subj "/CN=unused" 87 | openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem 88 | openssl ec -in ec_private.pem -pubout -out ec_public.pem 89 | } 90 | 91 | ############################################### 92 | # MAIN 93 | ############################################### 94 | mkdir -p tmp 95 | echo_my "CAR_ID=$CAR_ID" 96 | INSTALL_FLAG=tmp/install.marker # Location where the install flag is set to avoid repeated installs 97 | 98 | if [ -f "$INSTALL_FLAG" ]; then 99 | echo_my "File '$INSTALL_FLAG' was found = > no need to do the install since it already has been done." 100 | else 101 | setup 102 | touch $INSTALL_FLAG 103 | fi 104 | 105 | gcloud config set project $PROJECT 106 | 107 | echo "Activating service account '$SERVICE_ACCOUNT_SECRET'..." 108 | gcloud auth activate-service-account --key-file=$SERVICE_ACCOUNT_SECRET 109 | 110 | echo_my "Using subscription '$COMMAND_SUBSCRIPTION' to read data from the driving controller..." 111 | if gcloud pubsub subscriptions list | grep $COMMAND_SUBSCRIPTION; then 112 | echo_my "Subscription '$COMMAND_SUBSCRIPTION' already exists - dropping it to avoid processing of old messages..." 113 | gcloud pubsub subscriptions delete $COMMAND_SUBSCRIPTION | true # ignore if not found 114 | fi 115 | 116 | echo_my "Creating a subscription '$COMMAND_SUBSCRIPTION' to topic '$COMMAND_TOPIC'..." 117 | gcloud pubsub subscriptions create $COMMAND_SUBSCRIPTION --topic $COMMAND_TOPIC | true 118 | 119 | if gcloud iot registries list --region=$REGION | grep $IOT_CORE_REGISTRY; then 120 | echo_my "IOT Core Registry $IOT_CORE_REGISTRY already exists. Updating for consistency..." 121 | gcloud iot registries update $IOT_CORE_REGISTRY --project=$PROJECT --region=$REGION \ 122 | --event-notification-config=topic=projects/$PROJECT/topics/$SENSOR_TOPIC,subfolder=$SENSOR_TOPIC \ 123 | --event-notification-config=topic=projects/$PROJECT/topics/$COMMAND_TOPIC,subfolder=$COMMAND_TOPIC \ 124 | | true 125 | else 126 | echo_my "Creating an IOT Core Device for Registry: '$IOT_CORE_REGISTRY' " 127 | gcloud iot registries create $IOT_CORE_REGISTRY --project=$PROJECT --region=$REGION \ 128 | --event-notification-config=topic=projects/$PROJECT/topics/$SENSOR_TOPIC,subfolder=$SENSOR_TOPIC \ 129 | --event-notification-config=topic=projects/$PROJECT/topics/$COMMAND_TOPIC,subfolder=$COMMAND_TOPIC \ 130 | | true 131 | fi 132 | 133 | if ls | grep rsa_private.pem; then 134 | echo_my "Private Key Pairs exist for IOT Core" 135 | else 136 | generate_keys 137 | fi 138 | 139 | if gcloud iot devices list --project=$PROJECT --region=$REGION --registry=$IOT_CORE_REGISTRY | grep $IOT_CORE_DEVICE_ID; then 140 | echo_my "IOT Core Device ID '$IOT_CORE_DEVICE_ID' already registered." 141 | else 142 | echo_my "Registering IOT Core Device ID '$IOT_CORE_DEVICE_ID'..." 143 | gcloud iot devices create $IOT_CORE_DEVICE_ID --project=$PROJECT --region=$REGION --registry=$IOT_CORE_REGISTRY \ 144 | --public-key path=rsa_cert.pem,type=rs256 | true 145 | fi 146 | 147 | create_gcs_camera_bucket 148 | 149 | cd py 150 | 151 | # Start the car 152 | if [[ $# -gt 0 && "$1" != "" && "$1" == "--non-interactive" ]] ; then 153 | ./drive.py $PROJECT $COMMAND_SUBSCRIPTION --non-interactive 154 | else 155 | ./drive.py $PROJECT $COMMAND_SUBSCRIPTION 156 | fi -------------------------------------------------------------------------------- /cloud/controller/js/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | const MANUAL_MODE = require('./drive-message').MANUAL_MODE; 18 | const AUTOMATIC_MODE = require('./drive-message').AUTOMATIC_MODE; 19 | const DEBUG_MODE = require('./drive-message').DEBUG_MODE; 20 | 21 | /************************************************************ 22 | immutable constant options 23 | ************************************************************/ 24 | const BALL_COLORS = ["red", "blue", "green", "yellow"]; 25 | const DRIVING_MODES = [AUTOMATIC_MODE, MANUAL_MODE, DEBUG_MODE]; 26 | const CONFIG_PARAMS = ["ballColor", "currentDrivingMode", "listenerStatus"]; 27 | // map the client params to field names for now 28 | const DRIVING_MESSAGE_PARAMS = { 29 | "turnSpeed": "turn_speed_field", 30 | "angle": "angle_field", 31 | "driveSpeed": "drive_speed_field", 32 | "distance": "distance_field", 33 | "gripperOpen": "gripper_open", 34 | "gripperClosed": "gripper_closed", 35 | "ondemandMessages": "ondemand_messages", 36 | "nonstopMessages": "nonstop_messages" 37 | }; 38 | const DEBUG_MESSAGE_PARAMS = { 39 | "sendCommand": "send_command", "nextSensorMessage": "next_sensor_message" 40 | }; 41 | 42 | /************************************************************ 43 | Validate the configuration parameters based of required 44 | Inputs: 45 | - request, response, next 46 | Returns: 47 | - response or next 48 | ************************************************************/ 49 | module.exports.configParams = function (req, res, next) { 50 | var errors = []; 51 | 52 | // validParams validation 53 | var valid = false; 54 | var validParams = CONFIG_PARAMS; 55 | for (var i = 0; i < validParams.length; i++) { 56 | if (typeof req.body[validParams[i]] != 'undefined') { 57 | valid = true; 58 | break; 59 | } 60 | } 61 | if (!valid) { 62 | errors.push(`Valid request param required: [${validParams}]`); 63 | } 64 | if (errors.length > 0) { 65 | return res.status(400).json({ 66 | success: false, errors: errors, status: 400 67 | }) 68 | } 69 | 70 | // ballColor validation 71 | if (typeof req.body.ballColor != 'undefined' && BALL_COLORS.indexOf(req.body.ballColor) == -1) { 72 | errors.push("Valid ballColor required"); 73 | } 74 | 75 | // currentDrivingMode validation 76 | if (typeof req.body.currentDrivingMode != 'undefined' && DRIVING_MODES.indexOf(req.body.currentDrivingMode) == -1) { 77 | errors.push("Valid currentDrivingMode required"); 78 | } 79 | 80 | if (errors.length > 0) { 81 | return res.status(400).json({ 82 | success: false, errors: errors, status: 400 83 | }) 84 | } 85 | return next(); 86 | }; 87 | 88 | /************************************************************ 89 | Validate the driving Message parameters 90 | Inputs: 91 | - request, response, next 92 | Returns: 93 | - response or next 94 | ************************************************************/ 95 | module.exports.drivingMessageParams = function (req, res, next) { 96 | var errors = []; 97 | 98 | // validParams validation. We need the keys here 99 | var valid = false; 100 | var validParams = Object.keys(DRIVING_MESSAGE_PARAMS); 101 | for (var i = 0; i < validParams.length; i++) { 102 | if (typeof req.body[validParams[i]] != 'undefined') { 103 | valid = true; 104 | break; 105 | } 106 | } 107 | if (!valid) { 108 | errors.push(`Valid request param required: [${validParams}]`); 109 | } 110 | if (errors.length > 0) { 111 | return res.status(400).json({ 112 | success: false, errors: errors, status: 400 113 | }) 114 | } 115 | 116 | // range checks 117 | var rangeParams = ["turnSpeed", "drivingSpeed"]; 118 | for (var i = 0; i < rangeParams.length; i++) { 119 | var paramName = rangeParams[i]; 120 | if (typeof req.body[paramName] != 'undefined' && !(req.body[paramName] >= 1 && req.body[paramName] <= 1000)) { 121 | errors.push(`Valid ${paramName} required: number [1-1000]`); 122 | } 123 | } 124 | 125 | // number checks 126 | var numberParams = ["angle", "distance"]; 127 | for (var i = 0; i < numberParams.length; i++) { 128 | var paramName = numberParams[i]; 129 | if (typeof req.body[paramName] != 'undefined' && isNaN(req.body[paramName])) { 130 | errors.push(`Valid ${paramName} required: number`); 131 | } 132 | } 133 | 134 | // boolean param checks 135 | var booleanParams = ["gripperOpen", "gripperClosed", "ondemandMessages", "nonstopMessages"]; 136 | for (var i = 0; i < booleanParams.length; i++) { 137 | var paramName = booleanParams[i]; 138 | if (typeof req.body[paramName] != 'undefined' && !(req.body[paramName] === true || req.body[paramName] == false)) { 139 | errors.push(`Valid ${paramName} required: boolean`); 140 | } 141 | } 142 | 143 | if (errors.length > 0) { 144 | return res.status(400).json({ 145 | success: false, errors: errors, status: 400 146 | }) 147 | } 148 | return next(); 149 | }; 150 | 151 | /************************************************************ 152 | Validate the debug Message parameters 153 | Inputs: 154 | - request, response, next 155 | Returns: 156 | - response or next 157 | ************************************************************/ 158 | module.exports.debugMessageParams = function (req, res, next) { 159 | var errors = []; 160 | 161 | // validParams validation. We need the keys here 162 | var valid = false; 163 | var validParams = Object.keys(DEBUG_MESSAGE_PARAMS); 164 | for (var i = 0; i < validParams.length; i++) { 165 | if (typeof req.body[validParams[i]] != 'undefined') { 166 | valid = true; 167 | break; 168 | } 169 | } 170 | if (!valid) { 171 | errors.push(`Valid request param required: [${validParams}]`); 172 | } 173 | if (errors.length > 0) { 174 | return res.status(400).json({ 175 | success: false, errors: errors, status: 400 176 | }) 177 | } 178 | 179 | // boolean param checks 180 | var booleanParams = ["sendCommand", "nextSensorMessage"]; 181 | for (var i = 0; i < booleanParams.length; i++) { 182 | var paramName = booleanParams[i]; 183 | if (typeof req.body[paramName] != 'undefined' && !(req.body[paramName] === true || req.body[paramName] == false)) { 184 | errors.push(`Valid ${paramName} required: boolean`); 185 | } 186 | } 187 | 188 | if (errors.length > 0) { 189 | return res.status(400).json({ 190 | success: false, errors: errors, status: 400 191 | }) 192 | } 193 | return next(); 194 | }; 195 | 196 | /************************************************************************** 197 | Module exports 198 | **************************************************************************/ 199 | module.exports.DRIVING_MESSAGE_PARAMS = DRIVING_MESSAGE_PARAMS; 200 | module.exports.BALL_COLORS = BALL_COLORS; 201 | module.exports.DRIVING_MODES = DRIVING_MODES; -------------------------------------------------------------------------------- /third_party/dexter/robotderbycar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # https://github.com/DexterInd/GoPiGo3/blob/master/LICENSE.md 4 | # 5 | # MIT License 6 | # Copyright (c) 2017 Dexter Industries 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 8 | # (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 15 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 17 | # IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | # 19 | # Based on https://github.com/DexterInd/GoPiGo3/blob/master/Software/Python/easygopigo3.py 20 | # 21 | 22 | import easygopigo3 23 | import time 24 | 25 | class RobotDerbyCar(easygopigo3.EasyGoPiGo3): 26 | """ 27 | This class is used for controlling a `RobotDerbyCar`_ robot. 28 | With this class you can do the following things with your `RobotDerbyCar`_: 29 | * Drive your robot while avoiding obstacles 30 | * Inheriting all EasyGoPiGo3 functionality: https://github.com/DexterInd/GoPiGo3/blob/master/Software/Python/easygopigo3.py 31 | * Inheriting all GoPiGo3 functionality: https://github.com/DexterInd/GoPiGo3/blob/master/Software/Python/gopigo3.py 32 | * Set the grippers of the robot to Open or Close positions 33 | """ 34 | 35 | def __init__(self): 36 | """ 37 | This constructor sets the variables to the following values: 38 | : CONST_GRIPPER_FULL_OPEN : Position of gripper servo when open 39 | : CONST_GRIPPER_FULL_CLOSE: Position of gripper servo when closed 40 | : CONST_GRIPPER_FULL_OPEN : Position of gripper servo to grab ball 41 | : easygopigo3.EasyGoPiGo3 Easy_GPG: Initialization of EasyGoPiGo3 42 | : easygopigo3 Easy_GPG: Initialization of EasyGoPiGo3 43 | : easygopigo3.Servo gpgGripper: Initialization of Gripper Servo on Servo Pin 1 44 | : init_distance_sensor my_distance_sensor: Initialization of Distance Sensor 45 | : IOError: When the GoPiGo3 is not detected. It also debugs a message in the terminal. 46 | : gopigo3.FirmwareVersionError: If the GoPiGo3 firmware needs to be updated. It also debugs a message in the terminal. 47 | : Exception: For any other kind of exceptions. 48 | """ 49 | 50 | # Settings for cars in US Reston Office (these grippers were built differently) 51 | self.CONST_GRIPPER_FULL_OPEN = 90 52 | self.CONST_GRIPPER_FULL_CLOSE = 0 53 | self.CONST_GRIPPER_GRAB_POSITION = 40 54 | 55 | # Settings for cars in London Office (default method of assembly for grippers) 56 | #self.CONST_GRIPPER_FULL_OPEN = 180 57 | #self.CONST_GRIPPER_FULL_CLOSE = 20 58 | #self.CONST_GRIPPER_GRAB_POSITION = 120 59 | 60 | self.Easy_GPG = easygopigo3.EasyGoPiGo3() # Create an instance of the GoPiGo3 class. GPG will be the GoPiGo3 object. 61 | self.gpgGripper = easygopigo3.Servo("SERVO1", self.Easy_GPG) 62 | self.my_distance_sensor = self.Easy_GPG.init_distance_sensor() 63 | self.SetLEDsGreen() 64 | 65 | def SetLEDsYellow(self): 66 | self.Easy_GPG.set_left_eye_color((255,255,0)) 67 | self.Easy_GPG.set_right_eye_color((255,255,0)) 68 | self.Easy_GPG.open_left_eye() 69 | self.Easy_GPG.open_right_eye() 70 | 71 | def SetLEDsGreen(self): 72 | self.Easy_GPG.set_left_eye_color((1,255,1)) 73 | self.Easy_GPG.set_right_eye_color((1,255,1)) 74 | self.Easy_GPG.open_left_eye() 75 | self.Easy_GPG.open_right_eye() 76 | 77 | def SetLEDsRed(self): 78 | self.Easy_GPG.set_left_eye_color((255,1,1)) 79 | self.Easy_GPG.set_right_eye_color((255,1,1)) 80 | self.Easy_GPG.open_left_eye() 81 | self.Easy_GPG.open_right_eye() 82 | 83 | def GripperClose(self): 84 | self.SetLEDsRed() 85 | self.gpgGripper.rotate_servo(self.CONST_GRIPPER_GRAB_POSITION) 86 | self.SetLEDsGreen() 87 | 88 | def GripperOpen(self): 89 | self.SetLEDsRed() 90 | self.gpgGripper.rotate_servo(self.CONST_GRIPPER_FULL_OPEN) 91 | self.SetLEDsGreen() 92 | 93 | def ReadDistanceMM(self): 94 | return self.my_distance_sensor.read_mm() 95 | 96 | def ReadBatteryVoltage(self): 97 | return self.Easy_GPG.get_voltage_battery() 98 | 99 | def set_speed(self,speed): 100 | self.SetLEDsRed() 101 | self.Easy_GPG.set_speed(speed) 102 | self.SetLEDsGreen() 103 | 104 | def drive_cm(self,distance): 105 | self.SetLEDsRed() 106 | self.Easy_GPG.drive_cm(distance,True) 107 | self.SetLEDsGreen() 108 | 109 | def turn_degrees(self,degress): 110 | self.SetLEDsRed() 111 | self.Easy_GPG.turn_degrees(degress,True) 112 | self.SetLEDsGreen() 113 | 114 | def drive(self,dist_requested,dist_limit): 115 | """ 116 | Move the `GoPiGo3`_ forward / backward for ``dist`` amount of miliimeters. 117 | | For moving the `GoPiGo3`_ robot forward, the ``dist`` parameter has to be *positive*. 118 | | For moving the `GoPiGo3`_ robot backward, the ``dist`` parameter has to be *negative*. 119 | """ 120 | 121 | # Have we found any obstacles in the path 122 | ObstaclesFound = False 123 | 124 | # the number of degrees each wheel needs to turn 125 | WheelTurnDegrees = ((dist_requested / self.Easy_GPG.WHEEL_CIRCUMFERENCE) * 360) 126 | 127 | # get the starting position of each motor 128 | CurrentPositionLeft = self.Easy_GPG.get_motor_encoder(self.Easy_GPG.MOTOR_LEFT) 129 | CurrentPositionRight = self.Easy_GPG.get_motor_encoder(self.Easy_GPG.MOTOR_RIGHT) 130 | 131 | # determine the end position of each motor 132 | EndPositionLeft = CurrentPositionLeft + WheelTurnDegrees 133 | EndPositionRight = CurrentPositionRight + WheelTurnDegrees 134 | 135 | self.SetLEDsRed() 136 | self.Easy_GPG.set_motor_position(self.Easy_GPG.MOTOR_LEFT, EndPositionLeft) 137 | self.Easy_GPG.set_motor_position(self.Easy_GPG.MOTOR_RIGHT, EndPositionRight) 138 | 139 | while self.Easy_GPG.target_reached(EndPositionLeft, EndPositionRight) is False: 140 | # read the distance of the laser sensor 141 | dist_read = self.ReadDistanceMM() 142 | 143 | # stop car if there is an object within the limit 144 | if ((dist_read is not None) and (int(dist_read) <= int(dist_limit)) and (int(dist_requested) > int(dist_limit))): 145 | print("RobotDerbyCar.drive(): Obstacle Found. Stopping Car before requested distance. Object distance: " + str(dist_read)) 146 | ObstaclesFound = True 147 | CurrentPositionLeft = self.Easy_GPG.get_motor_encoder(self.Easy_GPG.MOTOR_LEFT) 148 | CurrentPositionRight = self.Easy_GPG.get_motor_encoder(self.Easy_GPG.MOTOR_RIGHT) 149 | self.Easy_GPG.set_motor_position(self.Easy_GPG.MOTOR_LEFT, CurrentPositionLeft) 150 | self.Easy_GPG.set_motor_position(self.Easy_GPG.MOTOR_RIGHT, CurrentPositionRight) 151 | break 152 | 153 | time.sleep(0.05) 154 | 155 | self.SetLEDsGreen() 156 | return ObstaclesFound 157 | -------------------------------------------------------------------------------- /admin/delete-hackathon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################################################################### 4 | # This script deletes hackathon event, including users and folders 5 | ############################################################################### 6 | 7 | # 8 | # Copyright 2018 Google LLC 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # https://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | set -u # This prevents running the script if any of the variables have not been set 24 | set -e # Exit if error is detected during pipeline execution 25 | 26 | source ./setenv.sh 27 | 28 | # How many Vms were deleted during the run 29 | VMS_DELETED=0 30 | # How many projects were found 31 | PROJECTS_FOUND=0 32 | # How many folders found 33 | FOLDERS_FOUND=0 34 | 35 | ############################################################################### 36 | # Remove all folders and projects under a designated folder ID 37 | # Input 38 | # 1 - folder ID under which all content is to be deleted 39 | ############################################################################### 40 | delete_folders() { 41 | local PARENT_FOLDER_ID=$1 42 | local FOLDER_ID 43 | 44 | FOLDERS_FOUND=$((FOLDERS_FOUND+1)) 45 | echo_my "Deleting projects directly under folder id '$PARENT_FOLDER_ID'..." 46 | delete_projects $PARENT_FOLDER_ID 47 | 48 | local FOLDER_LIST=$(gcloud alpha resource-manager folders list --folder=$PARENT_FOLDER_ID --format="value(name)") 49 | 50 | while read -r FOLDER_ID; do 51 | if [[ ! -z "$FOLDER_ID" ]] ; then 52 | echo_my "Recursively processing folders under folder id '$FOLDER_ID'..." 53 | delete_folders $FOLDER_ID 54 | if [ $DELETE_VM_ONLY = false ] ; then 55 | gcloud alpha resource-manager folders delete $FOLDER_ID | true # Ignore if error and proceed 56 | fi 57 | fi 58 | done <<< "$FOLDER_LIST" 59 | 60 | echo_my "Finally deleting the top folder '$PARENT_FOLDER_ID'..." 61 | gcloud alpha resource-manager folders delete $PARENT_FOLDER_ID | true # Ignore if error 62 | } 63 | 64 | ############################################################################### 65 | # Remove all projects under a given folder 66 | # Inputs 67 | # 1 - Folder ID 68 | ############################################################################### 69 | delete_projects() { 70 | echo_my "Deleting projects for folder '$1'..." 71 | local PROJECT_LIST=$(gcloud projects list --filter="parent.id=$1" --format="value(projectId)") 72 | local PROJ_ID 73 | 74 | while read -r PROJ_ID; do 75 | if [[ ! -z "$PROJ_ID" ]] ; then 76 | PROJECTS_FOUND=$((PROJECTS_FOUND+1)) 77 | echo_my "Processing project id '$PROJ_ID'..." 78 | if [ $DELETE_VM_ONLY = false ] ; then 79 | delete_project_liens $PROJ_ID 80 | yes | gcloud projects delete $PROJ_ID | true # Ignore if error and proceed 81 | else 82 | delete_vms $PROJ_ID 83 | fi 84 | fi 85 | done <<< "$PROJECT_LIST" 86 | } 87 | 88 | ############################################################################### 89 | # Remove all VMs in a project 90 | # Inputs 91 | # 1 - project ID 92 | ############################################################################### 93 | delete_vms() { 94 | local VM_ID 95 | echo_my "Deleting VMs for project '$1'..." 96 | 97 | local VM_LIST=$(gcloud compute instances list --project $1 --format="value(name)") 98 | 99 | while read -r VM_ID; do 100 | if [[ ! -z "$VM_ID" ]] ; then 101 | VMS_DELETED=$((VMS_DELETED+1)) 102 | # Get the zone of the instance 103 | local ZONE=$(gcloud compute instances list --filter="name:($VM_ID)" --project $1 --format="value(zone)") 104 | echo_my "Deleting VM id '$VM_ID'..." 105 | yes | gcloud compute instances delete $VM_ID --project $1 --zone=$ZONE | true # Ignore if error and proceed 106 | else 107 | echo_my "No more VMs found in this project" 108 | fi 109 | done <<< "$VM_LIST" 110 | } 111 | 112 | ############################################################################### 113 | # Remove all liens placed against projects by Dialogflow or other resources 114 | # Inputs 115 | # 1 - project ID 116 | ############################################################################### 117 | delete_project_liens() { 118 | local VM_ID 119 | echo_my "Deleting project liens for project '$1'..." 120 | 121 | local LIEN_LIST=$(gcloud alpha resource-manager liens list --project $1 --format="value(name)") 122 | 123 | while read -r LIEN_NAME; do 124 | if [[ ! -z "$LIEN_NAME" ]] ; then 125 | echo_my "Deleting lien name '$LIEN_NAME'..." 126 | yes | gcloud alpha resource-manager liens delete $LIEN_NAME | true # Ignore if error and proceed 127 | else 128 | echo_my "No more liens found for this project" 129 | fi 130 | done <<< "$LIEN_LIST" 131 | } 132 | 133 | ############################################################################### 134 | # Remove all users and groups 135 | ############################################################################### 136 | delete_everybody() { 137 | for i in $(seq $TEAM_START_NUM $NUM_TEAMS); 138 | do 139 | for j in $(seq 1 $NUM_PEOPLE_PER_TEAM); 140 | do 141 | $GAM delete user $(user_name $j $i) | true # ignore if error 142 | done 143 | 144 | $GAM delete group "$(team_name $i)" | true # ignore if error 145 | done 146 | } 147 | 148 | ############################################################################### 149 | # Reset all passwords for all auto-generated users 150 | ############################################################################### 151 | reset_passwords() { 152 | # Create empty file and overwrite the existing one 153 | echo "Email,Password" > $USER_LIST 154 | 155 | for i in $(seq $TEAM_START_NUM $NUM_TEAMS); 156 | do 157 | for j in $(seq 1 $NUM_PEOPLE_PER_TEAM); 158 | do 159 | local PASSWORD=$(generate_password) 160 | $GAM update user $(user_name $j $i) password $PASSWORD 161 | echo "$(user_name $j $i),$PASSWORD" >> $USER_LIST 162 | done 163 | done 164 | } 165 | 166 | ############################################################################### 167 | # MAIN 168 | ############################################################################### 169 | if [ -z ${1+x} ] ; then 170 | echo_my "NUMERIC_FOLDER_ID not found. \n Usage: delete-hackathon.sh [NUMERIC_FOLDER_ID] \n Example: \n ./delete-hackathon.sh 8763450985677 \n \n" $ECHO_ERROR 171 | exit 1 172 | fi 173 | 174 | FOLDER_TO_BE_DELETED=$1 175 | 176 | print_header "Delete workshop users, folders, etc." 177 | 178 | echo_my "\nATTENTION!!!!!!!!!!!\nATTENTION!!!!!!!!!!!\nATTENTION!!!!!!!!!!!\n" 179 | echo_my "This will remove all Users, Projects, Folders under the folder --- '$TOP_FOLDER' ---. Are you sure you want to proceed?" $ECHO_WARNING 180 | pause 181 | echo_my "\nAre you sure you want to delete all USERS, PROJECTS and FOLDERS???????" $ECHO_WARNING 182 | pause 183 | 184 | setup 185 | 186 | delete_everybody 187 | 188 | # If this is true, then folders and projects will not be deleted - only VMs 189 | DELETE_VM_ONLY=false 190 | 191 | if [ $DELETE_VM_ONLY = false ] ; then 192 | echo_my "DELETE_VM_ONLY=$DELETE_VM_ONLY - this means all projects, folders and VMs will be deleted." 193 | else 194 | echo_my "DELETE_VM_ONLY=$DELETE_VM_ONLY - this means only VMs will be deleted." 195 | fi 196 | 197 | delete_folders $FOLDER_TO_BE_DELETED 198 | 199 | echo_my "Found $PROJECTS_FOUND projects and $FOLDERS_FOUND folders." 200 | echo_my "Found and deleted $VMS_DELETED virtual machines." 201 | print_footer "SUCCESS: Workshop resources have been removed." -------------------------------------------------------------------------------- /car/driver/py/robotderbycar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # https://github.com/DexterInd/GoPiGo3/blob/master/LICENSE.md 4 | # 5 | # MIT License 6 | # Copyright (c) 2017 Dexter Industries 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 8 | # (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 15 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 17 | # IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | # 19 | # Based on https://github.com/DexterInd/GoPiGo3/blob/master/Software/Python/easygopigo3.py 20 | # 21 | 22 | import easygopigo3 23 | import time 24 | 25 | class RobotDerbyCar(easygopigo3.EasyGoPiGo3): 26 | """ 27 | This class is used for controlling a `RobotDerbyCar`_ robot. 28 | With this class you can do the following things with your `RobotDerbyCar`_: 29 | * Drive your robot while avoiding obstacles 30 | * Inheriting all EasyGoPiGo3 functionality: https://github.com/DexterInd/GoPiGo3/blob/master/Software/Python/easygopigo3.py 31 | * Inheriting all GoPiGo3 functionality: https://github.com/DexterInd/GoPiGo3/blob/master/Software/Python/gopigo3.py 32 | * Set the grippers of the robot to Open or Close positions 33 | """ 34 | 35 | def __init__(self): 36 | """ 37 | This constructor sets the variables to the following values: 38 | : CONST_GRIPPER_FULL_OPEN : Position of gripper servo when open 39 | : CONST_GRIPPER_FULL_CLOSE: Position of gripper servo when closed 40 | : CONST_GRIPPER_FULL_OPEN : Position of gripper servo to grab ball 41 | : easygopigo3.EasyGoPiGo3 Easy_GPG: Initialization of EasyGoPiGo3 42 | : easygopigo3 Easy_GPG: Initialization of EasyGoPiGo3 43 | : easygopigo3.Servo gpgGripper: Initialization of Gripper Servo on Servo Pin 1 44 | : init_distance_sensor my_distance_sensor: Initialization of Distance Sensor 45 | : IOError: When the GoPiGo3 is not detected. It also debugs a message in the terminal. 46 | : gopigo3.FirmwareVersionError: If the GoPiGo3 firmware needs to be updated. It also debugs a message in the terminal. 47 | : Exception: For any other kind of exceptions. 48 | """ 49 | 50 | # GoPiGo Color Codes 51 | self.YELLOW = (255, 255, 0) 52 | self.GREEN = (0, 255, 0) 53 | self.RED = (255, 0, 0) 54 | self.BLUE = (0, 0, 255) 55 | 56 | # Settings for cars in US Reston Office (these grippers were built differently) 57 | self.CONST_GRIPPER_FULL_OPEN = 90 58 | self.CONST_GRIPPER_FULL_CLOSE = 0 59 | self.CONST_GRIPPER_GRAB_POSITION = 40 60 | 61 | # Settings for cars in London Office (default method of assembly for grippers) 62 | #self.CONST_GRIPPER_FULL_OPEN = 180 63 | #self.CONST_GRIPPER_FULL_CLOSE = 20 64 | #self.CONST_GRIPPER_GRAB_POSITION = 120 65 | 66 | self.Easy_GPG = easygopigo3.EasyGoPiGo3() # Create an instance of the GoPiGo3 class. GPG will be the GoPiGo3 object. 67 | self.gpgGripper1 = easygopigo3.Servo("SERVO1", self.Easy_GPG) 68 | self.gpgGripper2 = easygopigo3.Servo("SERVO2", self.Easy_GPG) 69 | self.my_distance_sensor = self.Easy_GPG.init_distance_sensor() 70 | self.SetCarStatusLED(self.GREEN) 71 | 72 | def SetCarStatusLED(self,color): 73 | self.Easy_GPG.set_right_eye_color(color) 74 | self.Easy_GPG.open_right_eye() 75 | 76 | def SetCarModeLED(self,color): 77 | self.Easy_GPG.set_left_eye_color(color) 78 | self.Easy_GPG.open_left_eye() 79 | 80 | def SetBallModeLED(self,color): 81 | self.Easy_GPG.set_led(self.Easy_GPG.LED_WIFI,color[0],color[1],color[2]) 82 | 83 | def GripperClose(self): 84 | self.SetCarStatusLED(self.RED) 85 | self.gpgGripper1.rotate_servo(self.CONST_GRIPPER_GRAB_POSITION) 86 | self.gpgGripper2.rotate_servo(self.CONST_GRIPPER_GRAB_POSITION) 87 | self.SetCarStatusLED(self.GREEN) 88 | 89 | def GripperOpen(self): 90 | self.SetCarStatusLED(self.RED) 91 | self.gpgGripper1.rotate_servo(self.CONST_GRIPPER_FULL_OPEN) 92 | self.gpgGripper2.rotate_servo(self.CONST_GRIPPER_FULL_OPEN) 93 | self.SetCarStatusLED(self.GREEN) 94 | 95 | def ReadDistanceMM(self): 96 | return self.my_distance_sensor.read_mm() 97 | 98 | def ReadBatteryVoltage(self): 99 | return self.Easy_GPG.get_voltage_battery() 100 | 101 | def set_speed(self,speed): 102 | self.SetCarStatusLED(self.RED) 103 | self.Easy_GPG.set_speed(speed) 104 | self.SetCarStatusLED(self.GREEN) 105 | 106 | def drive_cm(self,distance): 107 | self.SetCarStatusLED(self.RED) 108 | self.Easy_GPG.drive_cm(distance,True) 109 | self.SetCarStatusLED(self.GREEN) 110 | 111 | def turn_degrees(self,degress): 112 | self.SetCarStatusLED(self.RED) 113 | self.Easy_GPG.turn_degrees(degress,True) 114 | self.SetCarStatusLED(self.GREEN) 115 | 116 | def drive(self,dist_requested,dist_limit): 117 | """ 118 | Move the `GoPiGo3`_ forward / backward for ``dist`` amount of miliimeters. 119 | | For moving the `GoPiGo3`_ robot forward, the ``dist`` parameter has to be *positive*. 120 | | For moving the `GoPiGo3`_ robot backward, the ``dist`` parameter has to be *negative*. 121 | """ 122 | 123 | # Have we found any obstacles in the path 124 | ObstaclesFound = False 125 | 126 | # the number of degrees each wheel needs to turn 127 | WheelTurnDegrees = ((dist_requested / self.Easy_GPG.WHEEL_CIRCUMFERENCE) * 360) 128 | 129 | # get the starting position of each motor 130 | CurrentPositionLeft = self.Easy_GPG.get_motor_encoder(self.Easy_GPG.MOTOR_LEFT) 131 | CurrentPositionRight = self.Easy_GPG.get_motor_encoder(self.Easy_GPG.MOTOR_RIGHT) 132 | 133 | # determine the end position of each motor 134 | EndPositionLeft = CurrentPositionLeft + WheelTurnDegrees 135 | EndPositionRight = CurrentPositionRight + WheelTurnDegrees 136 | 137 | self.SetCarStatusLED(self.RED) 138 | self.Easy_GPG.set_motor_position(self.Easy_GPG.MOTOR_LEFT, EndPositionLeft) 139 | self.Easy_GPG.set_motor_position(self.Easy_GPG.MOTOR_RIGHT, EndPositionRight) 140 | 141 | while self.Easy_GPG.target_reached(EndPositionLeft, EndPositionRight) is False: 142 | # read the distance of the laser sensor 143 | dist_read = self.ReadDistanceMM() 144 | 145 | # stop car if there is an object within the limit 146 | if ((dist_read is not None) and (int(dist_read) <= int(dist_limit)) and (int(dist_requested) > int(dist_limit))): 147 | print("RobotDerbyCar.drive(): Obstacle Found. Stopping Car before requested distance. Object distance: " + str(dist_read)) 148 | ObstaclesFound = True 149 | CurrentPositionLeft = self.Easy_GPG.get_motor_encoder(self.Easy_GPG.MOTOR_LEFT) 150 | CurrentPositionRight = self.Easy_GPG.get_motor_encoder(self.Easy_GPG.MOTOR_RIGHT) 151 | self.Easy_GPG.set_motor_position(self.Easy_GPG.MOTOR_LEFT, CurrentPositionLeft) 152 | self.Easy_GPG.set_motor_position(self.Easy_GPG.MOTOR_RIGHT, CurrentPositionRight) 153 | break 154 | 155 | time.sleep(0.05) 156 | 157 | self.SetCarStatusLED(self.GREEN) 158 | return ObstaclesFound 159 | -------------------------------------------------------------------------------- /cloud/controller/js/drive-message.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | // For some reason car gets stuck in limbo when the turn angle is 3 degrees or less 20 | const IGNORE_TURN_DEGREE = 3; 21 | 22 | // Command indicates that car has ball in sight and moving towards it 23 | const GO2BALL = "go2ball"; 24 | 25 | // Command indicates that Base is in sight and car is moving towards it 26 | const GO2BASE = "go2base"; 27 | 28 | // Command indicates that there is no ball of needed color in sight and car is looking for it by turning 29 | const SEEK_BALL_TURN = "seekBallTurn"; 30 | 31 | // Command indicates that there is no home base of needed color in sight and car is looking for it by turning 32 | const SEEK_HOME_TURN = "seekHomeTurn"; 33 | 34 | // Command indicates that there is no ball of needed color in sight and car is looking for it by moving 35 | const SEEK_BALL_MOVE = "seekBallMove"; 36 | 37 | // Command indicates that ball is within gripping distance - need to grab it now 38 | const CAPTURE_BALL = "captureBall"; 39 | 40 | // This means the goal of the command is to verify that ball has been gripped 41 | const CHECK_GRIP = "checkGrip"; 42 | 43 | // This indicated the end of the game when the car finished all tasks 44 | const GAME_END = "missionComplete"; 45 | 46 | // Command indicates that the car will be operated manually from the cloud user and all incoming sensor messages will be ignored 47 | // If this is not present in the command, this means car is in self-driving mode 48 | const MANUAL_MODE = "manual"; 49 | 50 | // Command indicates that the car is controlled by debugger 51 | const DEBUG_MODE = "debug"; 52 | 53 | // Command indicates that the car is controlled by debugger 54 | const AUTOMATIC_MODE = "automatic"; 55 | 56 | // Tells the car to keep taking pictures and continuously send them to the cloud non-stop 57 | const CONTINUOUS_SENSOR_RATE = "continuous"; 58 | 59 | // Tells the car to send sensor messages only when asked 60 | const ON_DEMAND_SENSOR_RATE = "onDemand"; 61 | 62 | /************************************************************************** 63 | Driving command message sent from cloud to the car. Example of a message: 64 | { "cloudTimestampMs": 1519592078172, 65 | "carTimestampMs": 1519592078100, 66 | "mode": "go2base", 67 | "actions": [ 68 | { "driveForward": 111 }, 69 | { "turnRight": 22 }, 70 | { "driveBackwardMm": 33 }, 71 | { "speed": 44 }, 72 | { "turnLeft": 55 }, 73 | { "driveForwardMm": 66 }, 74 | ...] 75 | } 76 | **************************************************************************/ 77 | class DriveMessage { 78 | 79 | constructor() { 80 | // Timestamp is generated at the time of creation of the message, not at the time of sending it 81 | this.cloudTimestampMs = new Date().getTime(); 82 | // Timestamp of the original message from the car as correlation ID 83 | // Car needs to validate this field against the latest info that it sent to the cloud 84 | this.carTimestampMs = undefined; 85 | // By default the mode is as follows 86 | this.mode = MANUAL_MODE; 87 | // How often does the car need to send sensor messages 88 | this.sensorRate = ON_DEMAND_SENSOR_RATE; 89 | // Array of commands to execute - could be a long list, in which case car will have to execute 90 | // those in sequence. The list can be arbitrarily long 91 | this.actions = []; 92 | } 93 | 94 | // Tells the car that one more ball has been captured 95 | addBallCount() { 96 | this.ballCaptured = 1; 97 | } 98 | 99 | // Takes four basic colors of balls as input 100 | setColor(color) { 101 | if (color == "Blue" || color == "Red" || color == "Green" || color == "Yellow") { 102 | this.actions.push({"setColor": color}); 103 | } 104 | } 105 | 106 | // This takes positive or negative angle and converts it into a proper command 107 | makeTurn(degrees) { 108 | if (degrees > 0) { 109 | this.turnRight(degrees); 110 | } else { 111 | this.turnLeft(degrees); 112 | } 113 | } 114 | 115 | // Cap max turn angle to 1000 degrees in any conditions 116 | turnLeft(degrees) { 117 | if (degrees < 0 && Math.abs(degrees) > IGNORE_TURN_DEGREE) { 118 | this.actions.push({"turnLeft": Math.min(degrees, 1000)}); 119 | } 120 | } 121 | 122 | // Cap max turn angle to 1000 degrees in any conditions 123 | turnRight(degrees) { 124 | if (degrees > IGNORE_TURN_DEGREE) { 125 | this.actions.push({"turnRight": Math.min(degrees, 1000)}); 126 | } 127 | } 128 | 129 | // This takes positive or negative value and converts it into a proper command 130 | // If speed is not explicitly set in the command, then drive at max speed 131 | drive(mm) { 132 | if (mm > 0) { 133 | this.driveForward(mm); 134 | } else { 135 | this.driveBackward(mm); 136 | } 137 | } 138 | 139 | // Cap max driving distance in any conditions to no more than 5 meters 140 | driveForward(mm) { 141 | if (mm > 0) { 142 | this.actions.push({"driveForwardMm": Math.min(mm, 5000)}); 143 | } 144 | } 145 | 146 | // Cap max driving distance in any conditions to no more than 5 meters 147 | driveBackward(mm) { 148 | if (mm <= 0) { 149 | this.actions.push({"driveBackwardMm": Math.min(mm, 5000)}); 150 | } 151 | } 152 | 153 | takePhoto() { 154 | this.actions.push({"takePhoto": true}); 155 | } 156 | 157 | // Speed must be more than 0 and less than 1000 (per GoPiGo docs, otherwise this command will be ignored 158 | setSpeed(speed) { 159 | if (speed > 0) { 160 | this.actions.push({"setSpeed": Math.min(speed, 1000)}); 161 | } 162 | } 163 | 164 | gripperOpen() { 165 | this.actions.push({"gripperPosition": "open"}); 166 | } 167 | 168 | gripperClose() { 169 | this.actions.push({"gripperPosition": "close"}); 170 | } 171 | 172 | sendSensorMessage() { 173 | this.actions.push({"sendSensorMessage": "true"}); 174 | } 175 | 176 | setGoalGo2Ball() { 177 | this.goal = GO2BALL; 178 | } 179 | 180 | setGoalGo2Base() { 181 | this.goal = GO2BASE; 182 | } 183 | 184 | setGoalGameEnd() { 185 | this.goal = GAME_END; 186 | } 187 | 188 | setGoalSeekBallTurn() { 189 | this.goal = SEEK_BALL_TURN; 190 | } 191 | 192 | setGoalSeekHomeTurn() { 193 | this.goal = SEEK_HOME_TURN; 194 | } 195 | 196 | setGoalSeekBallMove() { 197 | this.goal = SEEK_BALL_MOVE; 198 | } 199 | 200 | setGoalCaptureBall() { 201 | this.goal = CAPTURE_BALL; 202 | } 203 | 204 | setGoalCheck4Grip() { 205 | this.goal = CHECK_GRIP; 206 | } 207 | 208 | setModeManual() { 209 | this.mode = MANUAL_MODE; 210 | } 211 | 212 | setModeDebug() { 213 | this.mode = DEBUG_MODE; 214 | } 215 | 216 | setModeAutomatic() { 217 | this.mode = AUTOMATIC_MODE; 218 | } 219 | 220 | setOnDemandSensorRate() { 221 | this.sensorRate = ON_DEMAND_SENSOR_RATE; 222 | } 223 | 224 | setContinuousSensorRate() { 225 | this.sensorRate = CONTINUOUS_SENSOR_RATE; 226 | } 227 | 228 | setCorrelationID(timestampMs) { 229 | this.carTimestampMs = timestampMs; 230 | } 231 | } 232 | 233 | /************************************************************************** 234 | Module exports 235 | **************************************************************************/ 236 | module.exports.DriveMessage = DriveMessage; 237 | module.exports.SEEK_BALL_TURN = SEEK_BALL_TURN; 238 | module.exports.MANUAL_MODE = MANUAL_MODE; 239 | module.exports.DEBUG_MODE = DEBUG_MODE; 240 | module.exports.AUTOMATIC_MODE = AUTOMATIC_MODE; 241 | module.exports.CHECK_GRIP = CHECK_GRIP; 242 | module.exports.GO2BASE = GO2BASE; 243 | module.exports.SEEK_HOME_TURN = SEEK_HOME_TURN; -------------------------------------------------------------------------------- /cloud/controller/js/tests/api_success_tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var app = require('../app'); 18 | var chai = require('chai'); 19 | var request = require('supertest'); 20 | 21 | const BALL_COLORS = require('../validate').BALL_COLORS; 22 | const DRIVING_MODES = require('../validate').DRIVING_MODES; 23 | 24 | var expect = chai.expect; 25 | 26 | describe('API Successful Tests (/api)', function() { 27 | 28 | it('GET /: should return 200 and Welcome to the API! message', function(done) { 29 | request(app) 30 | .get('/api') 31 | .end(function(err, res) { 32 | expect(res.body.message).to.be.an('string').that.does.include('Welcome to the'); 33 | expect(res.body.success).to.be.true; 34 | expect(res.body.status).to.be.equal(200); 35 | expect(res.statusCode).to.be.equal(200); 36 | expect(res.headers).to.have.property('access-control-allow-origin'); 37 | done(); 38 | }); 39 | }); 40 | 41 | it('GET /stats: should return 200 and stats keys', function(done) { 42 | request(app) 43 | .get('/api/stats') 44 | .end(function(err, res) { 45 | var keys = ["totalErrors", "totalMessagesSent", "totalMessagesReceived", 46 | "rejectedFormatMessages", "rejectedOutOfOrderMessages"]; 47 | expect(res.body.data).to.all.keys(keys); 48 | expect(res.body.success).to.be.true; 49 | expect(res.body.status).to.be.equal(200); 50 | expect(res.statusCode).to.be.equal(200); 51 | expect(res.headers).to.have.property('access-control-allow-origin'); 52 | done(); 53 | }); 54 | }); 55 | 56 | it('GET /config/options: should return 200 and valid config options', function(done) { 57 | request(app) 58 | .get('/api/config/options') 59 | .end(function(err, res) { 60 | expect(res.body.data.ballColors).to.have.members(BALL_COLORS); 61 | expect(res.body.data.drivingModes).to.have.members(DRIVING_MODES); 62 | expect(res.body.success).to.be.true; 63 | expect(res.body.status).to.be.equal(200); 64 | expect(res.statusCode).to.be.equal(200); 65 | expect(res.headers).to.have.property('access-control-allow-origin'); 66 | done(); 67 | }); 68 | }); 69 | 70 | it('GET /config: should return 200 and config keys', function(done) { 71 | request(app) 72 | .get('/api/config') 73 | .end(function(err, res) { 74 | var keys = ["ballColor", "currentDrivingMode", "listenerStatus", "commandTopic", 75 | "sensorSubscription", "carId"]; 76 | expect(res.body.data).to.all.keys(keys); 77 | expect(res.body.success).to.be.true; 78 | expect(res.body.status).to.be.equal(200); 79 | expect(res.statusCode).to.be.equal(200); 80 | expect(res.headers).to.have.property('access-control-allow-origin'); 81 | done(); 82 | }); 83 | }); 84 | 85 | it('PUT /config: should return 200 for a sample payload', function(done) { 86 | request(app) 87 | .put('/api/config') 88 | .send({'ballColor': 'blue'}) 89 | .end(function(err, res) { 90 | expect(res.body.success).to.be.true; 91 | expect(res.body.status).to.be.equal(200); 92 | expect(res.statusCode).to.be.equal(200); 93 | expect(res.headers).to.have.property('access-control-allow-origin'); 94 | done(); 95 | }); 96 | }); 97 | 98 | it('GET /messages: should return 200 and messages arrays', function(done) { 99 | request(app) 100 | .get('/api/messages') 101 | .end(function(err, res) { 102 | expect(res.body.data.inboundMsgHistory).to.be.an('array'); 103 | expect(res.body.data.outboundMsgHistory).to.be.an('array'); 104 | expect(res.body.success).to.be.true; 105 | expect(res.body.status).to.be.equal(200); 106 | expect(res.statusCode).to.be.equal(200); 107 | expect(res.headers).to.have.property('access-control-allow-origin'); 108 | done(); 109 | }); 110 | }); 111 | 112 | it('POST /messages/debug: should return 201 for a sample payload', function(done) { 113 | request(app) 114 | .post('/api/messages/debug') 115 | .send({'sendCommand': true}) 116 | .end(function(err, res) { 117 | expect(res.body.success).to.be.true; 118 | expect(res.body.status).to.be.equal(201); 119 | expect(res.statusCode).to.be.equal(201); 120 | expect(res.headers).to.have.property('access-control-allow-origin'); 121 | done(); 122 | }); 123 | }); 124 | 125 | it('POST /messages/driving: should return 201 for gripperOpen true', function(done) { 126 | request(app) 127 | .post('/api/messages/driving') 128 | .send({'gripperOpen': true}) 129 | .end(function(err, res) { 130 | expect(res.body.success).to.be.true; 131 | expect(res.body.status).to.be.equal(201); 132 | expect(res.statusCode).to.be.equal(201); 133 | expect(res.headers).to.have.property('access-control-allow-origin'); 134 | done(); 135 | }); 136 | }); 137 | 138 | it('POST /messages/driving: should return 201 for gripperOpen false', function(done) { 139 | request(app) 140 | .post('/api/messages/driving') 141 | .send({'gripperOpen': false}) 142 | .end(function(err, res) { 143 | expect(res.body.success).to.be.true; 144 | expect(res.body.status).to.be.equal(201); 145 | expect(res.statusCode).to.be.equal(201); 146 | expect(res.headers).to.have.property('access-control-allow-origin'); 147 | done(); 148 | }); 149 | }); 150 | 151 | it('POST /messages/driving: should return 201 for drivingSpeed:100, distance:100', function(done) { 152 | request(app) 153 | .post('/api/messages/driving') 154 | .send({'drivingSpeed': 100, 'distance': 100}) 155 | .end(function(err, res) { 156 | expect(res.body.success).to.be.true; 157 | expect(res.body.status).to.be.equal(201); 158 | expect(res.statusCode).to.be.equal(201); 159 | expect(res.headers).to.have.property('access-control-allow-origin'); 160 | done(); 161 | }); 162 | }); 163 | 164 | it('POST /messages/driving: should return 201 for turnSpeed:200, angle:180', function(done) { 165 | request(app) 166 | .post('/api/messages/driving') 167 | .send({'turnSpeed': 200, 'angle': 180}) 168 | .end(function(err, res) { 169 | expect(res.body.success).to.be.true; 170 | expect(res.body.status).to.be.equal(201); 171 | expect(res.statusCode).to.be.equal(201); 172 | expect(res.headers).to.have.property('access-control-allow-origin'); 173 | done(); 174 | }); 175 | }); 176 | 177 | it('POST /messages/driving: should return 201 for turnSpeed:200, angle:-180', function(done) { 178 | request(app) 179 | .post('/api/messages/driving') 180 | .send({'turnSpeed': 100, 'angle': -180}) 181 | .end(function(err, res) { 182 | expect(res.body.success).to.be.true; 183 | expect(res.body.status).to.be.equal(201); 184 | expect(res.statusCode).to.be.equal(201); 185 | expect(res.headers).to.have.property('access-control-allow-origin'); 186 | done(); 187 | }); 188 | }); 189 | 190 | it('POST /messages/driving: should return 201 for drivingSpeed:100, distance:-100', function(done) { 191 | request(app) 192 | .post('/api/messages/driving') 193 | .send({'drivingSpeed': 100, 'distance': -100}) 194 | .end(function(err, res) { 195 | expect(res.body.success).to.be.true; 196 | expect(res.body.status).to.be.equal(201); 197 | expect(res.statusCode).to.be.equal(201); 198 | expect(res.headers).to.have.property('access-control-allow-origin'); 199 | done(); 200 | }); 201 | }); 202 | 203 | }); 204 | -------------------------------------------------------------------------------- /admin/ml-image-updates/js/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018, Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | console.log(`***Image processing is starting up***`); 18 | 19 | // Imports 20 | const process = require('process'); // Required for mocking environment variables 21 | const request = require('request'); 22 | 23 | const Storage = require('@google-cloud/storage'); 24 | const storage = new Storage(process.env.PROJECT); 25 | 26 | const VisionResponse = require('../../../cloud/controller/js/vision-response'); 27 | const BoundingBox = require('../../../cloud/controller/js/bounding-box'); 28 | 29 | // Constants 30 | const APP_URL = `http://${process.env.INFERENCE_IP}`; 31 | const HTTP_PORT = process.env.HTTP_PORT; 32 | const INFERENCE_URL = process.env.INFERENCE_URL; 33 | const OBJECT_INFERENCE_API_URL = APP_URL + ':' + HTTP_PORT + INFERENCE_URL; 34 | // User credentials to authenticate to remote Inference VM service 35 | const INFERENCE_USER_NAME = process.env.INFERENCE_USER_NAME; 36 | const INFERENCE_PASSWORD = process.env.INFERENCE_PASSWORD; 37 | const ALL_OBJECT_LABELS = process.env.ALL_OBJECT_LABELS.split(" "); 38 | const DESTINATION_BUCKET = process.env.DESTINATION_BUCKET; 39 | 40 | // Global counter of processed files 41 | let successCount = 0; 42 | 43 | /************************************************************ 44 | Scan all files in the bucket 45 | Great examples of API use can be found here: 46 | https://github.com/googleapis/nodejs-storage/blob/99e3b17f0b12ea66ed46060ca291124b10772111/samples/files.js#L26 47 | ************************************************************/ 48 | 49 | /************************************************************ 50 | Process one file 51 | Input: 52 | - Source Bucket 53 | - Source File 54 | Output: 55 | - None 56 | ************************************************************/ 57 | async function processOneFileAsync(srcBucket, srcFile) { 58 | let visionResponse = await recognizeObjectsAsync('gs://' + srcBucket + '/' + srcFile); 59 | // console.log('processOneFileAsync(): vision response: ' + JSON.stringify(visionResponse)); 60 | 61 | for (let label of ALL_OBJECT_LABELS) { 62 | let prefix = findObject(label, visionResponse); 63 | await gcsCopy(srcBucket, srcFile, DESTINATION_BUCKET, prefix + srcFile); 64 | } 65 | 66 | // After all labels have been processed, we delete the file from the source bucket 67 | gcsDelete(srcBucket, srcFile) 68 | .catch(function (error) { 69 | console.error("!!!!!!!!!!!!! Error deleting file: " + error); 70 | }); 71 | // console.log('Finished processing file ' + srcFile); 72 | } 73 | 74 | /************************************************************ 75 | Copy file from one bucket into another 76 | Input: 77 | - source GCS URI 78 | - destination GCS URI 79 | ************************************************************/ 80 | async function gcsCopy(srcBucket, srcFile, destBucket, destFile) { 81 | console.log('Copy from to '); 82 | await storage.bucket(srcBucket).file(srcFile).copy(storage.bucket(destBucket).file(destFile)) 83 | .catch(function (error) { 84 | console.error('!!!!!!!!!!!!! ERROR: Failed to copy a file: ' + destFile + ' with error: ' + error); 85 | }); 86 | } 87 | 88 | /************************************************************ 89 | Delete file from the bucket 90 | Input: 91 | - source GCS URI 92 | ************************************************************/ 93 | async function gcsDelete(bucket, file) { 94 | // console.log('Deleting file: '+file); 95 | storage.bucket(bucket).file(file).delete() 96 | .catch(function (error) { 97 | console.error("!!!!!!!!!!!! Failed to delete a file: " + error); 98 | }); 99 | } 100 | 101 | /************************************************************ 102 | Find the right object based on objectType input in the image 103 | Input: 104 | - object label 105 | - list of object bounding boxes found by Object Detection 106 | Output: 107 | - prefix file path for the future file in the format: 108 | "/<#objects>___file_" 109 | or for cases when object is not found: 110 | "/not_found/" 111 | ************************************************************/ 112 | function findObject(objectType, visionResponse) { 113 | let isFound = false; 114 | let count = 0; 115 | let highScore = 0.0; 116 | let lowScore = 1.0; 117 | for (let i = visionResponse.bBoxes.length; i--;) { 118 | // Is this the right object type? 119 | if (visionResponse.bBoxes[i].label.toLocaleLowerCase().indexOf(objectType.toLowerCase()) >= 0) { 120 | isFound = true; 121 | count++; 122 | let score = parseFloat(visionResponse.bBoxes[i].score); 123 | if (highScore < score) { 124 | highScore = score; 125 | } 126 | if (lowScore > score) { 127 | lowScore = score; 128 | } 129 | } 130 | } 131 | 132 | let response = objectType + '/none/'; 133 | 134 | if (isFound) { 135 | response = objectType + 136 | '/count_' + count + 137 | '_high_' + highScore.toFixed(4).split('.')[1] + 138 | '_low_' + lowScore.toFixed(4).split('.')[1] + 139 | '_file_'; 140 | } 141 | return response; 142 | } 143 | 144 | /************************************************************ 145 | Call Vision API to recognize objects in the file 146 | Input: full path to GCS object 147 | Output: VisionResponse object 148 | ************************************************************/ 149 | async function recognizeObjectsAsync(gcsPath) { 150 | console.log('recognizeObjectsAsync(): ' + gcsPath); 151 | 152 | // Call REST API Object Detection 153 | // this returns a Promise which when resolved returns the VisionResponse object 154 | return recognizeObjectAPIAsync(gcsPath) 155 | .then((response) => { 156 | return Promise.resolve() 157 | .then(() => { 158 | return createVisionResponse(response); 159 | }); 160 | }) 161 | .catch((error) => { 162 | console.error("!!!!!!!!!!!!!!! Error calling remote Object Detection API: " + error); 163 | throw error; 164 | }); 165 | } 166 | 167 | /************************************************************ 168 | Generate response from the ML Vision 169 | Input: 170 | - jsonAPIResponse - response from the Vision API 171 | Output: 172 | - VisionResponse - Coordinates of various objects that were recognized 173 | ************************************************************/ 174 | function createVisionResponse(jsonAPIResponse) { 175 | let response = new VisionResponse(); 176 | const objResponse = JSON.parse(jsonAPIResponse); 177 | 178 | for (let key in objResponse) { 179 | for (let i = 0; i < objResponse[key].length; i++) { 180 | //console.log("objResponse[key]["+i+"]: "+JSON.stringify(objResponse[key][i])); 181 | const bBox = new BoundingBox(key, objResponse[key][i]["x"], objResponse[key][i]["y"], objResponse[key][i]["w"], objResponse[key][i]["h"], objResponse[key][i]["score"]); 182 | response.addBox(bBox); 183 | } 184 | } 185 | return response; 186 | } 187 | 188 | /************************************************************ 189 | Generate response from the ML Vision 190 | Input: 191 | - sensorMessage - message from the car with sensor data 192 | Output: 193 | - 194 | ************************************************************/ 195 | function recognizeObjectAPIAsync(gcsURI) { 196 | return new Promise(function (resolve, reject) { 197 | 198 | if (!gcsURI) { 199 | reject("!!!!!!!!!! Error: No gcURI found in sensorMessage"); 200 | 201 | } else if (!gcsURI.startsWith("gs://")) { 202 | reject("!!!!!!!!!! Error: gcsURI must start with gs://"); 203 | 204 | } else { 205 | // Example request for the inference VM: 206 | // http://xx.xx.xx.xx:8082/v1/objectInference?gcs_uri=gs%3A%2F%2Fcamera-9-roman-test-oct9%2Fimage1.jpg 207 | const apiUrl = OBJECT_INFERENCE_API_URL + "?gcs_uri=" + encodeURIComponent(gcsURI); 208 | const auth = {user: INFERENCE_USER_NAME, pass: INFERENCE_PASSWORD}; 209 | 210 | // Measure the time it takes to call inference API 211 | const startTime = Date.now(); 212 | 213 | request({uri: apiUrl, auth: auth}, function (err, response, body) { 214 | if (err) { 215 | console.error("!!!!!!!!! ERROR calling remote ML API: " + err + ". Please verify that your Inference VM and the App are up and running and proper HTTP port is open in the firewall."); 216 | reject(err); 217 | } else { 218 | console.log("Vision API call took " + (Date.now() - startTime) + " ms. URI: " + apiUrl); 219 | if (response.statusCode !== 200) { 220 | reject("!!!!!!!!!!!! Error: Received " + response.statusCode + " from API"); 221 | } else { 222 | resolve(body); 223 | } 224 | } 225 | }); 226 | } 227 | }); 228 | } 229 | 230 | /************************************************************ 231 | Recursively process list of files 232 | Input: 233 | - List of files to be processed 234 | Output: 235 | - None 236 | ************************************************************/ 237 | async function processFilesAsync(files) { 238 | for (let file of files) { 239 | console.log('--- #' + successCount + ': ' + file.name); 240 | if (DEBUG_COUNT > MAX_COUNT) { 241 | break; 242 | } else { 243 | DEBUG_COUNT++; 244 | } 245 | 246 | await processOneFileAsync(process.env.CLOUD_BUCKET, file.name) 247 | .then(() => { 248 | console.log('=== done: #' + successCount + ': ' + file.name); 249 | successCount++; 250 | }) 251 | .catch(function (error) { 252 | console.error('!!! Error processing file <' + file.name + '> with the error: ' + error); 253 | }); 254 | } 255 | } 256 | 257 | /************************************************************ 258 | MAIN 259 | ************************************************************/ 260 | console.log("Starting up: Vroom Vroom..."); 261 | 262 | let DEBUG_COUNT = 0; 263 | const MAX_COUNT = 50000; 264 | 265 | let bucket = storage.bucket(process.env.CLOUD_BUCKET); 266 | 267 | // bucket.getFiles({}, (err, files) => {console.log(err,files)}); 268 | bucket.getFiles({}, (err, files) => { 269 | if (err) { 270 | console.error('!!! ERROR listing of files in bucket <: ' + process.env.CLOUD_BUCKET + '>: ' + err); 271 | } else { 272 | console.log('Bucket <' + process.env.CLOUD_BUCKET + '> contains <' + files.length + '> files.'); 273 | processFilesAsync(files).then(() => { 274 | console.log('# of files processed successfully: ' + successCount); 275 | }) 276 | } 277 | }); -------------------------------------------------------------------------------- /admin/create-hackathon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################################################################### 4 | # This script creates new hackathon event with users and folders generated 5 | ############################################################################### 6 | 7 | # 8 | # Copyright 2018 Google LLC 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # https://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | set -u # This prevents running the script if any of the variables have not been set 24 | set -e # Exit if error is detected during pipeline execution 25 | 26 | source ./setenv.sh 27 | 28 | ### File with the list of user names and passwords 29 | USER_LIST="$TMP/users.csv" 30 | 31 | ############################################################################### 32 | # Create special Read Only group 33 | ############################################################################### 34 | create_read_only_group() { 35 | echo_my "Create read only group '$ADMIN_READ_GROUP'..." 36 | $GAM create group "$ADMIN_READ_GROUP" name "Resource Read-Only group" description "Read only access to common resources" | true # ignore if error 37 | echo_my "Permissions for project '$ADMIN_PROJECT_ID'..." 38 | COMMAND="gcloud projects add-iam-policy-binding $ADMIN_PROJECT_ID --member=group:$ADMIN_READ_GROUP --role=roles/" 39 | 40 | echo_my "All users need to be able to read pre-annotated images..." 41 | eval ${COMMAND}storage.objectViewer 42 | 43 | echo_my "All users need to be able to use Windows VM image we provide for annotations with some software pre-installed on it..." 44 | eval ${COMMAND}compute.imageUser 45 | 46 | echo_my "All users need to be able to lookup IP address of the DEMO Inference VM from project '$DEMO_PROJECT'..." 47 | gcloud projects add-iam-policy-binding $DEMO_PROJECT --member=group:$ADMIN_READ_GROUP --role="roles/compute.networkUser" 48 | } 49 | 50 | ############################################################################### 51 | # Add user to the domain: https://developers.google.com/admin-sdk/directory/v1/guides/manage-users 52 | # Input: 53 | # 1 - user number 54 | # 2 - team number 55 | ############################################################################### 56 | add_user() { 57 | local USER_NUM=$1 58 | local TEAM_NUM=$2 59 | local PASSWORD=$(generate_password) 60 | 61 | $GAM create user $(user_name $USER_NUM $TEAM_NUM) firstname "User$USER_NUM" lastname "Member of Team $TEAM_NUM" \ 62 | password $PASSWORD 63 | 64 | echo "$(user_name $USER_NUM $TEAM_NUM)@$DOMAIN,$PASSWORD" >> ${USER_LIST} 65 | } 66 | 67 | ############################################################################### 68 | # Add new team to the domain 69 | # Input: 70 | # 1 - team number 71 | ############################################################################### 72 | create_team() { 73 | local TEAM_NUM=$1 74 | echo_my "create_team(): Creating team #$TEAM_NUM..." 75 | 76 | $GAM create group "$(team_name $TEAM_NUM)" name "Car team $TEAM_NUM" description \ 77 | "Developers working on the car # $TEAM_NUM" | true # ignore if error 78 | 79 | for j in $(seq 1 $NUM_PEOPLE_PER_TEAM); 80 | do 81 | add_user $j $TEAM_NUM 82 | echo_my "Adding user to his team group..." 83 | $GAM update group "$(team_name $TEAM_NUM)" add member $(user_name $j $TEAM_NUM)@$DOMAIN 84 | echo_my "Adding user to the read-only group for shared resources..." 85 | $GAM update group "$ADMIN_READ_GROUP" add member $(user_name $j $TEAM_NUM)@$DOMAIN 86 | done 87 | } 88 | 89 | ############################################################################### 90 | # Create all groups 91 | ############################################################################### 92 | create_groups_and_users() { 93 | echo_my "create_groups_and_users(): started..." 94 | echo "Email,Password" > $USER_LIST 95 | 96 | for i in $(seq $TEAM_START_NUM $NUM_TEAMS); 97 | do 98 | create_team $i 99 | done 100 | } 101 | 102 | ############################################################################### 103 | # Create folders and projects in GCP 104 | ############################################################################### 105 | create_folders() { 106 | echo_my "create_folders(): Creating event parent folder..." 107 | gcloud alpha resource-manager folders create --display-name=$TOP_FOLDER --organization=$(lookup_org_id) \ 108 | | true # ignore if already exists 109 | 110 | echo_my "Creating children folders for each car team..." 111 | local PARENT_FOLDER_ID=$(find_top_folder_id $TOP_FOLDER) 112 | 113 | for i in $(seq $TEAM_START_NUM $NUM_TEAMS); 114 | do 115 | gcloud alpha resource-manager folders create --display-name=$(team_folder_name $i) --folder=$PARENT_FOLDER_ID \ 116 | | true # ignore if already exists 117 | 118 | local NEW_FOLDER_ID=$(find_folder_id $(team_folder_name $i) $PARENT_FOLDER_ID) 119 | echo_my "NEW_FOLDER_ID=$NEW_FOLDER_ID" 120 | 121 | # See docs: https://cloud.google.com/iam/docs/understanding-roles 122 | gcloud alpha resource-manager folders add-iam-policy-binding $NEW_FOLDER_ID \ 123 | --member=group:$(team_name $i)@$DOMAIN --role="organizations/$(lookup_org_id)/roles/$DERBY_DEV_ROLE" 124 | 125 | local COMMAND="gcloud alpha resource-manager folders add-iam-policy-binding $NEW_FOLDER_ID --member=group:$(team_name $i)@$DOMAIN --role=roles/" 126 | 127 | eval ${COMMAND}resourcemanager.projectCreator 128 | eval ${COMMAND}resourcemanager.folderEditor 129 | eval ${COMMAND}resourcemanager.folderIamAdmin 130 | eval ${COMMAND}resourcemanager.projectIamAdmin 131 | eval ${COMMAND}resourcemanager.folderCreator 132 | eval ${COMMAND}resourcemanager.projectDeleter 133 | eval ${COMMAND}appengine.appAdmin 134 | eval ${COMMAND}dialogflow.admin 135 | eval ${COMMAND}ml.admin 136 | eval ${COMMAND}pubsub.admin 137 | eval ${COMMAND}storage.admin 138 | eval ${COMMAND}iam.serviceAccountAdmin 139 | eval ${COMMAND}iam.serviceAccountKeyAdmin 140 | eval ${COMMAND}iam.serviceAccountTokenCreator 141 | eval ${COMMAND}iam.serviceAccountUser 142 | eval ${COMMAND}iam.securityReviewer 143 | eval ${COMMAND}servicemanagement.quotaAdmin 144 | eval ${COMMAND}errorreporting.admin 145 | eval ${COMMAND}logging.admin 146 | eval ${COMMAND}monitoring.admin 147 | eval ${COMMAND}cloudiot.admin 148 | eval ${COMMAND}compute.instanceAdmin.v1 149 | eval ${COMMAND}compute.imageUser 150 | eval ${COMMAND}compute.networkAdmin 151 | eval ${COMMAND}compute.securityAdmin 152 | eval ${COMMAND}source.admin 153 | eval ${COMMAND}clouddebugger.user 154 | eval ${COMMAND}editor 155 | done 156 | } 157 | 158 | ############################################# 159 | # Create Special Cloud Derby Role - this is only used for the service account permissions 160 | ############################################# 161 | create_role() { 162 | if gcloud iam roles list --organization $(lookup_org_id) | grep -q $DERBY_DEV_ROLE; then 163 | echo_my "Role '$DERBY_DEV_ROLE' already exists - updating it..." 164 | ACTION="update" 165 | else 166 | echo "Creating a role '$DERBY_DEV_ROLE'..." 167 | ACTION="create" 168 | fi 169 | 170 | PERMISSIONS="\ 171 | compute.projects.get,\ 172 | compute.projects.setCommonInstanceMetadata,\ 173 | pubsub.subscriptions.consume,\ 174 | pubsub.subscriptions.create,\ 175 | pubsub.subscriptions.delete,\ 176 | pubsub.subscriptions.get,\ 177 | pubsub.subscriptions.list,\ 178 | pubsub.subscriptions.update,\ 179 | pubsub.topics.attachSubscription,\ 180 | pubsub.topics.create,\ 181 | pubsub.topics.delete,\ 182 | pubsub.topics.get,\ 183 | pubsub.topics.list,\ 184 | pubsub.topics.publish,\ 185 | pubsub.topics.update,\ 186 | resourcemanager.organizations.get,\ 187 | resourcemanager.projects.get,\ 188 | resourcemanager.projects.getIamPolicy,\ 189 | resourcemanager.projects.list,\ 190 | storage.buckets.create,\ 191 | storage.buckets.delete,\ 192 | storage.buckets.get,\ 193 | storage.buckets.getIamPolicy,\ 194 | storage.buckets.list,\ 195 | storage.buckets.setIamPolicy,\ 196 | storage.buckets.update,\ 197 | storage.objects.create,\ 198 | storage.objects.delete,\ 199 | storage.objects.get,\ 200 | storage.objects.getIamPolicy,\ 201 | storage.objects.list,\ 202 | storage.objects.setIamPolicy,\ 203 | storage.objects.update,\ 204 | clouddebugger.breakpoints.create,\ 205 | clouddebugger.breakpoints.delete,\ 206 | clouddebugger.breakpoints.get,\ 207 | clouddebugger.breakpoints.list,\ 208 | clouddebugger.breakpoints.listActive,\ 209 | clouddebugger.breakpoints.update,\ 210 | clouddebugger.debuggees.create,\ 211 | clouddebugger.debuggees.list,\ 212 | cloudiot.devices.bindGateway,\ 213 | cloudiot.devices.create,\ 214 | cloudiot.devices.delete,\ 215 | cloudiot.devices.get,\ 216 | cloudiot.devices.list,\ 217 | cloudiot.devices.sendCommand,\ 218 | cloudiot.devices.unbindGateway,\ 219 | cloudiot.devices.update,\ 220 | cloudiot.devices.updateConfig,\ 221 | cloudiot.registries.create,\ 222 | cloudiot.registries.delete,\ 223 | cloudiot.registries.get,\ 224 | cloudiot.registries.getIamPolicy,\ 225 | cloudiot.registries.list,\ 226 | cloudiot.registries.setIamPolicy,\ 227 | cloudiot.registries.update" 228 | 229 | #resourcemanager.projects.update,\ 230 | #resourcemanager.projects.updateLiens,\ 231 | #appengine.applications.create,\ 232 | #appengine.applications.get,\ 233 | #appengine.applications.update,\ 234 | #appengine.instances.delete,\ 235 | #appengine.instances.get,\ 236 | #appengine.instances.list,\ 237 | #appengine.services.delete,\ 238 | #appengine.services.get,\ 239 | #appengine.services.list,\ 240 | #appengine.services.update,\ 241 | #appengine.versions.create,\ 242 | #appengine.versions.delete,\ 243 | #appengine.versions.get,\ 244 | #appengine.versions.list,\ 245 | #appengine.versions.update,\ 246 | #iam.serviceAccountKeys.create,\ 247 | #iam.serviceAccountKeys.delete,\ 248 | #iam.serviceAccountKeys.get,\ 249 | #iam.serviceAccountKeys.list,\ 250 | #iam.serviceAccounts.actAs,\ 251 | #iam.serviceAccounts.create,\ 252 | #iam.serviceAccounts.delete,\ 253 | #iam.serviceAccounts.get,\ 254 | #iam.serviceAccounts.getAccessToken,\ 255 | #iam.serviceAccounts.getIamPolicy,\ 256 | #iam.serviceAccounts.implicitDelegation,\ 257 | #iam.serviceAccounts.list,\ 258 | #iam.serviceAccounts.setIamPolicy,\ 259 | #iam.serviceAccounts.signBlob,\ 260 | #iam.serviceAccounts.signJwt,\ 261 | #iam.serviceAccounts.update 262 | 263 | gcloud iam roles ${ACTION} $DERBY_DEV_ROLE \ 264 | --organization $(lookup_org_id) \ 265 | --title "Cloud Derby Developer Role" \ 266 | --description "Access to resources needed to develop and deploy Cloud Derby" \ 267 | --stage "GA" \ 268 | --permissions $PERMISSIONS 269 | 270 | sleep 3 271 | } 272 | 273 | ############################################################################### 274 | # MAIN 275 | ############################################################################### 276 | print_header "Creating workshop users, folders, etc..." 277 | 278 | setup 279 | 280 | create_read_only_group 281 | 282 | create_role 283 | 284 | create_groups_and_users 285 | 286 | create_folders 287 | 288 | print_footer "SUCCESS: New workshop configuration created." 289 | --------------------------------------------------------------------------------