├── LICENSE ├── NOTICE ├── README.md ├── architecture-arm.png ├── architecture-website.png ├── cfn ├── srs-with-domain.json └── srs.json ├── device ├── lib │ ├── README.md │ └── __init__.py └── src │ ├── LICENSE │ ├── NOTICE │ ├── __init__.py │ ├── publisher.py │ ├── subscriber.py │ └── test_subscriber.py ├── srs-demo.png └── web ├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .yo-rc.json ├── Gruntfile.js ├── LICENSE.txt ├── NOTICE.txt ├── app ├── images │ └── bg.png ├── index.html ├── robots.txt ├── scripts │ └── main.js └── styles │ └── main.scss ├── bower.json ├── lambda ├── lambda.sh ├── srsgetdata │ ├── index.js │ └── package.json └── srsputdata │ ├── index.js │ └── package.json ├── package.json └── test ├── index.html └── spec └── test.js /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Simple Beer Service 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Robot Service 2 | 3 | SRS is a cloud-connected robot arm that subscribes to a gesture input device (Leap Motion) via the AWS IoT service. Observe metrics such as x, y, z coordinate movement and hand gestures in near real-time through a custom JavaScript application running on Amazon S3. 4 | 5 | ## Overview 6 | 7 | Simple Robot Service uses AWS IoT to send messages between a publisher and a subscriber. The **publisher.py** script will run on the computer connected to the LEAP motion controller. The **subscriber.py** script will run on the raspberry pi that has the Adafruit Servo Hat and the Robotic Arm connected. 8 | 9 | ![Overall Architecture](architecture-arm.png) 10 | 11 | Simple Robot Service was demonstrated during the AWS keynote address at Re:Invent 2015. 12 | 13 | [![Simple Robot Service Demo](srs-demo.png)](https://youtu.be/y-0Wf2Zyi5Q?t=4176) 14 | 15 | [Click here to play the video.](https://youtu.be/y-0Wf2Zyi5Q?t=4176) 16 | 17 | ### Bill of Materials 18 | 19 | This includes: 20 | - [Raspberry Pi](http://www.amazon.com/dp/B008XVAVAW) 21 | - [Adafruit Servo Hat for Raspberry Pi](http://www.amazon.com/dp/B00XW2OY5A) 22 | - [Robotic Arm](http://www.amazon.com/dp/B00NB1DFF2) 23 | - [LEAP Motion Controller](http://www.amazon.com/dp/B00HVYBWQO) 24 | 25 | ## Setting up AWS IoT 26 | 27 | 1. Click on the **AWS IoT (Beta)** icon in the AWS Management Console. 28 | 29 | 2. Press **Getting Started**. 30 | 31 | 3. There are three resources that need to be created. An IoT "Thing", a certificate and associated keys and a policy. To start, click **Create a thing** and then type *LeapMotionController* as the name of the thing. Press **Create**. 32 | 33 | 4. Next, download the X.509 certificates. Click **Create a certificate**, followed by **1-Click Certificate Create**. Download the three files that are created. These files are needed to connect over TLS from both the local computer with the LEAP Motion Controller and the Raspberry Pi. 34 | 35 | 5. Now, create a policy to interact with the IoT resources. Click **Create a policy**. This example will be very permissive. In a real-world scenario, the policies that are created should only allow specific read/write permissions to specific IoT resources. Call the policy **LeapMotionPolicy**. Define the action as **iot:\*** and the Resource as **\***. Press **Create**. 36 | 37 | 6. The final step is to attach the policy and the thing (Leap Motion Controller) to the certificate. On the AWS IoT dashboard, click the checkbox under the certificate. In the actions dropdown, click **Activate** followed by **Attach a Policy** and **Attach a Thing**. Attach the policy and the thing that was made in the previous steps. 38 | 39 | *NOTE:* 40 | A root certificate is required to complete the TLS connection to AWS IoT from the devices. Here is the link to the AWS IoT root certificate: 41 | 42 | https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem 43 | 44 | ## LEAP Motion 45 | 46 | Create an account at https://developer.leapmotion.com/ and download the SDK. Once done, copy over the following files to the **devices/lib** directory: 47 | - Leap.py 48 | - LeapPython.so 49 | - libLeap.dylib 50 | 51 | Next, setup the LEAP development environment following the guides below. 52 | 53 | - https://developer.leapmotion.com/documentation/java/devguide/Project_Setup.html 54 | - https://developer.leapmotion.com/documentation/python/devguide/Sample_Tutorial.html 55 | 56 | ## Adafruit PWM/Servo Hat 57 | 58 | On the Raspberry Pi, the Adafruit PWM/servo libraries are required. Download them to the Raspberry Pi from here: https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library 59 | 60 | ``` 61 | git clone https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library.git 62 | ``` 63 | 64 | - http://www.amazon.com/Adafruit-16-Channel-PWM-Servo-Raspberry/dp/B00XW2OY5A 65 | - https://learn.adafruit.com/adafruit-16-channel-pwm-servo-hat-for-raspberry-pi/overview 66 | 67 | ## Operation 68 | 69 | The following python libraries are required: 70 | 71 | ``` 72 | pip install schedule paho-mqtt 73 | ``` 74 | 75 | To operate the robotic arm, attach the LEAP motion controller to the laptop and run: **python publisher.py**. Secondly, SSH into the IoT device and copy over the application files. Run **python subscriber.py** on the raspberry-pi. The arm should now move when gestures are made above the LEAP Motion Controller. 76 | 77 | ## Web Visualization 78 | 79 | Visualize this data in real-time by running the web application code found in the web/ directory. 80 | 81 | ![Overall Architecture](architecture-website.png) 82 | 83 | In order to visualize the data from various locations, we will need to store the data in a database. In this example, AWS IoT is used to invoke a Lambda function that writes to DynamoDB based on a defined rule. All of the resources with exception of the AWS IoT resources can be created using cloudformation. Two cloud formation scripts are included, one for custom domains that includes route53 hosted zone information (**srs-with-domain.json**), and one that uses AWS provided S3 URLs (**srs.json**). The two scripts can be found in the */cfn* folder. 84 | 85 | ### Setting up DynamoDB 86 | 87 | First, create a new DynamoDB table to store the data. The DynamoDB table should have a hash key named **EventID** and a range key named **FrameID**. The data will be stored in a JSON object in this table. This table was created via the cloud formation script. 88 | 89 | ### Setting up Lambda and API Gateway 90 | 91 | Next, create the Lambda functions. In the folder web/lambda there are two subfolders, **srsgetdata** and **srsputdata**. In each of these folders run: 92 | 93 | ``` 94 | npm install 95 | ``` 96 | 97 | This will download and install all of the required node modules. Then, edit **index.js** for each function and put in the DynamoDB table name created above and an EventID. After you have edited the index.js file and the NPM install process completes, zip up the contents of that directory (make sure that index.js is in the root of the zip file). 98 | 99 | Once the zip file is ready, follow these steps: 100 | 101 | 1. Open the **Lambda** console. 102 | 103 | 2. Click on the **srsputdata-XXXXXXXXX** function. Upload the **srsputdata** zip file that was created above. 104 | 105 | 3. Click on the **srsgetdata-XXXXXXXXX** function. Upload the **srsgetdata** zip file that was created above. 106 | 107 | 4. In **srsgetdata**, click the API Endpoints tab. Create a new endpoint for this function and enable CORS. Note the endpoint that was created. 108 | 109 | ### Setting up an AWS IoT Rule and Action 110 | 111 | 1. First, let's create a rule. Press **Create a rule**. Type **LeapMotionRule** as the rule name and give it a description. 112 | 113 | 2. In the Rule Query Statement section, type **\*** in the attribute field and **srs** in the topic filter. 114 | 115 | 3. Next, let's add an action to this rule. In the drop down list select **Insert this message into a cloud function and run it (Lambda)**. 116 | 117 | 4. Once done, press **Add Action** followed by **Create**. Select the SRS Put Data lambda function in the drop down. 118 | 119 | ### Uploading the web application files to Amazon S3. 120 | 121 | 1. In the web application files, find **main.js**. Update the *ENDPOINT* variable to point to the AWS API Gateway endpoint created above. 122 | 123 | 2. Open the file Gruntfile.js and look for the following code snippet: 124 | 125 | ``` 126 | grunt.registerTask('publish', [ 127 | 'build', 128 | 'exec:s3push:::us-east-1' 129 | ]); 130 | ``` 131 | 132 | 3. Replace *SRS_BUCKET_NAME* with the name of the website bucket and *PROFILE* with the name of the AWS CLI profile to use. The profile can be set as *default*. 133 | 134 | 4. Use the command line to open the **web/** directory. Here are some commands that are provided by this Gruntfile: 135 | 136 | ``` 137 | grunt serve # Builds the code and runs a local web server. 138 | grunt build # Builds the code 139 | grunt publish # Builds the code and publishes to S3 140 | ``` 141 | 142 | 5. Alternatively, upload the files directly after a **grunt build** using the browser. Simply upload all of the files in the **/dist** folder to the S3 bucket. 143 | -------------------------------------------------------------------------------- /architecture-arm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/simplerobotservice/04a5a46e02ad9509ed7f338e37dfd8e75b62d3d5/architecture-arm.png -------------------------------------------------------------------------------- /architecture-website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/simplerobotservice/04a5a46e02ad9509ed7f338e37dfd8e75b62d3d5/architecture-website.png -------------------------------------------------------------------------------- /cfn/srs-with-domain.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Creates the backend resources for Simple Robot Service.", 4 | "Mappings" : { 5 | "RegionMap" : { 6 | "us-east-1" : { "S3hostedzoneID" : "Z3AQBSTGFYJSTF", "websiteendpoint" : "s3-website-us-east-1.amazonaws.com" }, 7 | "us-west-1" : { "S3hostedzoneID" : "Z2F56UZL2M1ACD", "websiteendpoint" : "s3-website-us-west-1.amazonaws.com" }, 8 | "us-west-2" : { "S3hostedzoneID" : "Z3BJ6K6RIION7M", "websiteendpoint" : "s3-website-us-west-2.amazonaws.com" }, 9 | "eu-west-1" : { "S3hostedzoneID" : "Z1BKCTXD74EZPE", "websiteendpoint" : "s3-website-eu-west-1.amazonaws.com" }, 10 | "ap-southeast-1" : { "S3hostedzoneID" : "Z3O0J2DXBE1FTB", "websiteendpoint" : "s3-website-ap-southeast-1.amazonaws.com" }, 11 | "ap-southeast-2" : { "S3hostedzoneID" : "Z1WCIGYICN2BYD", "websiteendpoint" : "s3-website-ap-southeast-2.amazonaws.com" }, 12 | "ap-northeast-1" : { "S3hostedzoneID" : "Z2M4EHUR26P7ZW", "websiteendpoint" : "s3-website-ap-northeast-1.amazonaws.com" }, 13 | "sa-east-1" : { "S3hostedzoneID" : "Z31GFT0UA1I2HV", "websiteendpoint" : "s3-website-sa-east-1.amazonaws.com" } 14 | } 15 | }, 16 | "Parameters": { 17 | "RootDomainName": { 18 | "Description": "Domain name for your website (example.com)", 19 | "Type": "String" 20 | } 21 | }, 22 | "Resources": { 23 | "SRSRootBucket": { 24 | "Type": "AWS::S3::Bucket", 25 | "Properties": { 26 | "BucketName" : {"Ref":"RootDomainName"}, 27 | "AccessControl": "PublicRead", 28 | "WebsiteConfiguration": { 29 | "IndexDocument":"index.html", 30 | "ErrorDocument":"404.html" 31 | } 32 | } 33 | }, 34 | "SRSWWWBucket": { 35 | "Type": "AWS::S3::Bucket", 36 | "Properties": { 37 | "BucketName": { 38 | "Fn::Join": ["", ["www.", {"Ref":"RootDomainName"}]] 39 | }, 40 | "AccessControl": "BucketOwnerFullControl", 41 | "WebsiteConfiguration": { 42 | "RedirectAllRequestsTo": { 43 | "HostName": {"Ref": "SRSRootBucket"} 44 | } 45 | } 46 | } 47 | }, 48 | "WWWBucketPolicy" : { 49 | "Type" : "AWS::S3::BucketPolicy", 50 | "Properties" : { 51 | "Bucket" : { "Ref" : "SRSWWWBucket" }, 52 | "PolicyDocument": { 53 | "Statement":[{ 54 | "Action":["s3:GetObject"], 55 | "Effect":"Allow", 56 | "Resource": { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "SRSWWWBucket" } , "/*" ]]}, 57 | "Principal":"*" 58 | }] 59 | } 60 | } 61 | }, 62 | "WebRootBucketPolicy" : { 63 | "Type" : "AWS::S3::BucketPolicy", 64 | "Properties" : { 65 | "Bucket" : { "Ref" : "SRSRootBucket" }, 66 | "PolicyDocument": { 67 | "Statement":[{ 68 | "Action":["s3:GetObject"], 69 | "Effect":"Allow", 70 | "Resource": { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "SRSRootBucket" } , "/*" ]]}, 71 | "Principal":"*" 72 | }] 73 | } 74 | } 75 | }, 76 | "SRSDNS": { 77 | "Type": "AWS::Route53::RecordSetGroup", 78 | "Properties": { 79 | "HostedZoneName": { 80 | "Fn::Join": ["", [{"Ref": "RootDomainName"}, "."]] 81 | }, 82 | "Comment": "Zone apex alias.", 83 | "RecordSets": [ 84 | { 85 | "Name": {"Ref": "RootDomainName"}, 86 | "Type": "A", 87 | "AliasTarget": { 88 | "HostedZoneId": {"Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "S3hostedzoneID"]}, 89 | "DNSName": {"Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "websiteendpoint"]} 90 | } 91 | }, 92 | { 93 | "Name": { 94 | "Fn::Join": ["", ["www.", {"Ref":"RootDomainName"}]] 95 | }, 96 | "Type": "CNAME", 97 | "TTL" : "900", 98 | "ResourceRecords" : [ 99 | {"Fn::GetAtt":["SRSWWWBucket", "DomainName"]} 100 | ] 101 | } 102 | ] 103 | } 104 | }, 105 | "SRSLogsBucket": { 106 | "Type": "AWS::S3::Bucket" 107 | }, 108 | "SRSDataTable": { 109 | "Type": "AWS::DynamoDB::Table", 110 | "Properties": { 111 | "AttributeDefinitions": [{ 112 | "AttributeName": "EventID", 113 | "AttributeType": "S" 114 | }, { 115 | "AttributeName": "FrameID", 116 | "AttributeType": "N" 117 | }], 118 | "ProvisionedThroughput": { 119 | "ReadCapacityUnits": "5", 120 | "WriteCapacityUnits": "5" 121 | }, 122 | "KeySchema": [{ 123 | "AttributeName": "EventID", 124 | "KeyType": "HASH" 125 | }, { 126 | "AttributeName": "FrameID", 127 | "KeyType": "RANGE" 128 | }] 129 | } 130 | }, 131 | "ReadSRSData": { 132 | "Type": "AWS::Lambda::Function", 133 | "Properties": { 134 | "Handler": "index.handler", 135 | "Role": { "Fn::GetAtt" : ["SRSReadRole", "Arn"] }, 136 | "Code": { 137 | "ZipFile": { "Fn::Join": ["", [ 138 | "exports.handler = function(event, context) {", 139 | " context.succeed('placeholder');", 140 | "};" 141 | ]]} 142 | }, 143 | "Runtime": "nodejs", 144 | "Timeout": "25" 145 | } 146 | }, 147 | "SRSReadRole": { 148 | "Type": "AWS::IAM::Role", 149 | "Properties": { 150 | "Path": { 151 | "Fn::Join": ["", ["/", "srs", "/"]] 152 | }, 153 | "AssumeRolePolicyDocument": { 154 | "Version": "2012-10-17", 155 | "Statement": [{ 156 | "Effect": "Allow", 157 | "Principal": { 158 | "Service": ["lambda.amazonaws.com", "ec2.amazonaws.com"] 159 | }, 160 | "Action": ["sts:AssumeRole"] 161 | }] 162 | } 163 | } 164 | }, 165 | "WriteSRSData": { 166 | "Type": "AWS::Lambda::Function", 167 | "Properties": { 168 | "Handler": "index.handler", 169 | "Role": { "Fn::GetAtt" : ["SRSWriteRole", "Arn"] }, 170 | "Code": { 171 | "ZipFile": { "Fn::Join": ["", [ 172 | "exports.handler = function(event, context) {", 173 | " context.succeed('placeholder');", 174 | "};" 175 | ]]} 176 | }, 177 | "Runtime": "nodejs", 178 | "Timeout": "25" 179 | } 180 | }, 181 | "SRSWriteRole": { 182 | "Type": "AWS::IAM::Role", 183 | "Properties": { 184 | "Path": { 185 | "Fn::Join": ["", ["/", "srs", "/"]] 186 | }, 187 | "AssumeRolePolicyDocument": { 188 | "Version": "2012-10-17", 189 | "Statement": [{ 190 | "Effect": "Allow", 191 | "Principal": { 192 | "Service": ["lambda.amazonaws.com", "ec2.amazonaws.com"] 193 | }, 194 | "Action": ["sts:AssumeRole"] 195 | }] 196 | } 197 | } 198 | }, 199 | "LambdaLogPolicy": { 200 | "Type": "AWS::IAM::Policy", 201 | "Properties": { 202 | "PolicyName": "srs-lambdalogpolicy", 203 | "Roles": [{ 204 | "Ref": "SRSReadRole" 205 | }], 206 | "PolicyDocument": { 207 | "Version": "2012-10-17", 208 | "Statement": [{ 209 | "Effect": "Allow", 210 | "Action": [ 211 | "logs:*" 212 | ], 213 | "Resource": "arn:aws:logs:*:*:*" 214 | }] 215 | } 216 | } 217 | }, 218 | "S3Policy": { 219 | "Type": "AWS::IAM::Policy", 220 | "Properties": { 221 | "PolicyName": "SRS-s3policy", 222 | "Roles": [{ 223 | "Ref": "SRSReadRole" 224 | }], 225 | "PolicyDocument": { 226 | "Statement": [{ 227 | "Resource": [{ 228 | "Fn::Join": ["", ["arn:aws:s3:::", { 229 | "Ref": "SRSLogsBucket" 230 | }, "*"]] 231 | }], 232 | "Action": [ 233 | "s3:ListBucket", 234 | "s3:Put*", 235 | "s3:Get*", 236 | "s3:*MultipartUpload*", 237 | "s3:DeleteObject*" 238 | ], 239 | "Sid": "1", 240 | "Effect": "Allow" 241 | }, { 242 | "Resource": ["arn:aws:s3:::*"], 243 | "Action": ["s3:ListAllMyBuckets", "s3:GetBucketLocation"], 244 | "Sid": "2", 245 | "Effect": "Allow" 246 | }] 247 | } 248 | } 249 | }, 250 | "DynamoWritePolicy": { 251 | "Type": "AWS::IAM::Policy", 252 | "Properties": { 253 | "PolicyName": "srs-ddbwritepolicy", 254 | "Roles": [{ 255 | "Ref": "SRSWriteRole" 256 | }], 257 | "PolicyDocument": { 258 | "Statement": [{ 259 | "Resource": [{ 260 | "Fn::Join": ["", ["arn:aws:dynamodb:", { 261 | "Ref": "AWS::Region" 262 | }, ":", { 263 | "Ref": "AWS::AccountId" 264 | }, ":table/", { 265 | "Ref": "SRSDataTable" 266 | }, "*"]] 267 | }], 268 | "Action": [ 269 | "dynamodb:PutItem", 270 | "dynamodb:UpdateItem" 271 | ], 272 | "Sid": "1", 273 | "Effect": "Allow" 274 | }, { 275 | "Resource": "*", 276 | "Action": [ 277 | "dynamodb:DescribeTable", 278 | "dynamodb:ListTables", 279 | "dynamodb:CreateTable", 280 | "cloudwatch:*" 281 | ], 282 | "Sid": "2", 283 | "Effect": "Allow" 284 | }] 285 | } 286 | } 287 | }, 288 | "DynamoReadPolicy": { 289 | "Type": "AWS::IAM::Policy", 290 | "Properties": { 291 | "PolicyName": "SRS-ddbreadpolicy", 292 | "Roles": [{ 293 | "Ref": "SRSReadRole" 294 | }], 295 | "PolicyDocument": { 296 | "Statement": [{ 297 | "Resource": [{ 298 | "Fn::Join": ["", ["arn:aws:dynamodb:", { 299 | "Ref": "AWS::Region" 300 | }, ":", { 301 | "Ref": "AWS::AccountId" 302 | }, ":table/", { 303 | "Ref": "SRSDataTable" 304 | }, "*"]] }], 305 | "Action": [ 306 | "dynamodb:BatchGetItem", 307 | "dynamodb:BatchWriteItem", 308 | "dynamodb:Query", 309 | "dynamodb:GetItem", 310 | "dynamodb:Scan" 311 | ], 312 | "Sid": "1", 313 | "Effect": "Allow" 314 | }, { 315 | "Resource": "*", 316 | "Action": [ 317 | "dynamodb:DescribeTable", 318 | "dynamodb:ListTables", 319 | "dynamodb:CreateTable", 320 | "cloudwatch:*" 321 | ], 322 | "Sid": "2", 323 | "Effect": "Allow" 324 | }] 325 | } 326 | } 327 | } 328 | }, 329 | "Outputs": { 330 | "DynamoRegion": { 331 | "Value": { 332 | "Ref": "AWS::Region" 333 | } 334 | }, 335 | "SRSDataDynamoDBTable": { 336 | "Value": { 337 | "Ref": "SRSDataTable" 338 | } 339 | }, 340 | "SRSLambdaWriteSBSData": { 341 | "Value": { 342 | "Ref": "WriteSRSData" 343 | } 344 | }, 345 | "SRSLambdaReadSRSData": { 346 | "Value": { 347 | "Ref": "ReadSRSData" 348 | } 349 | }, 350 | "WebsiteURL": { 351 | "Value": {"Fn::GetAtt": ["SRSRootBucket", "WebsiteURL"]}, 352 | "Description": "URL for website hosted on S3" 353 | }, 354 | "LogsBucket": { 355 | "Value": { 356 | "Ref": "SRSLogsBucket" 357 | } 358 | } 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /cfn/srs.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Creates the backend resources for Simple Robot Service.", 4 | "Resources": { 5 | "SRSWWWBucket": { 6 | "Type": "AWS::S3::Bucket", 7 | "Properties": { 8 | "AccessControl": "PublicRead", 9 | "WebsiteConfiguration": { 10 | "IndexDocument":"index.html", 11 | "ErrorDocument":"404.html" 12 | } 13 | } 14 | }, 15 | "WWWBucketPolicy" : { 16 | "Type" : "AWS::S3::BucketPolicy", 17 | "Properties" : { 18 | "Bucket" : { "Ref" : "SRSWWWBucket" }, 19 | "PolicyDocument": { 20 | "Statement":[{ 21 | "Action":["s3:GetObject"], 22 | "Effect":"Allow", 23 | "Resource": { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "SRSWWWBucket" } , "/*" ]]}, 24 | "Principal":"*" 25 | }] 26 | } 27 | } 28 | }, 29 | "SRSLogsBucket": { 30 | "Type": "AWS::S3::Bucket" 31 | }, 32 | "SRSDataTable": { 33 | "Type": "AWS::DynamoDB::Table", 34 | "Properties": { 35 | "AttributeDefinitions": [{ 36 | "AttributeName": "EventID", 37 | "AttributeType": "S" 38 | }, { 39 | "AttributeName": "FrameID", 40 | "AttributeType": "N" 41 | }], 42 | "ProvisionedThroughput": { 43 | "ReadCapacityUnits": "5", 44 | "WriteCapacityUnits": "5" 45 | }, 46 | "KeySchema": [{ 47 | "AttributeName": "EventID", 48 | "KeyType": "HASH" 49 | }, { 50 | "AttributeName": "FrameID", 51 | "KeyType": "RANGE" 52 | }] 53 | } 54 | }, 55 | "ReadSRSData": { 56 | "Type": "AWS::Lambda::Function", 57 | "Properties": { 58 | "Handler": "index.handler", 59 | "Role": { "Fn::GetAtt" : ["SRSReadRole", "Arn"] }, 60 | "Code": { 61 | "ZipFile": { "Fn::Join": ["", [ 62 | "exports.handler = function(event, context) {", 63 | " context.succeed('placeholder');", 64 | "};" 65 | ]]} 66 | }, 67 | "Runtime": "nodejs", 68 | "Timeout": "25" 69 | } 70 | }, 71 | "SRSReadRole": { 72 | "Type": "AWS::IAM::Role", 73 | "Properties": { 74 | "Path": { 75 | "Fn::Join": ["", ["/", "srs", "/"]] 76 | }, 77 | "AssumeRolePolicyDocument": { 78 | "Version": "2012-10-17", 79 | "Statement": [{ 80 | "Effect": "Allow", 81 | "Principal": { 82 | "Service": ["lambda.amazonaws.com", "ec2.amazonaws.com"] 83 | }, 84 | "Action": ["sts:AssumeRole"] 85 | }] 86 | } 87 | } 88 | }, 89 | "WriteSRSData": { 90 | "Type": "AWS::Lambda::Function", 91 | "Properties": { 92 | "Handler": "index.handler", 93 | "Role": { "Fn::GetAtt" : ["SRSWriteRole", "Arn"] }, 94 | "Code": { 95 | "ZipFile": { "Fn::Join": ["", [ 96 | "exports.handler = function(event, context) {", 97 | " context.succeed('placeholder');", 98 | "};" 99 | ]]} 100 | }, 101 | "Runtime": "nodejs", 102 | "Timeout": "25" 103 | } 104 | }, 105 | "SRSWriteRole": { 106 | "Type": "AWS::IAM::Role", 107 | "Properties": { 108 | "Path": { 109 | "Fn::Join": ["", ["/", "srs", "/"]] 110 | }, 111 | "AssumeRolePolicyDocument": { 112 | "Version": "2012-10-17", 113 | "Statement": [{ 114 | "Effect": "Allow", 115 | "Principal": { 116 | "Service": ["lambda.amazonaws.com", "ec2.amazonaws.com"] 117 | }, 118 | "Action": ["sts:AssumeRole"] 119 | }] 120 | } 121 | } 122 | }, 123 | "LambdaLogPolicy": { 124 | "Type": "AWS::IAM::Policy", 125 | "Properties": { 126 | "PolicyName": "srs-lambdalogpolicy", 127 | "Roles": [{ 128 | "Ref": "SRSReadRole" 129 | }], 130 | "PolicyDocument": { 131 | "Version": "2012-10-17", 132 | "Statement": [{ 133 | "Effect": "Allow", 134 | "Action": [ 135 | "logs:*" 136 | ], 137 | "Resource": "arn:aws:logs:*:*:*" 138 | }] 139 | } 140 | } 141 | }, 142 | "S3Policy": { 143 | "Type": "AWS::IAM::Policy", 144 | "Properties": { 145 | "PolicyName": "SRS-s3policy", 146 | "Roles": [{ 147 | "Ref": "SRSReadRole" 148 | }], 149 | "PolicyDocument": { 150 | "Statement": [{ 151 | "Resource": [{ 152 | "Fn::Join": ["", ["arn:aws:s3:::", { 153 | "Ref": "SRSLogsBucket" 154 | }, "*"]] 155 | }], 156 | "Action": [ 157 | "s3:ListBucket", 158 | "s3:Put*", 159 | "s3:Get*", 160 | "s3:*MultipartUpload*", 161 | "s3:DeleteObject*" 162 | ], 163 | "Sid": "1", 164 | "Effect": "Allow" 165 | }, { 166 | "Resource": ["arn:aws:s3:::*"], 167 | "Action": ["s3:ListAllMyBuckets", "s3:GetBucketLocation"], 168 | "Sid": "2", 169 | "Effect": "Allow" 170 | }] 171 | } 172 | } 173 | }, 174 | "DynamoWritePolicy": { 175 | "Type": "AWS::IAM::Policy", 176 | "Properties": { 177 | "PolicyName": "srs-ddbwritepolicy", 178 | "Roles": [{ 179 | "Ref": "SRSWriteRole" 180 | }], 181 | "PolicyDocument": { 182 | "Statement": [{ 183 | "Resource": [{ 184 | "Fn::Join": ["", ["arn:aws:dynamodb:", { 185 | "Ref": "AWS::Region" 186 | }, ":", { 187 | "Ref": "AWS::AccountId" 188 | }, ":table/", { 189 | "Ref": "SRSDataTable" 190 | }, "*"]] 191 | }], 192 | "Action": [ 193 | "dynamodb:PutItem", 194 | "dynamodb:UpdateItem" 195 | ], 196 | "Sid": "1", 197 | "Effect": "Allow" 198 | }, { 199 | "Resource": "*", 200 | "Action": [ 201 | "dynamodb:DescribeTable", 202 | "dynamodb:ListTables", 203 | "dynamodb:CreateTable", 204 | "cloudwatch:*" 205 | ], 206 | "Sid": "2", 207 | "Effect": "Allow" 208 | }] 209 | } 210 | } 211 | }, 212 | "DynamoReadPolicy": { 213 | "Type": "AWS::IAM::Policy", 214 | "Properties": { 215 | "PolicyName": "SRS-ddbreadpolicy", 216 | "Roles": [{ 217 | "Ref": "SRSReadRole" 218 | }], 219 | "PolicyDocument": { 220 | "Statement": [{ 221 | "Resource": [{ 222 | "Fn::Join": ["", ["arn:aws:dynamodb:", { 223 | "Ref": "AWS::Region" 224 | }, ":", { 225 | "Ref": "AWS::AccountId" 226 | }, ":table/", { 227 | "Ref": "SRSDataTable" 228 | }, "*"]] }], 229 | "Action": [ 230 | "dynamodb:BatchGetItem", 231 | "dynamodb:BatchWriteItem", 232 | "dynamodb:Query", 233 | "dynamodb:GetItem", 234 | "dynamodb:Scan" 235 | ], 236 | "Sid": "1", 237 | "Effect": "Allow" 238 | }, { 239 | "Resource": "*", 240 | "Action": [ 241 | "dynamodb:DescribeTable", 242 | "dynamodb:ListTables", 243 | "dynamodb:CreateTable", 244 | "cloudwatch:*" 245 | ], 246 | "Sid": "2", 247 | "Effect": "Allow" 248 | }] 249 | } 250 | } 251 | } 252 | }, 253 | "Outputs": { 254 | "DynamoRegion": { 255 | "Value": { 256 | "Ref": "AWS::Region" 257 | } 258 | }, 259 | "SRSDataDynamoDBTable": { 260 | "Value": { 261 | "Ref": "SRSDataTable" 262 | } 263 | }, 264 | "SRSLambdaWriteSBSData": { 265 | "Value": { 266 | "Ref": "WriteSRSData" 267 | } 268 | }, 269 | "SRSLambdaReadSRSData": { 270 | "Value": { 271 | "Ref": "ReadSRSData" 272 | } 273 | }, 274 | "WebsiteURL": { 275 | "Value": {"Fn::GetAtt": ["SRSWWWBucket", "WebsiteURL"]}, 276 | "Description": "URL for website hosted on S3" 277 | }, 278 | "LogsBucket": { 279 | "Value": { 280 | "Ref": "SRSLogsBucket" 281 | } 282 | } 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /device/lib/README.md: -------------------------------------------------------------------------------- 1 | # Library Directory 2 | 3 | In this directory, you will store the libraries required for this project. 4 | 5 | ## LEAP Motion 6 | 7 | Create an account at https://developer.leapmotion.com/ and download the SDK. Once done, copy over the following files to this directory: 8 | - Leap.py 9 | - LeapPython.so 10 | - libLeap.dylib 11 | 12 | You will then need to run a few commands to setup your LEAP motion development environment. Follow the guide below. 13 | 14 | - https://developer.leapmotion.com/documentation/java/devguide/Project_Setup.html 15 | - https://developer.leapmotion.com/documentation/python/devguide/Sample_Tutorial.html 16 | 17 | ## Adafruit Development Board and Servos 18 | 19 | You will also need to copy over the files for the Adafruit expansion board and the servos associated with your robotic arm. 20 | 21 | - http://www.amazon.com/Adafruit-16-Channel-PWM-Servo-Raspberry/dp/B00XW2OY5A 22 | - https://learn.adafruit.com/adafruit-16-channel-pwm-servo-hat-for-raspberry-pi/overview 23 | -------------------------------------------------------------------------------- /device/lib/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'jeremygw' 2 | -------------------------------------------------------------------------------- /device/src/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /device/src/NOTICE: -------------------------------------------------------------------------------- 1 | Simple Beer Service 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /device/src/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'jeremygw' 2 | -------------------------------------------------------------------------------- /device/src/publisher.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 5 | 6 | http://aws.amazon.com/apache2.0/ 7 | 8 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 9 | 10 | Note: Other license terms may apply to certain, identified software files contained within or 11 | distributed with the accompanying software if such terms are included in the directory containing 12 | the accompanying software. Such other license terms will then apply in lieu of the terms of the 13 | software license above. 14 | ''' 15 | 16 | import sys 17 | sys.path.insert(0, "../lib") 18 | import Leap 19 | from Leap import CircleGesture 20 | import json 21 | import time 22 | 23 | import paho.mqtt.client as paho 24 | import ssl 25 | 26 | ROOT_CA = "" 27 | CERTIFICATE = "" 28 | PRIVATE_KEY = "" 29 | AWS_IOT_TOPIC = "" 30 | AWS_IOT_ENDPOINT = "" 31 | 32 | ''' 33 | AWS IoT Listener sends data to AWS IoT via MQTT. 34 | @extends Leap.Listener 35 | ''' 36 | 37 | class AWSIoTListener(Leap.Listener): 38 | 39 | mqttc = None 40 | 41 | def on_mqtt_log(self, client, userdata, level, buf): 42 | print (str(level) + ": '" + str(buf)) 43 | 44 | def on_mqtt_connect(self, client, userdata, flags, rc): 45 | print("Connected with result code "+str(rc)) 46 | self.mqttc = client 47 | 48 | def on_init(self, controller): 49 | mqttc = paho.Client() 50 | mqttc.tls_set(ROOT_CA, CERTIFICATE, PRIVATE_KEY, tls_version=ssl.PROTOCOL_TLSv1_2) 51 | mqttc.on_log = self.on_mqtt_log 52 | mqttc.on_connect = self.on_mqtt_connect 53 | mqttc.connect(AWS_IOT_ENDPOINT, 8883, 10) 54 | time.sleep(4) 55 | mqttc.loop_start() 56 | 57 | 58 | def on_connect(self, controller): 59 | controller.enable_gesture(Leap.Gesture.TYPE_CIRCLE); 60 | 61 | def on_frame(self, controller): 62 | 63 | clockwiseness = 0 64 | frame = controller.frame() 65 | 66 | # Get gestures 67 | for gesture in frame.gestures(): 68 | if gesture.type == Leap.Gesture.TYPE_CIRCLE: 69 | circle = CircleGesture(gesture) 70 | 71 | # Determine clock direction using the angle between the pointable and the circle normal 72 | if circle.pointable.direction.angle_to(circle.normal) <= Leap.PI/2: 73 | clockwiseness = 1 74 | else: 75 | clockwiseness = 0 76 | 77 | if not (frame.hands.is_empty and frame.gestures().is_empty): 78 | 79 | xAxis = round(frame.hands[0].direction[0], 2) 80 | yAxis = round(frame.hands[0].arm.wrist_position[1], 2) 81 | zAxis = round(frame.hands[0].arm.wrist_position[2], 2) 82 | 83 | set_xAxis = int((600 - ((xAxis / 1.98) * 450)) - 150 ) 84 | set_yAxis = int(375 - (100 * (yAxis / 360))) 85 | set_zAxis = int(275 + (((zAxis + 50) / 200) * 200)) 86 | 87 | tev_json_obj = json.dumps({'frameId':frame.id, 'coordinates':{'xAxis':set_xAxis,'yAxis':set_yAxis, 'zAxis': set_zAxis, 'clockwiseness': clockwiseness}}) 88 | print(tev_json_obj) 89 | 90 | counter = str(frame.id) 91 | counter = counter[-1:] 92 | if counter == '1' and self.mqttc != None: 93 | self.mqttc.publish(AWS_IOT_TOPIC, tev_json_obj, 0, False) 94 | 95 | if __name__ == "__main__": 96 | 97 | # Create the AWS IoT listener and add it to the Leap Motion Controller 98 | aws_iot_listener = AWSIoTListener() 99 | controller = Leap.Controller() 100 | controller.add_listener(aws_iot_listener) 101 | 102 | print "To quit, press enter." 103 | try: 104 | sys.stdin.readline() 105 | except KeyboardInterrupt: 106 | pass 107 | finally: 108 | controller.remove_listener(aws_iot_listener) 109 | -------------------------------------------------------------------------------- /device/src/subscriber.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 5 | 6 | http://aws.amazon.com/apache2.0/ 7 | 8 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 9 | 10 | Note: Other license terms may apply to certain, identified software files contained within or 11 | distributed with the accompanying software if such terms are included in the directory containing 12 | the accompanying software. Such other license terms will then apply in lieu of the terms of the 13 | software license above. 14 | ''' 15 | 16 | import paho.mqtt.client as paho 17 | import ssl 18 | import ast 19 | from Adafruit_PWM_Servo_Driver import PWM 20 | import json 21 | import schedule, time 22 | 23 | pins = { 24 | "xAxis": 0, 25 | "yAxis": 1, 26 | "zAxis": 2, 27 | "head": 3, 28 | "rightEye": 8, 29 | "leftEye": 9 30 | } 31 | 32 | ROOT_CA = "" 33 | CERTIFICATE = "" 34 | PRIVATE_KEY = "" 35 | AWS_IOT_TOPIC = "" 36 | AWS_IOT_ENDPOINT = "" 37 | 38 | var_payload = '' 39 | 40 | # Default active flag is false. 41 | active = False 42 | 43 | # Initialise the PWM device using the default address 44 | pwm = PWM(0x40) 45 | 46 | # Note if you'd like more debug output you can instead run: 47 | # pwm = PWM(0x40, debug=True) 48 | 49 | pwm.setPWMFreq(60) # Set frequency to 60 Hz. 50 | 51 | ''' 52 | This function sets the servo pulse and channel. 53 | ''' 54 | def set_servo_pulse(channel, pulse): 55 | pulseLength = 1000000 # 1,000,000 us per second 56 | pulseLength /= 60 # 60 Hz 57 | print "%d us per period" % pulseLength 58 | pulseLength /= 4096 # 12 bits of resolution 59 | print "%d us per bit" % pulseLength 60 | pulse *= 1000 61 | pulse /= pulseLength 62 | pwm.setPWM(channel, 0, pulse) 63 | 64 | ''' 65 | This function is invoked when the mqtt client makes a successful connection. It subscribes the client to the AWS IoT Topic. 66 | @param client The MQTT Client 67 | @param userdata 68 | @param flags 69 | @param rc The result code 70 | ''' 71 | def on_connect(client, userdata, flags, rc): 72 | print("Connected with result code "+str(rc)) 73 | client.subscribe(AWS_IOT_TOPIC) 74 | 75 | ''' 76 | This function is invoked when a new message is received by the MQTT client. It sets the servo values to the values received from AWS IoT. 77 | @param client The MQTT Client 78 | @param userdata 79 | @param msg The message that was recieved. 80 | ''' 81 | def on_message(client, userdata, msg): 82 | 83 | print(msg.topic+" "+str(msg.payload)) 84 | 85 | # Message received! Wake up! 86 | active = True 87 | 88 | # The message is in JSON format 89 | tev_json_obj = json.loads(msg.payload) 90 | 91 | pwm.setPWM(pins["xAxis"], 0, tev_json_obj["coordinates"]["xAxis"]) 92 | pwm.setPWM(pins["yAxis"], 0, tev_json_obj["coordinates"]["yAxis"]) 93 | pwm.setPWM(pins["zAxis"], 0, tev_json_obj["coordinates"]["zAxis"]) 94 | 95 | pwm.setPWM(pins["rightEye"], 600) 96 | pwm.setPWM(pins["leftEye"], 600) 97 | 98 | if tev_json_obj["coordinates"]['clockwiseness'] == 0: 99 | print('tapStatus is counterclockwise') 100 | pwm.setPWM(pins["head"], 0, 200) 101 | if tev_json_obj["coordinates"]['clockwiseness'] == 1: 102 | print('tapStatus is clockwise') 103 | pwm.setPWM(pins["head"], 0, 500) 104 | 105 | def go_to_sleep(active): 106 | 107 | if (active): 108 | print("Still active... I'm Awake!"); 109 | active = False 110 | else: 111 | print("Not active... Sleeping..."); 112 | pwm.setPWM(pins["xAxis"], 0, 0) 113 | pwm.setPWM(pins["yAxis"], 0, 0) 114 | pwm.setPWM(pins["zAxis"], 0, 0) 115 | pwm.setPWM(pins["rightEye"], 4, 0) 116 | pwm.setPWM(pins["leftEye"], 5, 0) 117 | 118 | # The main application runs the Paho MQTT Client 119 | if __name__ == "__main__": 120 | 121 | mqttc = paho.Client() 122 | mqttc.on_connect = on_connect 123 | mqttc.on_message = on_message 124 | mqttc.tls_set(ROOT_CA, CERTIFICATE, PRIVATE_KEY, tls_version=ssl.PROTOCOL_TLSv1_2) 125 | mqttc.connect(AWS_IOT_ENDPOINT, 8883, 10) 126 | mqttc.loop_start() 127 | 128 | schedule.every(0.1).minutes.do(lambda: go_to_sleep(active)); 129 | 130 | while True: 131 | schedule.run_pending() 132 | time.sleep(1) 133 | -------------------------------------------------------------------------------- /device/src/test_subscriber.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import paho.mqtt.client as mqtt 4 | import ssl 5 | 6 | ROOT_CA = "" 7 | CERTIFICATE = "" 8 | PRIVATE_KEY = "" 9 | AWS_IOT_TOPIC = "" 10 | AWS_IOT_ENDPOINT = "" 11 | 12 | def on_connect(client, userdata, flags, rc): 13 | print("Connected with result code "+str(rc)) 14 | client.subscribe(AWS_IOT_TOPIC) 15 | 16 | def on_message(client, userdata, msg): 17 | print(msg.topic+" "+str(msg.payload)) 18 | 19 | client = mqtt.Client() 20 | client.on_connect = on_connect 21 | client.tls_set(ROOT_CA, 22 | CERTIFICATE, 23 | PRIVATE_KEY, 24 | tls_version=ssl.PROTOCOL_TLSv1_2) 25 | 26 | client.on_message = on_message 27 | 28 | client.connect(AWS_IOT_ENDPOINT, 8883, 10) 29 | 30 | client.loop_forever() 31 | -------------------------------------------------------------------------------- /srs-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/simplerobotservice/04a5a46e02ad9509ed7f338e37dfd8e75b62d3d5/srs-demo.png -------------------------------------------------------------------------------- /web/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /web/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /web/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | bower_components 5 | -------------------------------------------------------------------------------- /web/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-mocha": {} 3 | } -------------------------------------------------------------------------------- /web/Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2015-09-08 using 2 | // generator-webapp 1.0.1 3 | 'use strict'; 4 | 5 | // # Globbing 6 | // for performance reasons we're only matching one level down: 7 | // 'test/spec/{,*/}*.js' 8 | // If you want to recursively match all subfolders, use: 9 | // 'test/spec/**/*.js' 10 | 11 | module.exports = function (grunt) { 12 | 13 | // Time how long tasks take. Can help when optimizing build times 14 | require('time-grunt')(grunt); 15 | 16 | // Automatically load required grunt tasks 17 | require('jit-grunt')(grunt, { 18 | useminPrepare: 'grunt-usemin' 19 | }); 20 | 21 | // Configurable paths 22 | var config = { 23 | app: 'app', 24 | dist: 'dist' 25 | }; 26 | 27 | // Load Grunt Exec for AWS CLI commands 28 | grunt.loadNpmTasks('grunt-exec'); 29 | 30 | // Define the configuration for all the tasks 31 | grunt.initConfig({ 32 | 33 | // Project settings 34 | config: config, 35 | 36 | // Watches files for changes and runs tasks based on the changed files 37 | watch: { 38 | bower: { 39 | files: ['bower.json'], 40 | tasks: ['wiredep'] 41 | }, 42 | babel: { 43 | files: ['<%= config.app %>/scripts/{,*/}*.js'], 44 | tasks: ['babel:dist'] 45 | }, 46 | babelTest: { 47 | files: ['test/spec/{,*/}*.js'], 48 | tasks: ['babel:test', 'test:watch'] 49 | }, 50 | gruntfile: { 51 | files: ['Gruntfile.js'] 52 | }, 53 | sass: { 54 | files: ['<%= config.app %>/styles/{,*/}*.{scss,sass}'], 55 | tasks: ['sass:server', 'postcss'] 56 | }, 57 | styles: { 58 | files: ['<%= config.app %>/styles/{,*/}*.css'], 59 | tasks: ['newer:copy:styles', 'postcss'] 60 | } 61 | }, 62 | 63 | browserSync: { 64 | options: { 65 | notify: false, 66 | background: true 67 | }, 68 | livereload: { 69 | options: { 70 | files: [ 71 | '<%= config.app %>/{,*/}*.html', 72 | '.tmp/styles/{,*/}*.css', 73 | '<%= config.app %>/images/{,*/}*', 74 | '.tmp/scripts/{,*/}*.js' 75 | ], 76 | port: 9000, 77 | server: { 78 | baseDir: ['.tmp', config.app], 79 | routes: { 80 | '/bower_components': './bower_components' 81 | } 82 | } 83 | } 84 | }, 85 | test: { 86 | options: { 87 | port: 9001, 88 | open: false, 89 | logLevel: 'silent', 90 | host: 'localhost', 91 | server: { 92 | baseDir: ['.tmp', './test', config.app], 93 | routes: { 94 | '/bower_components': './bower_components' 95 | } 96 | } 97 | } 98 | }, 99 | dist: { 100 | options: { 101 | background: false, 102 | server: '<%= config.dist %>' 103 | } 104 | } 105 | }, 106 | 107 | // Empties folders to start fresh 108 | clean: { 109 | dist: { 110 | files: [{ 111 | dot: true, 112 | src: [ 113 | '.tmp', 114 | '<%= config.dist %>/*', 115 | '!<%= config.dist %>/.git*' 116 | ] 117 | }] 118 | }, 119 | server: '.tmp' 120 | }, 121 | 122 | // Make sure code styles are up to par and there are no obvious mistakes 123 | eslint: { 124 | target: [ 125 | 'Gruntfile.js', 126 | '<%= config.app %>/scripts/{,*/}*.js', 127 | '!<%= config.app %>/scripts/vendor/*', 128 | 'test/spec/{,*/}*.js' 129 | ] 130 | }, 131 | 132 | // Mocha testing framework configuration options 133 | mocha: { 134 | all: { 135 | options: { 136 | run: true, 137 | urls: ['http://<%= browserSync.test.options.host %>:<%= browserSync.test.options.port %>/index.html'] 138 | } 139 | } 140 | }, 141 | 142 | // Compiles ES6 with Babel 143 | babel: { 144 | options: { 145 | sourceMap: true 146 | }, 147 | dist: { 148 | files: [{ 149 | expand: true, 150 | cwd: '<%= config.app %>/scripts', 151 | src: '{,*/}*.js', 152 | dest: '.tmp/scripts', 153 | ext: '.js' 154 | }] 155 | }, 156 | test: { 157 | files: [{ 158 | expand: true, 159 | cwd: 'test/spec', 160 | src: '{,*/}*.js', 161 | dest: '.tmp/spec', 162 | ext: '.js' 163 | }] 164 | } 165 | }, 166 | 167 | // Compiles Sass to CSS and generates necessary files if requested 168 | sass: { 169 | options: { 170 | sourceMap: true, 171 | sourceMapEmbed: true, 172 | sourceMapContents: true, 173 | includePaths: ['.'] 174 | }, 175 | dist: { 176 | files: [{ 177 | expand: true, 178 | cwd: '<%= config.app %>/styles', 179 | src: ['*.{scss,sass}'], 180 | dest: '.tmp/styles', 181 | ext: '.css' 182 | }] 183 | }, 184 | server: { 185 | files: [{ 186 | expand: true, 187 | cwd: '<%= config.app %>/styles', 188 | src: ['*.{scss,sass}'], 189 | dest: '.tmp/styles', 190 | ext: '.css' 191 | }] 192 | } 193 | }, 194 | 195 | postcss: { 196 | options: { 197 | map: true, 198 | processors: [ 199 | // Add vendor prefixed styles 200 | require('autoprefixer-core')({ 201 | browsers: ['> 1%', 'last 2 versions', 'Firefox ESR', 'Opera 12.1'] 202 | }) 203 | ] 204 | }, 205 | dist: { 206 | files: [{ 207 | expand: true, 208 | cwd: '.tmp/styles/', 209 | src: '{,*/}*.css', 210 | dest: '.tmp/styles/' 211 | }] 212 | } 213 | }, 214 | 215 | exec: { 216 | s3push: { 217 | cmd: function(bucket, profile, region) { 218 | return 'aws s3 cp dist/ s3://'+bucket+' --recursive --profile '+profile+' --region '+region; 219 | } 220 | }, 221 | lambda: { 222 | cmd: function(profile, region) { 223 | return 'lambda/lambda.sh '+profile+' '+region; 224 | } 225 | } 226 | }, 227 | 228 | // Automatically inject Bower components into the HTML file 229 | wiredep: { 230 | app: { 231 | src: ['<%= config.app %>/index.html'], 232 | exclude: ['bootstrap.js'], 233 | ignorePath: /^(\.\.\/)*\.\./ 234 | }, 235 | sass: { 236 | src: ['<%= config.app %>/styles/{,*/}*.{scss,sass}'], 237 | ignorePath: /^(\.\.\/)+/ 238 | } 239 | }, 240 | 241 | // Renames files for browser caching purposes 242 | filerev: { 243 | dist: { 244 | src: [ 245 | '<%= config.dist %>/scripts/{,*/}*.js', 246 | '<%= config.dist %>/styles/{,*/}*.css', 247 | '<%= config.dist %>/images/{,*/}*.*', 248 | '<%= config.dist %>/styles/fonts/{,*/}*.*', 249 | '<%= config.dist %>/*.{ico,png}' 250 | ] 251 | } 252 | }, 253 | 254 | // Reads HTML for usemin blocks to enable smart builds that automatically 255 | // concat, minify and revision files. Creates configurations in memory so 256 | // additional tasks can operate on them 257 | useminPrepare: { 258 | options: { 259 | dest: '<%= config.dist %>' 260 | }, 261 | html: '<%= config.app %>/index.html' 262 | }, 263 | 264 | // Performs rewrites based on rev and the useminPrepare configuration 265 | usemin: { 266 | options: { 267 | assetsDirs: [ 268 | '<%= config.dist %>', 269 | '<%= config.dist %>/images', 270 | '<%= config.dist %>/styles' 271 | ] 272 | }, 273 | html: ['<%= config.dist %>/{,*/}*.html'], 274 | css: ['<%= config.dist %>/styles/{,*/}*.css'] 275 | }, 276 | 277 | // The following *-min tasks produce minified files in the dist folder 278 | imagemin: { 279 | dist: { 280 | files: [{ 281 | expand: true, 282 | cwd: '<%= config.app %>/images', 283 | src: '{,*/}*.{gif,jpeg,jpg,png}', 284 | dest: '<%= config.dist %>/images' 285 | }] 286 | } 287 | }, 288 | 289 | svgmin: { 290 | dist: { 291 | files: [{ 292 | expand: true, 293 | cwd: '<%= config.app %>/images', 294 | src: '{,*/}*.svg', 295 | dest: '<%= config.dist %>/images' 296 | }] 297 | } 298 | }, 299 | 300 | htmlmin: { 301 | dist: { 302 | options: { 303 | collapseBooleanAttributes: true, 304 | collapseWhitespace: true, 305 | conservativeCollapse: true, 306 | removeAttributeQuotes: true, 307 | removeCommentsFromCDATA: true, 308 | removeEmptyAttributes: true, 309 | removeOptionalTags: true, 310 | // true would impact styles with attribute selectors 311 | removeRedundantAttributes: false, 312 | useShortDoctype: true 313 | }, 314 | files: [{ 315 | expand: true, 316 | cwd: '<%= config.dist %>', 317 | src: '{,*/}*.html', 318 | dest: '<%= config.dist %>' 319 | }] 320 | } 321 | }, 322 | 323 | // By default, your `index.html`'s will take care 324 | // of minification. These next options are pre-configured if you do not 325 | // wish to use the Usemin blocks. 326 | // cssmin: { 327 | // dist: { 328 | // files: { 329 | // '<%= config.dist %>/styles/main.css': [ 330 | // '.tmp/styles/{,*/}*.css', 331 | // '<%= config.app %>/styles/{,*/}*.css' 332 | // ] 333 | // } 334 | // } 335 | // }, 336 | // uglify: { 337 | // dist: { 338 | // files: { 339 | // '<%= config.dist %>/scripts/scripts.js': [ 340 | // '<%= config.dist %>/scripts/scripts.js' 341 | // ] 342 | // } 343 | // } 344 | // }, 345 | // concat: { 346 | // dist: {} 347 | // }, 348 | 349 | // Copies remaining files to places other tasks can use 350 | copy: { 351 | dist: { 352 | files: [{ 353 | expand: true, 354 | dot: true, 355 | cwd: '<%= config.app %>', 356 | dest: '<%= config.dist %>', 357 | src: [ 358 | '*.{ico,png,txt}', 359 | 'images/{,*/}*.webp', 360 | '{,*/}*.html', 361 | 'styles/fonts/{,*/}*.*' 362 | ] 363 | }, { 364 | expand: true, 365 | dot: true, 366 | cwd: '.', 367 | src: 'bower_components/bootstrap-sass/assets/fonts/bootstrap/*', 368 | dest: '<%= config.dist %>' 369 | }] 370 | } 371 | }, 372 | 373 | // Generates a custom Modernizr build that includes only the tests you 374 | // reference in your app 375 | modernizr: { 376 | dist: { 377 | devFile: 'bower_components/modernizr/modernizr.js', 378 | outputFile: '<%= config.dist %>/scripts/vendor/modernizr.js', 379 | files: { 380 | src: [ 381 | '<%= config.dist %>/scripts/{,*/}*.js', 382 | '<%= config.dist %>/styles/{,*/}*.css', 383 | '!<%= config.dist %>/scripts/vendor/*' 384 | ] 385 | }, 386 | uglify: true 387 | } 388 | }, 389 | 390 | // Run some tasks in parallel to speed up build process 391 | concurrent: { 392 | server: [ 393 | 'babel:dist', 394 | 'sass:server' 395 | ], 396 | test: [ 397 | 'babel' 398 | ], 399 | dist: [ 400 | 'babel', 401 | 'sass', 402 | 'imagemin', 403 | 'svgmin' 404 | ] 405 | } 406 | }); 407 | 408 | 409 | grunt.registerTask('serve', 'start the server and preview your app', function (target) { 410 | 411 | if (target === 'dist') { 412 | return grunt.task.run(['build', 'browserSync:dist']); 413 | } 414 | 415 | grunt.task.run([ 416 | 'clean:server', 417 | 'wiredep', 418 | 'concurrent:server', 419 | 'postcss', 420 | 'browserSync:livereload', 421 | 'watch' 422 | ]); 423 | }); 424 | 425 | grunt.registerTask('server', function (target) { 426 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); 427 | grunt.task.run([target ? ('serve:' + target) : 'serve']); 428 | }); 429 | 430 | grunt.registerTask('test', function (target) { 431 | if (target !== 'watch') { 432 | grunt.task.run([ 433 | 'clean:server', 434 | 'concurrent:test', 435 | 'postcss' 436 | ]); 437 | } 438 | 439 | grunt.task.run([ 440 | 'browserSync:test', 441 | 'mocha' 442 | ]); 443 | }); 444 | 445 | grunt.registerTask('build', [ 446 | 'clean:dist', 447 | 'wiredep', 448 | 'useminPrepare', 449 | 'concurrent:dist', 450 | 'postcss', 451 | 'concat', 452 | 'cssmin', 453 | 'uglify', 454 | 'copy:dist', 455 | 'modernizr', 456 | 'filerev', 457 | 'usemin', 458 | 'htmlmin' 459 | ]); 460 | 461 | grunt.registerTask('publish', [ 462 | 'build', 463 | 'exec:s3push:srs-website:iot:us-east-1' 464 | ]); 465 | 466 | grunt.registerTask('publish-lambda', [ 467 | 'exec:lambda:iot:us-east-1' 468 | ]); 469 | 470 | grunt.registerTask('default', [ 471 | 'newer:eslint', 472 | 'test', 473 | 'build' 474 | ]); 475 | }; 476 | -------------------------------------------------------------------------------- /web/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /web/NOTICE.txt: -------------------------------------------------------------------------------- 1 | Simple Beer Service 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /web/app/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/simplerobotservice/04a5a46e02ad9509ed7f338e37dfd8e75b62d3d5/web/app/images/bg.png -------------------------------------------------------------------------------- /web/app/index.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | Simple Robot Service 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 67 |
68 |

Servo Movements

69 |
    70 |
    71 |
    72 |
    00:00
    73 |
    Get Ready!!
    74 |
    Go! Go! Go!
    75 |
    Time's up!
    76 |
    77 | 78 | 79 |
    80 |

    Instructions:

    Your goal is to pick up the ball and put it in the bowl. As soon as you put your hands over the Leap Motion sensor, the timer will start. Can you finish in under 60 seconds? 81 |
    82 |
    83 | CLOCKWISENESS 84 | 85 |
    86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /web/app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | Disallow: 5 | -------------------------------------------------------------------------------- /web/app/scripts/main.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 6 | 7 | http://aws.amazon.com/apache2.0/ 8 | 9 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 10 | 11 | Note: Other license terms may apply to certain, identified software files contained within or 12 | distributed with the accompanying software if such terms are included in the directory containing 13 | the accompanying software. Such other license terms will then apply in lieu of the terms of the 14 | software license above. 15 | */ 16 | 17 | // CHANGE TO YOUR ENDPOINT 18 | var ENDPOINT = ""; 19 | var POLL_INTERVAL = 1000; 20 | var PAUSE_INTERVAL = 10000; 21 | 22 | // Smoothie Settings 23 | var MILLIS_PER_PIXEL = 50; 24 | var MAX_VAL_SCALE = 1.0; 25 | var MIN_VAL_SCALE = 1.0; 26 | var LINE_WIDTH = 1; 27 | var MILLIS_PER_LINE = 400; 28 | var VERTICAL_SECTIONS = 6; 29 | var SMOOTHIE_SPEED = 1000; 30 | 31 | /* 32 | * Timer object, counts down from 60 seconds when the arm is moved. 33 | */ 34 | var timer = { 35 | 36 | timerText: $("#timer"), 37 | goText: $(".go"), 38 | getReadyText: $(".get-ready"), 39 | timeoutText: $(".timeout"), 40 | ms: 0, 41 | s: 60, 42 | running: false, 43 | pause: false, 44 | pauseInterval: null, 45 | 46 | init: function() { 47 | this.timerText.html(this.getText()); 48 | }, 49 | startClock: function() { 50 | this.hideAll(); 51 | this.goText.show(); 52 | this.running = true; 53 | }, 54 | hideAll: function() { 55 | this.goText.hide(); 56 | this.timeoutText.hide(); 57 | this.getReadyText.hide(); 58 | }, 59 | stopClock: function () { 60 | this.timerText.html(this.getText()); 61 | this.hideAll(); 62 | this.timeoutText.show(); 63 | this.timerText.toggleClass("redbox"); 64 | this.running = false; 65 | this.pause = true; 66 | var object = this; 67 | clearInterval(interval); 68 | setTimeout(function(){ 69 | object.resetClock(); 70 | }, PAUSE_INTERVAL); 71 | }, 72 | getText: function() { 73 | var seconds = this.s.toString(); 74 | var miliseconds = this.ms.toString() 75 | if (seconds.length == 1) { 76 | seconds = "0"+seconds; 77 | } 78 | if (miliseconds.length == 1) { 79 | miliseconds = miliseconds+"0"; 80 | } 81 | return seconds+":"+miliseconds; 82 | }, 83 | resetClock: function() { 84 | this.pause = false; 85 | this.s = 60, 86 | this.ms = 0, 87 | this.hideAll(); 88 | this.timerText.toggleClass("redbox"); 89 | this.timerText.html(this.getText()); 90 | this.getReadyText.show(); 91 | }, 92 | decrementTimer: function (){ 93 | this.ms = this.ms-1; 94 | if (this.ms==-1) { 95 | this.s = this.s-1; 96 | this.ms = 9; 97 | if (this.s==-1) { 98 | this.s = 0; 99 | this.ms = 0; 100 | this.stopClock(); 101 | } 102 | } 103 | this.timerText.html(this.getText()); 104 | } 105 | } 106 | 107 | var timeseries = {}; 108 | 109 | var colors = { 110 | chartgray: { 111 | stroke: 'rgba(60, 60, 60, 0)', 112 | fill: 'rgba(0, 0, 0, 0.6)' 113 | }, 114 | green: { 115 | stroke: 'rgb(141, 232, 44)', 116 | fill: 'rgba(141, 232, 44, 0.4)', 117 | zero: 'rgba(141, 232, 44, 0)' 118 | }, 119 | yellow: { 120 | stroke: 'rgb(232, 232, 44)', 121 | fill: 'rgba(232, 232, 44, 0.4)', 122 | zero: 'rgba(232, 232, 44, 0)' 123 | }, 124 | blue: { 125 | stroke: 'rgb(44, 132, 232)', 126 | fill: 'rgba(44, 132, 232, 0.4)', 127 | zero: 'rgba(44, 132, 232, 0)' 128 | } 129 | }; 130 | 131 | var interval = null; 132 | var timestamp = new Date().getTime(); 133 | 134 | // Init function. 135 | $( document ).ready(function() { 136 | timer.init(); 137 | window.addEventListener("resize", resizeCanvas, false); 138 | resizeCanvas(""); 139 | servo = createTimeSeriesGraph("servo"); 140 | timeseries = { "yAxis": new TimeSeries(), "xAxis": new TimeSeries(), "zAxis": new TimeSeries() }; 141 | addAxis('yAxis', colors.green); 142 | addAxis('xAxis', colors.blue); 143 | addAxis('zAxis', colors.yellow); 144 | setInterval(refresh, POLL_INTERVAL); 145 | }); 146 | 147 | /** 148 | * Resizes the canvas to fit the screen. 149 | * @param e The event 150 | */ 151 | function resizeCanvas (e) { 152 | var myCanvas = document.getElementById("servo"); 153 | myCanvas.width = document.documentElement.clientWidth; 154 | myCanvas.height = document.documentElement.clientHeight - 70; 155 | } 156 | 157 | /** 158 | * Adds an axis to the graph. 159 | * @param {string} name The name of the axis. 160 | * @param {string} color The color of the axis. 161 | */ 162 | function addAxis(name, color) { 163 | servo.addTimeSeries(timeseries[name], { strokeStyle: color.stroke, fillStyle: color.zero, lineWidth: 3 }); 164 | $("#legend-list").append('
  • '+name+' Servo Movements
  • '); 165 | } 166 | 167 | /** 168 | * Pull the latest data from dynamodb based on the last timestamp when data was successfully recieved. 169 | */ 170 | function refresh() { 171 | $.ajax({ 172 | dataType : 'json', 173 | url: ENDPOINT, 174 | data: {"timestamp": timestamp}, 175 | async: true, 176 | success: function(response) { 177 | if ($.isEmptyObject(response)) { 178 | console.log("Empty object"); 179 | } else { 180 | if (response.length>1) { 181 | if (!timer.pause&&!timer.running) { 182 | timer.startClock(); 183 | interval = setInterval(function(){ 184 | timer.decrementTimer(); 185 | }, 100); 186 | } 187 | } 188 | for (var key in response) { 189 | if (response.hasOwnProperty(key)) { 190 | update(response[key].coordinates); 191 | } 192 | } 193 | 194 | timestamp = response[response.length-1].timestamp; 195 | } 196 | }, 197 | error: function(xhr) { 198 | console.error("Could not get record data: | "+xhr); 199 | return null; 200 | } 201 | }); 202 | } 203 | 204 | /** 205 | * Update the values 206 | * @param {values} values The latest coordinate values to update in the graph. 207 | */ 208 | function update(values) { 209 | //console.log(values); 210 | if (values===undefined) { 211 | console.error("No data."); 212 | return; 213 | } 214 | if (values.yAxis!==undefined) { 215 | //console.log("yAxis: ", values.yAxis); 216 | timeseries["yAxis"].append(Date.now(), values.yAxis); 217 | } 218 | if (values.xAxis!==undefined) { 219 | //console.log("Sound: ", values.xAxis); 220 | timeseries["xAxis"].append(Date.now(), values.xAxis); 221 | } 222 | if (values.zAxis!==undefined) { 223 | //console.log("Sound: ", values.xAxis); 224 | timeseries["zAxis"].append(Date.now(), values.zAxis); 225 | } 226 | if (values.clockwiseness!==undefined) { 227 | $("#clockwiseness").html(values.clockwiseness); 228 | } 229 | } 230 | 231 | /** 232 | * Creates a new smoothie time series graph. 233 | * @param {sensor} sensor The title of the sensor that you want to graph 234 | */ 235 | function createTimeSeriesGraph(sensor) { 236 | var smoothie = new SmoothieChart({ maxValue: 800, minValue: 100, millisPerPixel: MILLIS_PER_PIXEL, grid: { strokeStyle: colors.chartgray.stroke, fillStyle: colors.chartgray.fill, lineWidth: LINE_WIDTH, millisPerLine: MILLIS_PER_LINE, yAxisSections: VERTICAL_SECTIONS } }); 237 | smoothie.streamTo(document.getElementById(sensor), SMOOTHIE_SPEED); 238 | return smoothie; 239 | } 240 | -------------------------------------------------------------------------------- /web/app/styles/main.scss: -------------------------------------------------------------------------------- 1 | $icon-font-path: "../bower_components/bootstrap-sass/assets/fonts/bootstrap/"; 2 | 3 | // bower:scss 4 | @import "bower_components/bootstrap-sass/assets/stylesheets/_bootstrap.scss"; 5 | // endbower 6 | 7 | body { 8 | margin-top: 0px; 9 | margin-bottom: 70px; 10 | background: none; 11 | font-family: "Helvetica Neue", Arial,Helvetica,Geneva,sans-serif; 12 | color:#fff; 13 | background: url(../images/bg.png) no-repeat center center fixed; 14 | -webkit-background-size: cover; 15 | -moz-background-size: cover; 16 | -o-background-size: cover; 17 | background-size: cover; 18 | } 19 | 20 | h1 { 21 | font-size:22px; 22 | margin:6px; 23 | } 24 | 25 | h2 { 26 | font-size:18px; 27 | margin:6px; 28 | font-weight:200; 29 | } 30 | 31 | .placeholder-title { 32 | font-size:12px; 33 | margin: 0; 34 | padding: 0px; 35 | display: block; 36 | color:#999; 37 | font-weight:800; 38 | } 39 | 40 | .colorblock { 41 | width:36px; 42 | height:36px; 43 | margin: 4px; 44 | font-size:12px; 45 | font-weight:200; 46 | border: 2px solid #000; 47 | } 48 | 49 | .graphs { 50 | float:left; 51 | margin-left:10px; 52 | margin-right:10px; 53 | } 54 | 55 | 56 | /* TIMER */ 57 | 58 | .timer-block { 59 | text-align:left; padding-left:20px; font-weight: bold; font-size:80px; margin-top: 4px; border:#666 solid 1px; background-color: rgba(40,40,40,0.6); height: 120px; 60 | } 61 | 62 | .countdown { 63 | text-transform:uppercase; font-weight:bold; color:#999; font-size:20px; 64 | } 65 | 66 | .timeout, .get-ready, .go, .instructions { 67 | padding: 10px; 68 | } 69 | 70 | .timeout { 71 | display:none; 72 | } 73 | 74 | .redbox { 75 | background: rgba(255,0,0,0.3); 76 | border: 1px solid rgba(255,0,0,1); 77 | color: rgba(255,0,0,1); 78 | text-decoration: blink; 79 | } 80 | 81 | .get-ready { 82 | background: rgba(44, 132, 232,0.3); 83 | border: 1px solid rgba(44, 132, 232,1); 84 | } 85 | 86 | .go { 87 | background: rgba(0,255,0,0.3); 88 | border: 1px solid rgba(0,255,0,1); 89 | display: none; 90 | } 91 | 92 | /* LEGEND */ 93 | 94 | #legend { 95 | max-width:600px; 96 | margin-left: 0px; 97 | } 98 | 99 | #legend ul { 100 | list-style-type: none; 101 | padding: 0px; 102 | margin: 0px; 103 | } 104 | 105 | #legend ul li { 106 | padding: 0px; 107 | margin: 10px 0px; 108 | } 109 | 110 | .legend-row { 111 | clear:both; 112 | display: block; 113 | border-top:2px solid #333; 114 | padding-top:5px; 115 | background: rgba(0,0,0,0.5); 116 | height: 55px; 117 | } 118 | 119 | .legend-row > div { 120 | float:left; 121 | margin-left:10px; 122 | } 123 | 124 | .legend-row .temp, 125 | .legend-row .humidity, 126 | .legend-row .location { 127 | font-size:18px; 128 | font-weight:500; 129 | margin-top:0; 130 | } 131 | 132 | .legend-row .location { 133 | min-width:160px; 134 | } 135 | 136 | .legend-box { 137 | width: 20px; 138 | height: 20px; 139 | float:left; 140 | margin-right: 10px; 141 | } 142 | 143 | /* NAVIGATION CSS */ 144 | 145 | .navbar { 146 | background: #444; 147 | background: -moz-linear-gradient(top,#444 0,#222 100%); 148 | background: -webkit-gradient(linear,left top,left bottom,color-stop(0,#444),color-stop(100%,#222)); 149 | background: -webkit-linear-gradient(top,#444 0,#222 100%); 150 | background: -o-linear-gradient(top,#444 0,#222 100%); 151 | background: -ms-linear-gradient(top,#444 0,#222 100%); 152 | background: linear-gradient(to bottom,#444 0,#222 100%); 153 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#444444',endColorstr='#222222',GradientType=0) 154 | } 155 | 156 | #navbar { 157 | margin-left: 120px; 158 | } 159 | 160 | nav { 161 | border-bottom: 2px solid #fff; 162 | } 163 | 164 | .navbar-brand, 165 | .navbar-nav li a { 166 | line-height: 70px; 167 | height: 70px; 168 | padding-top: 0; 169 | } 170 | 171 | .navbar-header .small { 172 | display:none; 173 | } 174 | 175 | img.ie-fallback { 176 | display: none; 177 | } 178 | 179 | .countdown-side { 180 | position: absolute; 181 | right: 30px; 182 | top: 86px; 183 | width: 250px; 184 | } 185 | 186 | .instructions { 187 | position: absolute; 188 | bottom: 20px; 189 | left: 40px; 190 | max-width: 600px; 191 | font-size: 16px; 192 | margin-right: 40px; 193 | } 194 | 195 | /* GRAPHS AND KNOBS */ 196 | 197 | .graphs { 198 | padding: 20px; 199 | background: rgba(20,20,20,0.4); 200 | border: rgb(20,20,20) solid 2px; 201 | position: absolute; 202 | top: 70px; 203 | left: 20px; 204 | } 205 | 206 | .clockwiseness { 207 | position: absolute; 208 | right: 40px; 209 | bottom: 40px; 210 | border: #999 1px solid; 211 | background: rgba(60,60,60,0.5); 212 | width: 100px; 213 | height: 100px; 214 | } 215 | 216 | #clockwiseness { 217 | padding:20px; 218 | font-size: 65px; 219 | font-weight: bold; 220 | padding: 30px; 221 | } 222 | 223 | #servo { 224 | position: absolute; 225 | left: 0px; 226 | top: 70px; 227 | z-index:-1; 228 | } 229 | 230 | /* MISC */ 231 | 232 | .allcaps-titles { 233 | font-size: 10px; 234 | color: #fff; 235 | font-weight: bold; 236 | position: absolute; 237 | text-align:center; 238 | top: 5px; 239 | width: 100%; 240 | } 241 | 242 | /* MOBILE STYLES*/ 243 | @media only screen and (max-width: 500px) { 244 | 245 | .navbar-header .large { 246 | display:none; 247 | } 248 | 249 | .navbar-header .small { 250 | display:block; 251 | } 252 | 253 | .countdown-side { 254 | display:none; 255 | } 256 | 257 | .instructions { 258 | display:none; 259 | } 260 | 261 | } 262 | -------------------------------------------------------------------------------- /web/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simplerobotservice", 3 | "private": true, 4 | "dependencies": { 5 | "bootstrap-sass": "~3.3.5", 6 | "modernizr": "~2.8.3", 7 | "smoothie": "^1.27.0" 8 | }, 9 | "overrides": { 10 | "bootstrap-sass": { 11 | "main": [ 12 | "assets/stylesheets/_bootstrap.scss", 13 | "assets/fonts/bootstrap/*", 14 | "assets/javascripts/bootstrap.js" 15 | ] 16 | } 17 | }, 18 | "devDependencies": { 19 | "chai": "~3.2.0", 20 | "mocha": "~2.3.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /web/lambda/lambda.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LAMBDA_FCT_FOLDERS=('srsputdata' 'srsgetdata') 4 | LAMBDA_FCT_NAMES=('srsputdata' 'srsgetdata') 5 | mkdir temp 6 | for i in "${!LAMBDA_FCT_FOLDERS[@]}"; do 7 | read -p "Do you want to deploy lambda function: ${LAMBDA_FCT_NAMES[$i]}? " -n 1 -r 8 | echo # (optional) move to a new line 9 | if [[ $REPLY =~ ^[Yy]$ ]] 10 | then 11 | cd ${LAMBDA_FCT_FOLDERS[$i]} 12 | npm install 13 | zip -r ../temp/${LAMBDA_FCT_FOLDERS[$i]}.zip index.js node_modules 14 | aws lambda update-function-code \ 15 | --function-name "${LAMBDA_FCT_NAMES[$i]}" \ 16 | --zip-file "fileb://../temp/${LAMBDA_FCT_FOLDERS[$i]}.zip" \ 17 | --profile $1 \ 18 | --region $2 19 | fi 20 | echo "Skipping this function..." 21 | done 22 | rm -r -f ../temp/ 23 | -------------------------------------------------------------------------------- /web/lambda/srsgetdata/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 5 | 6 | http://aws.amazon.com/apache2.0/ 7 | 8 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 9 | 10 | Note: Other license terms may apply to certain, identified software files contained within or 11 | distributed with the accompanying software if such terms are included in the directory containing 12 | the accompanying software. Such other license terms will then apply in lieu of the terms of the 13 | software license above. 14 | */ 15 | console.log('Loading event'); 16 | 17 | var DATA_TABLE = ''; 18 | var EVENT = ''; 19 | 20 | var AWS = require("aws-sdk"); 21 | var async = require("async"); 22 | var ddb = new AWS.DynamoDB(); 23 | 24 | exports.handler = function(event, context) { 25 | if (event.timestamp===undefined||event.timestamp==null||event.timestamp==0) { 26 | var rTimestamp = new Date().getTime(); 27 | } else { 28 | var rTimestamp = event.timestamp; 29 | } 30 | var params = { 31 | TableName: DATA_TABLE, 32 | Select: 'SPECIFIC_ATTRIBUTES', 33 | AttributesToGet: ['RecordTimestamp','Coordinates'], 34 | KeyConditions: { 35 | EventID: { 36 | ComparisonOperator: 'EQ', 37 | AttributeValueList: [{ S: EVENT }] 38 | }, 39 | RecordTimestamp: { 40 | ComparisonOperator: 'GE', 41 | AttributeValueList: [{ 42 | N: rTimestamp.toString() 43 | }] 44 | } 45 | } 46 | }; 47 | 48 | ddb.query(params, function(err, data) { 49 | if (err) { 50 | console.log(err); 51 | context.fail(err); 52 | } 53 | else { 54 | console.log(this.data.Items); 55 | var response = []; 56 | for (var i in this.data.Items) { 57 | console.log(this.data.Items[i].Coordinates.S); 58 | response[i] = { 59 | timestamp: this.data.Items[i].RecordTimestamp.N, 60 | coordinates: JSON.parse(this.data.Items[i].Coordinates.S) 61 | }; 62 | } 63 | context.succeed(response); 64 | } 65 | }); 66 | }; 67 | -------------------------------------------------------------------------------- /web/lambda/srsgetdata/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "srsgetdata", 3 | "version": "1.0.0", 4 | "description": "Lambda function to get Simple Robot Service data.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "dependencies" : { 10 | "aws-sdk" : "latest", 11 | "async": "latest" 12 | }, 13 | "keywords": [ 14 | "aws", 15 | "simple", 16 | "robot", 17 | "service" 18 | ], 19 | "author": "Todd Varland, Jeremy Wallace", 20 | "license": "Apache License 2.0" 21 | } 22 | -------------------------------------------------------------------------------- /web/lambda/srsputdata/index.js: -------------------------------------------------------------------------------- 1 | console.log('Loading event'); 2 | 3 | var DATA_TABLE = ''; 4 | var EVENT = ''; 5 | var AWS = require("aws-sdk"); 6 | var ddb = new AWS.DynamoDB(); 7 | 8 | exports.handler = function(event, context) { 9 | 10 | if (event.coordinates!==undefined) { 11 | 12 | var rTimestamp = new Date().getTime(); 13 | console.log(rTimestamp); 14 | var params = { 15 | TableName: DATA_TABLE, 16 | Item:{ 17 | "EventID":{"S": EVENT }, 18 | "RecordTimestamp":{"N": rTimestamp.toString() }, 19 | "Coordinates":{"S": JSON.stringify(event.coordinates)} 20 | } 21 | }; 22 | 23 | ddb.putItem(params, function(err, result) { 24 | console.log(result); 25 | context.done(err, result); 26 | }); 27 | 28 | } else { 29 | context.fail("JSON input [frameId or coordinates] not defined."); 30 | } 31 | 32 | }; 33 | -------------------------------------------------------------------------------- /web/lambda/srsputdata/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "srsputdata", 3 | "version": "1.0.0", 4 | "description": "Lambda function to put Simple Robot Service data.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "dependencies" : { 10 | "aws-sdk" : "latest", 11 | "async": "latest" 12 | }, 13 | "keywords": [ 14 | "aws", 15 | "simple", 16 | "robot", 17 | "service" 18 | ], 19 | "author": "Todd Varland, Jeremy Wallace", 20 | "license": "Apache License 2.0" 21 | } 22 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "autoprefixer-core": "^5.2.1", 5 | "grunt": "^0.4.5", 6 | "grunt-babel": "^5.0.0", 7 | "grunt-browser-sync": "^2.1.2", 8 | "grunt-concurrent": "^1.0.0", 9 | "grunt-contrib-clean": "^0.6.0", 10 | "grunt-contrib-concat": "^0.5.1", 11 | "grunt-contrib-copy": "^0.8.0", 12 | "grunt-contrib-cssmin": "^0.12.2", 13 | "grunt-contrib-htmlmin": "^0.4.0", 14 | "grunt-contrib-imagemin": "^0.9.3", 15 | "grunt-contrib-uglify": "^0.8.0", 16 | "grunt-contrib-watch": "^0.6.1", 17 | "grunt-eslint": "^16.0.0", 18 | "grunt-filerev": "^2.2.0", 19 | "grunt-mocha": "^0.4.12", 20 | "grunt-modernizr": "^0.6.0", 21 | "grunt-newer": "^1.1.0", 22 | "grunt-postcss": "^0.5.3", 23 | "grunt-sass": "^1.0.0", 24 | "grunt-svgmin": "^2.0.1", 25 | "grunt-usemin": "^3.0.0", 26 | "grunt-wiredep": "^2.0.0", 27 | "jit-grunt": "^0.9.1", 28 | "grunt-exec": "0.4.6", 29 | "time-grunt": "^1.1.0" 30 | }, 31 | "engines": { 32 | "node": ">=0.10.0" 33 | }, 34 | "scripts": { 35 | "test": "grunt test" 36 | }, 37 | "eslintConfig": { 38 | "env": { 39 | "node": true, 40 | "browser": true, 41 | "mocha": true 42 | }, 43 | "rules": { 44 | "quotes": [ 45 | 2, 46 | "single" 47 | ] 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /web/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha Spec Runner 6 | 7 | 8 | 9 |
    10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /web/test/spec/test.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | describe('Give it some context', function () { 5 | describe('maybe a bit more context here', function () { 6 | it('should run here few assertions', function () { 7 | 8 | }); 9 | }); 10 | }); 11 | })(); 12 | --------------------------------------------------------------------------------