├── ArduinoSignals ├── APICalls.cpp ├── APICalls.h ├── ArduinoSignals.ino └── Credentials.h ├── CONTRIBUTING.md ├── LICENSE ├── README.md └── images ├── image1.png ├── image2.jpg ├── image3.png ├── image4.png ├── image5.png └── image6.png /ArduinoSignals/APICalls.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /* 18 | Copyright © 2017 All market data provided by Barchart Solutions. 19 | 20 | BATS market data is at least 15-minutes delayed. Forex market data is at least 10-minutes delayed. 21 | AMEX, NASDAQ, NYSE and futures market data (CBOT, CME, COMEX and NYMEX) is end-of-day. 22 | Information is provided 'as is' and solely for informational purposes, not for trading purposes or advice, 23 | and is delayed. To see all exchange delays and terms of use, please see our disclaimer. 24 | */ 25 | 26 | #include "APICalls.h" 27 | 28 | // #define PRINT_PAYLOAD 29 | 30 | String urlencode(String str) 31 | { 32 | String encodedString=""; 33 | char c; 34 | char code0; 35 | char code1; 36 | char code2; 37 | for (int i =0; i < str.length(); i++){ 38 | c=str.charAt(i); 39 | if (c == ' '){ 40 | encodedString+= '+'; 41 | } else if (isalnum(c)){ 42 | encodedString+=c; 43 | } else{ 44 | code1=(c & 0xf)+'0'; 45 | if ((c & 0xf) >9){ 46 | code1=(c & 0xf) - 10 + 'A'; 47 | } 48 | c=(c>>4)&0xf; 49 | code0=c+'0'; 50 | if (c > 9){ 51 | code0=c - 10 + 'A'; 52 | } 53 | code2='\0'; 54 | encodedString+='%'; 55 | encodedString+=code0; 56 | encodedString+=code1; 57 | //encodedString+=code2; 58 | } 59 | yield(); 60 | } 61 | return encodedString; 62 | } 63 | 64 | int days_in_month[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31}; 65 | 66 | int leap_year(int year) { 67 | if(year%400==0) return 1; 68 | 69 | if(year%4==0 && year%100!=0) return 1; 70 | 71 | return 0; 72 | } 73 | 74 | int number_of_days(int year, int day, int month) { 75 | int result = 0; 76 | int i; 77 | 78 | for(i=1; i < year; i++) { 79 | if(leap_year(i)) 80 | result += 366; 81 | else 82 | result += 365; 83 | } 84 | 85 | for(i=1; i < month; i++) { 86 | result += days_in_month[i]; 87 | 88 | if(leap_year(year) && i == 2) result++; 89 | } 90 | 91 | result += day; 92 | return result; 93 | } 94 | 95 | String makeLessPrettyJSON(String JSONData) 96 | { 97 | String notPretty = ""; 98 | for(int i = 0; i < JSONData.length(); i++) 99 | { 100 | if(JSONData.charAt(i) != '\n' && JSONData.charAt(i) != '\r' && 101 | JSONData.charAt(i) != ' ' && JSONData.charAt(i) != ' ' && 102 | JSONData.charAt(i) != '[' && JSONData.charAt(i) != ']') 103 | { 104 | notPretty += JSONData.charAt(i); 105 | } 106 | } 107 | 108 | return notPretty; 109 | } 110 | 111 | void PaperSignals::StartUp() 112 | { 113 | myservo.attach(SERVO_PIN); 114 | MoveServoToPosition(CENTER_POSITION, 10); // Initialize 115 | } 116 | 117 | void PaperSignals::MoveServoToPosition(int position, int speed) 118 | { 119 | if(position < currentServoPosition) 120 | { 121 | for(int i = currentServoPosition; i > position; i--) 122 | { 123 | myservo.write(i); 124 | delay(speed); 125 | } 126 | } 127 | else if(position > currentServoPosition) 128 | { 129 | for(int i = currentServoPosition; i < position; i++) 130 | { 131 | myservo.write(i); 132 | delay(speed); 133 | } 134 | } 135 | 136 | currentServoPosition = position; 137 | } 138 | 139 | void PaperSignals::DefaultExecution(String JSONData) 140 | { 141 | Serial.println("Default"); 142 | } 143 | 144 | void PaperSignals::TimerExecution(String JSONData) 145 | { 146 | DynamicJsonBuffer jsonBuffer; 147 | JsonObject& root = jsonBuffer.parseObject(JSONData); 148 | int seconds = root["parameters"]["duration"]["inSeconds"]; 149 | 150 | Serial.print("Timer: "); 151 | Serial.println(seconds); 152 | 153 | if(updatedIntentTimeStamp) 154 | { 155 | int totalDistance = abs(TIMER_END - TIMER_START); 156 | int speed = (seconds*1000)/totalDistance; 157 | MoveServoToPosition(TIMER_START, 0); 158 | MoveServoToPosition(TIMER_END, speed); 159 | 160 | MoveServoToPosition(TIMER_WIGGLE_TOP, 10); 161 | MoveServoToPosition(TIMER_WIGGLE_BOTTOM, 10); 162 | MoveServoToPosition(TIMER_WIGGLE_TOP, 10); 163 | MoveServoToPosition(TIMER_WIGGLE_BOTTOM, 10); 164 | } 165 | } 166 | 167 | double PaperSignals::GetBitcoin() 168 | { 169 | char* host = "api.coinmarketcap.com"; 170 | String url = "/v1/ticker/bitcoin/"; 171 | 172 | String payload = getJson(host, url); 173 | 174 | String unPretty = makeLessPrettyJSON(payload); 175 | 176 | DynamicJsonBuffer jsonBuffer; 177 | JsonObject& root = jsonBuffer.parseObject(unPretty); 178 | 179 | // Test if parsing succeeds. 180 | if (!root.success()) { 181 | Serial.println("parseObject() failed"); 182 | } 183 | 184 | double change = root[String("percent_change_24h")]; 185 | 186 | return change; 187 | } 188 | 189 | double PaperSignals::GetEthereum() 190 | { 191 | char* host = "api.coinmarketcap.com"; 192 | String url = "/v1/ticker/ethereum/"; 193 | 194 | String payload = getJson(host, url); 195 | 196 | String unPretty = makeLessPrettyJSON(payload); 197 | 198 | DynamicJsonBuffer jsonBuffer; 199 | JsonObject& root = jsonBuffer.parseObject(unPretty); 200 | 201 | // Test if parsing succeeds. 202 | if (!root.success()) { 203 | Serial.println("parseObject() failed"); 204 | } 205 | 206 | double change = root[String("percent_change_24h")]; 207 | 208 | return change; 209 | } 210 | 211 | void PaperSignals::CryptoCurrencyExecution(String JSONData) 212 | { 213 | String Bitcoin = "Bitcoin"; 214 | String Ethereum = "Ethereum"; 215 | 216 | DynamicJsonBuffer cryptoBuffer; 217 | JsonObject& cryptoRoot = cryptoBuffer.parseObject(JSONData); 218 | String CurrencyType = cryptoRoot["parameters"]["Crypto"]; 219 | 220 | if(CurrencyType == Bitcoin) 221 | { 222 | Serial.println("Tracking Bitcoin"); 223 | double curChange = GetBitcoin(); 224 | Serial.println(curChange); 225 | 226 | if(curChange < 0) 227 | { 228 | Serial.println("Bitcoin is Down"); 229 | MoveServoToPosition(CURRENCY_DOWN, 10); 230 | } 231 | else if(curChange > 0) 232 | { 233 | Serial.println("Bitcoin is Up"); 234 | MoveServoToPosition(CURRENCY_UP, 10); 235 | } 236 | else 237 | { 238 | Serial.println("Bitcoin No Change"); 239 | MoveServoToPosition(CURRENCY_NO_CHANGE, 10); 240 | } 241 | } 242 | else if(CurrencyType == Ethereum) 243 | { 244 | Serial.println("Tracking Ethereum"); 245 | double curChange = GetEthereum(); 246 | Serial.println(curChange); 247 | 248 | if(curChange < 0) 249 | { 250 | Serial.println("Ethereum is Down"); 251 | MoveServoToPosition(CURRENCY_DOWN, 10); 252 | } 253 | else if(curChange > 0) 254 | { 255 | Serial.println("Ethereum is Up"); 256 | MoveServoToPosition(CURRENCY_UP, 10); 257 | } 258 | else 259 | { 260 | Serial.println("Ethereum No Change"); 261 | MoveServoToPosition(CURRENCY_NO_CHANGE, 10); 262 | } 263 | } 264 | else 265 | { 266 | Serial.println("Currency not supported"); 267 | return; 268 | } 269 | 270 | 271 | } 272 | 273 | void PaperSignals::StretchBreakExecution(String JSONData) 274 | { 275 | DynamicJsonBuffer stretchBreakBuffer; 276 | JsonObject& stretchBreakRoot = stretchBreakBuffer.parseObject(JSONData); 277 | breakTimeInterval = stretchBreakRoot["parameters"]["duration"]["amount"]; // In Minutes 278 | breakTimeInterval = breakTimeInterval*60*1000; 279 | 280 | if(lastBreakTime > millis()) // Reset every 49 days 281 | { 282 | lastBreakTime = millis(); 283 | } 284 | 285 | if(millis() - lastBreakTime > breakTimeInterval) 286 | { 287 | lastBreakTime = millis(); 288 | } 289 | 290 | Serial.print("Break in "); Serial.println(breakTimeInterval - (millis() - lastBreakTime)); 291 | 292 | if(millis() - lastBreakTime < breakTimeLength) 293 | { 294 | // Hands Up 295 | Serial.println("Break Time"); 296 | MoveServoToPosition(STRETCHBREAK_TIME_REACHED, 10); 297 | } 298 | else 299 | { 300 | // Hands Down 301 | Serial.println("No Breaks Yet"); 302 | MoveServoToPosition(STRETCHBREAK_NOT_YET, 10); 303 | } 304 | 305 | } 306 | bool PaperSignals::throttleWeatherAPI() 307 | { 308 | if(millis() < lastWeatherCall) lastWeatherCall = 0; // Reset every 49 days 309 | 310 | if(!updatedIntentTimeStamp && millis() - lastWeatherCall < timeBetweenWeatherCalls) return true; 311 | lastWeatherCall = millis(); 312 | 313 | return false; 314 | } 315 | 316 | bool PaperSignals::throttleStockAPI() 317 | { 318 | if(millis() < lastStockCall) lastStockCall = 0; // Reset every 49 days 319 | 320 | if(!updatedIntentTimeStamp && millis() - lastStockCall < timeBetweenStockCalls) return true; 321 | lastStockCall = millis(); 322 | return false; 323 | } 324 | 325 | void PaperSignals::ShortsOrPantsExecution(String JSONData) 326 | { 327 | //Only poll the weather API if a new location has been requested or it has been more than 2 minutes 328 | if(throttleWeatherAPI()) return; 329 | 330 | Serial.print("CUR DATA "); 331 | Serial.println(JSONData); 332 | DynamicJsonBuffer weatherLocationBuffer; 333 | JsonObject& locationRoot = weatherLocationBuffer.parseObject(JSONData); 334 | String City = locationRoot["parameters"]["location"]["city"]; 335 | String State = locationRoot["parameters"]["location"]["admin-area"]; 336 | String Address = locationRoot["parameters"]["location"]["street-address"]; 337 | String MaybeAlsoACity = locationRoot["parameters"]["location"]["subadmin-area"]; 338 | 339 | String Location = MaybeAlsoACity + " " + City + " " + State + " " + Address; 340 | 341 | String WeatherJSON = GetWeather(Location); 342 | 343 | DynamicJsonBuffer jsonBufferWeather; 344 | JsonObject& weatherRoot = jsonBufferWeather.parseObject(WeatherJSON); 345 | 346 | double temperature = weatherRoot["daily"]["data"][0]["temperatureLow"]; 347 | Serial.println(temperature); 348 | 349 | if(temperature > SHORTSORPANTS_THRESHOLD) 350 | { 351 | MoveServoToPosition(SHORTSORPANTS_SHORTS, 10); 352 | } 353 | else 354 | { 355 | MoveServoToPosition(SHORTSORPANTS_PANTS, 10); 356 | } 357 | } 358 | 359 | void PaperSignals::UmbrellaExecution(String JSONData) 360 | { 361 | //Only poll the weather API if a new location has been requested or it has been more than 2 minutes 362 | if(throttleWeatherAPI()) return; 363 | 364 | DynamicJsonBuffer weatherLocationBuffer; 365 | JsonObject& locationRoot = weatherLocationBuffer.parseObject(JSONData); 366 | String City = locationRoot["parameters"]["location"]["city"]; 367 | String State = locationRoot["parameters"]["location"]["admin-area"]; 368 | String Address = locationRoot["parameters"]["location"]["street-address"]; 369 | String MaybeAlsoACity = locationRoot["parameters"]["location"]["subadmin-area"]; 370 | 371 | String Location = MaybeAlsoACity + " " + City + " " + State + " " + Address; 372 | 373 | String WeatherJSON = GetWeather(Location); 374 | 375 | DynamicJsonBuffer jsonBufferWeather; 376 | JsonObject& weatherRoot = jsonBufferWeather.parseObject(WeatherJSON); 377 | 378 | String iconWeather = weatherRoot["daily"]["data"][0]["icon"]; 379 | Serial.println(iconWeather); 380 | 381 | String rainIcon = "rain"; 382 | String sleetIcon = "sleet"; 383 | 384 | if(iconWeather == rainIcon || iconWeather == sleetIcon) 385 | { 386 | MoveServoToPosition(UMBRELLA_OPEN, 10); 387 | } 388 | else 389 | { 390 | MoveServoToPosition(UMBRELLA_CLOSED, 10); 391 | } 392 | } 393 | 394 | // Performs Geocoding and a dark sky call to 395 | String PaperSignals::GetWeather(String Location) 396 | { 397 | String latLong = GetLatLong(Location); 398 | 399 | String curTimeHost = "currentmillis.com"; 400 | String curTimeURL = "/time/minutes-since-unix-epoch.php"; 401 | String unixTime = getJsonHTTP(curTimeHost, curTimeURL); 402 | 403 | unsigned long unixTimeLong = atol(unixTime.c_str())*60; 404 | String finalUnixTime = String(unixTimeLong); 405 | 406 | String weatherHost = "api.darksky.net"; 407 | String weatherURL = "/forecast/" + DarkSkyAPIKey + "/" + latLong + "," + finalUnixTime + "?exclude=minutely,flags,hourly,currently,alerts"; 408 | 409 | // Get Weather 410 | String weatherPayload = getJson(weatherHost, weatherURL); 411 | Serial.println(weatherPayload); 412 | 413 | return weatherPayload; 414 | } 415 | 416 | // This function handles the Google Geocoding API Pretty Print Output 417 | String PaperSignals::GetLatLong(String Location) 418 | { 419 | Location = urlencode(Location); 420 | char* geoHost = "maps.googleapis.com"; 421 | String geoURL = "/maps/api/geocode/json?address=" + Location + "&key=" + GeoCodingAPiKey; 422 | Serial.println(geoURL); 423 | 424 | // Get Lat Long 425 | String LatLongPayload = getJson(geoHost, geoURL); 426 | Serial.println(LatLongPayload); 427 | 428 | int index = LatLongPayload.indexOf("lat"); 429 | 430 | Serial.println(index); 431 | boolean foundColon = false; 432 | String latString = ""; 433 | for(int i = index; i < index+20; i++) 434 | { 435 | if(LatLongPayload.charAt(i) == ':') 436 | { 437 | foundColon = true; 438 | } 439 | else if(foundColon) 440 | { 441 | if(LatLongPayload.charAt(i) == ',') break; 442 | latString += LatLongPayload.charAt(i); 443 | } 444 | } 445 | 446 | latString.trim(); 447 | Serial.print("Lat String: "); 448 | Serial.println(latString); 449 | 450 | index = LatLongPayload.indexOf("lng"); 451 | 452 | Serial.println(index); 453 | foundColon = false; 454 | String lngString = ""; 455 | for(int i = index; i < index+20; i++) 456 | { 457 | if(LatLongPayload.charAt(i) == ':') 458 | { 459 | foundColon = true; 460 | } 461 | else if(foundColon) 462 | { 463 | if(LatLongPayload.charAt(i) == '\n'| LatLongPayload.charAt(i) == '\r') break; 464 | lngString += LatLongPayload.charAt(i); 465 | } 466 | } 467 | 468 | lngString.trim(); 469 | Serial.print("Lng String: "); 470 | Serial.println(lngString); 471 | 472 | return latString + "," + lngString; 473 | 474 | } 475 | 476 | void PaperSignals::RocketExecution(String JSONData) 477 | { 478 | Serial.println("Rockets"); 479 | 480 | DynamicJsonBuffer agencyBuffer; 481 | JsonObject& agencyRoot = agencyBuffer.parseObject(JSONData); 482 | String curSpaceAgency = agencyRoot["parameters"]["SpaceAgency"]; 483 | String curCountry = agencyRoot["parameters"]["RocketCountryCodes"]; 484 | 485 | char* rocketHost = "launchlibrary.net"; 486 | String rocketURL = "/1.2/launch/next/1"; 487 | String rocketPayload = getJson(rocketHost, rocketURL); 488 | Serial.println(rocketPayload); 489 | 490 | DynamicJsonBuffer jsonBufferRockets; 491 | JsonObject& rocketRoot = jsonBufferRockets.parseObject(rocketPayload); 492 | 493 | String output = rocketRoot["launches"][0]["isonet"]; 494 | 495 | if(curSpaceAgency != "" || curCountry != "") 496 | { 497 | Serial.print("Looking for "); Serial.print(curSpaceAgency); Serial.println(" Rockets"); 498 | boolean foundAgency = false; 499 | String agency = ""; 500 | int spaceAgencyNum = 0; 501 | do 502 | { 503 | String countryCode = rocketRoot["launches"][0]["location"]["pads"][0]["agencies"][spaceAgencyNum]["countryCode"]; 504 | String agencyNew = rocketRoot["launches"][0]["location"]["pads"][0]["agencies"][spaceAgencyNum]["abbrev"]; 505 | agency = agencyNew; 506 | 507 | Serial.print(countryCode); Serial.print(" "); 508 | Serial.println(agency); 509 | 510 | if(agency == curSpaceAgency) foundAgency = true; // Find your agency if defined 511 | 512 | if(countryCode == curCountry) foundAgency = true; 513 | 514 | spaceAgencyNum++; 515 | }while(agency.length() > 0); 516 | 517 | // If wrong agency found, then we'll just ignore launches 518 | if(!foundAgency) 519 | NextRocketLaunchTime = ""; 520 | } 521 | 522 | if(rocketLaunched && (millis() - rocketStartedTime) < ROCKET_ON_TIME) 523 | { 524 | MoveServoToPosition(ROCKET_LAUNCH, 10); 525 | } 526 | else if(NextRocketLaunchTime == "") 527 | { 528 | Serial.println("Initalized Launch"); 529 | 530 | NextRocketLaunchTime = output; 531 | rocketLaunched = false; 532 | 533 | MoveServoToPosition(ROCKET_NOT_LAUNCHED, 10); 534 | } 535 | else if(NextRocketLaunchTime != output) 536 | { 537 | Serial.println ("LAUNCH"); 538 | NextRocketLaunchTime = output; 539 | rocketStartedTime = millis(); 540 | rocketLaunched = true; 541 | 542 | MoveServoToPosition(ROCKET_LAUNCH, 10); 543 | } 544 | else 545 | { 546 | Serial.println("No launch yet"); 547 | 548 | rocketLaunched = false; 549 | MoveServoToPosition(ROCKET_NOT_LAUNCHED, 10); 550 | } 551 | } 552 | 553 | void PaperSignals::CountdownExecution(String JSONData) 554 | { 555 | DynamicJsonBuffer countDownBuffer; 556 | JsonObject& countDownRoot = countDownBuffer.parseObject(JSONData); 557 | String timeStamp = countDownRoot["timestamp"]; 558 | 559 | // GET START DATE 560 | int startYear = (timeStamp.charAt(0)-'0')*1000 + (timeStamp.charAt(1)-'0')*100 + (timeStamp.charAt(2)-'0')*10 + (timeStamp.charAt(3)-'0'); 561 | int startMonth = (timeStamp.charAt(5)-'0')*10 + (timeStamp.charAt(6)-'0'); 562 | int startDay = (timeStamp.charAt(8)-'0')*10 + (timeStamp.charAt(9)-'0'); 563 | 564 | Serial.print("TimeStamp Year: "); Serial.print(startYear); Serial.print("; Month: "); Serial.print(startMonth); Serial.print("; Day: "); Serial.println(startDay); 565 | 566 | // GET CURRENT DATE 567 | int curYear = 0; 568 | int curMonth = 0; 569 | int curDay = 0; 570 | const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; 571 | 572 | for(int i = 0; i < 12; i++) 573 | { 574 | int monthIndex = mostRecentDateString.indexOf(months[i]); 575 | if(monthIndex != -1) 576 | { 577 | curMonth = i+1; 578 | 579 | curDay = ((mostRecentDateString.charAt(monthIndex - 3) - '0')*10) + (mostRecentDateString.charAt(monthIndex - 2) - '0'); 580 | curYear = ((mostRecentDateString.charAt(monthIndex + 4) - '0')*1000) + ((mostRecentDateString.charAt(monthIndex + 5) - '0')*100) + ((mostRecentDateString.charAt(monthIndex + 6) - '0')*10) + (mostRecentDateString.charAt(monthIndex + 7) - '0'); 581 | } 582 | } 583 | Serial.print("Current Year: "); Serial.print(curYear); Serial.print("; Month: "); Serial.print(curMonth); Serial.print("; Day: "); Serial.println(curDay); 584 | 585 | // GET DESTINATION DATE 586 | String destTime = countDownRoot["parameters"]["date"]; 587 | 588 | int destYear = (destTime.charAt(0)-'0')*1000 + (destTime.charAt(1)-'0')*100 + (destTime.charAt(2)-'0')*10 + (destTime.charAt(3)-'0'); 589 | int destMonth = (destTime.charAt(5)-'0')*10 + (destTime.charAt(6)-'0'); 590 | int destDay = (destTime.charAt(8)-'0')*10 + (destTime.charAt(9)-'0'); 591 | Serial.print("Dest Year: "); Serial.print(destYear); Serial.print("; Month: "); Serial.print(destMonth); Serial.print("; Day: "); Serial.println(destDay); 592 | 593 | int destDays = number_of_days(destYear, destDay, destMonth); 594 | int curDays = number_of_days(curYear, curDay, curMonth); 595 | int startDays = number_of_days(startYear, startDay, startMonth); 596 | 597 | int totalDays = destDays - startDays; 598 | int daysLeft = destDays - curDays; 599 | 600 | Serial.print("Days Left: "); Serial.println(daysLeft); 601 | 602 | if(daysLeft <= 0) // Countdown done 603 | { 604 | Serial.println("Countdown Complete"); 605 | } 606 | else 607 | { 608 | double percentComplete = 1.0 - ((double)daysLeft / (double)totalDays); 609 | Serial.print("Percent Complete: "); Serial.println(percentComplete); 610 | } 611 | } 612 | 613 | void PaperSignals::TestSignalExecution(String JSONData) 614 | { 615 | if(numTestServoSwings < TEST_NUM_SWINGS) 616 | { 617 | MoveServoToPosition(TEST_FIRST_POSITION, 10); 618 | MoveServoToPosition(TEST_SECOND_POSITION, 10); 619 | numTestServoSwings++; 620 | } 621 | } 622 | 623 | void PaperSignals::StockExecution(String JSONData) 624 | { 625 | //Only poll the barchart API if a new stock has been requested or it has been more than 2 minutes 626 | if(throttleStockAPI()) return; 627 | 628 | DynamicJsonBuffer stockBuffer; 629 | JsonObject& stockRoot = stockBuffer.parseObject(JSONData); 630 | String StockSymbol = stockRoot["parameters"]["StockSymbol"]; 631 | 632 | Serial.print("Stock Symbol: "); Serial.println(StockSymbol); 633 | 634 | char* host = "marketdata.websol.barchart.com"; 635 | String url = "/getQuote.json?apikey=" + BarChartAPIKey + "&symbols=" + StockSymbol; 636 | 637 | String barChartStockJSON = getJson(host, url); 638 | JsonObject& barChartStockRoot = stockBuffer.parseObject(barChartStockJSON); 639 | double stockNumber = barChartStockRoot["results"][0]["netChange"]; 640 | 641 | if(stockNumber < 0) 642 | { 643 | String debugMsg = StockSymbol + " is down"; 644 | Serial.println(debugMsg); 645 | MoveServoToPosition(CURRENCY_DOWN, 10); 646 | } 647 | else if(stockNumber > 0) 648 | { 649 | String debugMsg = StockSymbol + " is up"; 650 | Serial.println(debugMsg); 651 | MoveServoToPosition(CURRENCY_UP, 10); 652 | } 653 | else 654 | { 655 | String debugMsg = StockSymbol + " no change"; 656 | Serial.println(debugMsg); 657 | MoveServoToPosition(CURRENCY_NO_CHANGE, 10); 658 | } 659 | } 660 | 661 | void PaperSignals::CustomExecution(String JSONData) 662 | { 663 | DynamicJsonBuffer customIntentBuffer; 664 | JsonObject& customIntentRoot = customIntentBuffer.parseObject(JSONData); 665 | String customIntentData = customIntentRoot["parameters"]["customParameter"]; 666 | 667 | Serial.print("Current Custom Parameter Data: "); Serial.println(customIntentData); 668 | } 669 | 670 | void PaperSignals::ParseIntentName(String intentName, String JSONData) 671 | { 672 | if(intentName == CryptoCurrencyType) 673 | { 674 | CryptoCurrencyExecution(JSONData); 675 | } 676 | else if(intentName == ShortsOrPantsType) 677 | { 678 | ShortsOrPantsExecution(JSONData); 679 | } 680 | else if(intentName == UmbrellaType) 681 | { 682 | UmbrellaExecution(JSONData); 683 | } 684 | else if(intentName == TimerType) 685 | { 686 | TimerExecution(JSONData); 687 | } 688 | else if(intentName == StretchBreakType) 689 | { 690 | StretchBreakExecution(JSONData); 691 | } 692 | else if(intentName == RocketLaunchType) 693 | { 694 | RocketExecution(JSONData); 695 | } 696 | else if(intentName == CountdownType) 697 | { 698 | CountdownExecution(JSONData); 699 | } 700 | else if(intentName == TestSignalType) 701 | { 702 | TestSignalExecution(JSONData); 703 | } 704 | else if(intentName == StockType) 705 | { 706 | StockExecution(JSONData); 707 | } 708 | else if(intentName.equalsIgnoreCase(CustomIntentType)) 709 | { 710 | CustomExecution(JSONData); 711 | } 712 | else 713 | { 714 | DefaultExecution(JSONData); 715 | } 716 | } 717 | 718 | String PaperSignals::getSignalByID(String signalID){ 719 | char* host = "gweb-paper-signals.firebaseio.com"; 720 | String url = "/signals/" + signalID + ".json"; 721 | 722 | String payload = getJson(host, url); 723 | 724 | DynamicJsonBuffer jsonBuffer; 725 | JsonObject& root = jsonBuffer.parseObject(payload); 726 | 727 | // Test if parsing succeeds. 728 | if (!root.success()) { 729 | Serial.println("parseObject() failed"); 730 | return "bad"; 731 | } 732 | else 733 | { 734 | String signalInfo = root["result"]; 735 | String intentName = root["result"]["metadata"]["intentName"]; 736 | String intentTimeStamp = root["result"]["timestamp"]; 737 | 738 | if(intentTimeStamp != currentIntentTimeStamp) 739 | { 740 | updatedIntentTimeStamp = true; 741 | numTestServoSwings = 0; 742 | } 743 | else { 744 | updatedIntentTimeStamp = false; 745 | } 746 | currentIntent = intentName; 747 | currentIntentTimeStamp = intentTimeStamp; 748 | 749 | return signalInfo; 750 | } 751 | } 752 | 753 | String PaperSignals::getJson(String host, String url){ 754 | 755 | Serial.print("connecting to "); 756 | Serial.println(host); 757 | 758 | if (!client.connect(host.c_str(), httpsPort)) { 759 | Serial.println("connection failed"); 760 | return "bad"; 761 | } 762 | 763 | Serial.print("requesting URL "); 764 | Serial.println(url); 765 | 766 | client.print(String("GET ") + url + " HTTP/1.1\r\n" + 767 | "Host: " + host + "\r\n" + 768 | "User-Agent: BuildFailureDetectorESP8266\r\n" + 769 | "Connection: close\r\n\r\n"); 770 | 771 | Serial.println("request sent"); 772 | 773 | while (client.connected()) { 774 | String line = client.readStringUntil('\n'); 775 | if(line.indexOf("Date") != -1) 776 | { 777 | mostRecentDateString = line; 778 | } 779 | Serial.println(line); 780 | if (line == "\r") { 781 | Serial.println("headers received"); 782 | break; 783 | } 784 | } 785 | 786 | String payload = client.readString(); 787 | 788 | #ifdef PRINT_PAYLOAD 789 | Serial.println("reply was:"); 790 | Serial.println("=========="); 791 | Serial.println(payload); 792 | Serial.println("=========="); 793 | #endif 794 | return payload; 795 | } 796 | 797 | String PaperSignals::getJsonHTTP(String host, String url){ 798 | 799 | HTTPClient http; 800 | String payload; 801 | 802 | Serial.print("[HTTP] begin...\n"); 803 | 804 | http.begin("http://"+host+url); //HTTP 805 | 806 | Serial.print("[HTTP] GET...\n"); 807 | // start connection and send HTTP header 808 | int httpCode = http.GET(); 809 | 810 | // httpCode will be negative on error 811 | if(httpCode > 0) { 812 | // HTTP header has been send and Server response header has been handled 813 | Serial.printf("[HTTP] GET... code: %d\n", httpCode); 814 | 815 | // file found at server 816 | if(httpCode == HTTP_CODE_OK) { 817 | payload = http.getString(); 818 | } 819 | } else { 820 | Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); 821 | } 822 | 823 | http.end(); 824 | 825 | 826 | #ifdef PRINT_PAYLOAD 827 | Serial.println("reply was:"); 828 | Serial.println("=========="); 829 | Serial.println(payload); 830 | Serial.println("=========="); 831 | #endif 832 | return payload; 833 | } 834 | void PaperSignals::RunPaperSignals() 835 | { 836 | String JSONData = getSignalByID(SignalID.c_str()); 837 | ParseIntentName(currentIntent, JSONData); 838 | } 839 | -------------------------------------------------------------------------------- /ArduinoSignals/APICalls.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "Credentials.h" 25 | 26 | #define SERVO_PIN 14 27 | 28 | // Servo Positions and Intent specific Numbers 29 | 30 | #define CENTER_POSITION 90 31 | 32 | // Crypto Currency 33 | #define CURRENCY_UP 180 34 | #define CURRENCY_DOWN 0 35 | #define CURRENCY_NO_CHANGE 90 36 | 37 | // Umbrella 38 | #define UMBRELLA_OPEN 60 39 | #define UMBRELLA_CLOSED 180 40 | 41 | // Shorts or Pants 42 | #define SHORTSORPANTS_PANTS 0 43 | #define SHORTSORPANTS_SHORTS 65 44 | #define SHORTSORPANTS_THRESHOLD 70.0 45 | 46 | // Timer 47 | #define TIMER_START 0 48 | #define TIMER_END 180 49 | #define TIMER_WIGGLE_TOP 0 50 | #define TIMER_WIGGLE_BOTTOM 180 51 | 52 | // RocketLaunch 53 | #define ROCKET_ON_TIME 43200000 // Rocket shows fire for 12 hours 54 | #define ROCKET_LAUNCH 0 55 | #define ROCKET_NOT_LAUNCHED 65 56 | 57 | // Celebrate 58 | #define STRETCHBREAK_TIME_REACHED 0 59 | #define STRETCHBREAK_NOT_YET 90 60 | 61 | // Test Signal 62 | #define TEST_FIRST_POSITION 10 63 | #define TEST_SECOND_POSITION 170 64 | #define TEST_NUM_SWINGS 5 65 | 66 | class PaperSignals 67 | { 68 | public: 69 | PaperSignals() {}; 70 | ~PaperSignals() {}; 71 | 72 | void StartUp(); 73 | String getJson(String host, String url); 74 | String getJsonHTTP(String host, String url); 75 | void ParseIntentName(String intentName, String JSONData); 76 | String getSignalByID(String signalID); 77 | void RunPaperSignals(); 78 | 79 | // Intent Types 80 | String CryptoCurrencyType = "CryptoCurrency"; 81 | String ShortsOrPantsType = "ShortsOrPants"; 82 | String UmbrellaType = "Umbrella"; 83 | String TimerType = "Timer"; 84 | String StretchBreakType = "StretchBreak"; 85 | String RocketLaunchType = "RocketLaunch"; 86 | String CountdownType = "Countdown"; 87 | String TestSignalType = "TestSignal"; 88 | String StockType = "Stock"; 89 | String CustomIntentType = "YOUR_CUSTOM_INTENT_NAME"; 90 | 91 | String currentIntent = ""; 92 | String currentIntentTimeStamp = ""; 93 | bool updatedIntentTimeStamp = true; 94 | 95 | unsigned long lastWeatherCall = 0; 96 | unsigned long timeBetweenWeatherCalls = 120000; 97 | bool throttleWeatherAPI(); 98 | 99 | 100 | unsigned long lastStockCall = 0; 101 | unsigned long timeBetweenStockCalls = 120000; 102 | bool throttleStockAPI(); 103 | 104 | public: 105 | 106 | // Use WiFiClientSecure class to create TLS connection 107 | WiFiClientSecure client; 108 | const int httpsPort = 443; 109 | 110 | // Signal Functions 111 | void DefaultExecution(String JSONData); 112 | void CountdownExecution(String JSONData); 113 | void TimerExecution(String JSONData); 114 | void CryptoCurrencyExecution(String JSONData); 115 | void ShortsOrPantsExecution(String JSONData); 116 | void UmbrellaExecution(String JSONData); 117 | void StretchBreakExecution(String JSONData); 118 | void RocketExecution(String JSONData); 119 | void TestSignalExecution(String JSONData); 120 | void StockExecution(String JSONData); 121 | void CustomExecution(String JSONData); 122 | String GetWeather(String JSONData); 123 | String GetLatLong(String JSONData); 124 | 125 | double GetBitcoin(); 126 | double GetEthereum(); 127 | 128 | void MoveServoToPosition(int position, int speed); 129 | 130 | double initialBitcoin; 131 | double initialEthereum; 132 | 133 | int lastTimerTime = 0; 134 | 135 | unsigned long breakTimeLength = 60000; // 1 Minute Default 136 | unsigned long breakTimeInterval = 900000; // 15 Minute Default 137 | unsigned long lastBreakTime = 0; 138 | 139 | unsigned long rocketStartedTime = 0; 140 | boolean rocketLaunched = false; 141 | 142 | int numTestServoSwings = 0; 143 | 144 | String mostRecentDateString = ""; 145 | String NextRocketLaunchTime = ""; 146 | 147 | Servo myservo; 148 | int currentServoPosition = 0; 149 | }; 150 | -------------------------------------------------------------------------------- /ArduinoSignals/ArduinoSignals.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "APICalls.h" 18 | 19 | #define ACTION_LED_PIN LED_BUILTIN 20 | #define CONNECTION_LED_PIN 2 21 | 22 | PaperSignals mySignal; 23 | 24 | void setupIndicatorLEDs() 25 | { 26 | pinMode(ACTION_LED_PIN, OUTPUT); 27 | pinMode(CONNECTION_LED_PIN, OUTPUT); 28 | } 29 | 30 | void SetActionLEDOn() 31 | { 32 | digitalWrite(ACTION_LED_PIN, false); 33 | } 34 | 35 | void SetActionLEDOff() 36 | { 37 | digitalWrite(ACTION_LED_PIN, true); 38 | } 39 | 40 | void SetConnectionLEDOn() 41 | { 42 | digitalWrite(CONNECTION_LED_PIN, false); 43 | } 44 | 45 | void SetConnectionLEDOff() 46 | { 47 | digitalWrite(CONNECTION_LED_PIN, true); 48 | } 49 | 50 | void setup() { 51 | 52 | setupIndicatorLEDs(); 53 | SetActionLEDOn(); 54 | SetConnectionLEDOff(); 55 | 56 | Serial.begin(115200); 57 | Serial.println(); 58 | Serial.print("connecting to "); 59 | Serial.println(SSID); 60 | 61 | WiFi.begin(SSID.c_str(), Password.c_str()); 62 | 63 | while (WiFi.status() != WL_CONNECTED) { 64 | delay(250); 65 | Serial.print("."); 66 | } 67 | Serial.println(""); 68 | Serial.println("WiFi connected"); 69 | Serial.print("IP address: "); 70 | Serial.println(WiFi.localIP()); 71 | 72 | while(WiFi.status() != WL_CONNECTED); 73 | 74 | SetConnectionLEDOn(); 75 | 76 | mySignal.StartUp(); 77 | } 78 | 79 | void loop() { 80 | SetActionLEDOn(); 81 | 82 | // wait for WiFi connection 83 | if(WiFi.status() == WL_CONNECTED) { 84 | SetConnectionLEDOn(); 85 | mySignal.RunPaperSignals(); 86 | } 87 | else{ 88 | SetConnectionLEDOff(); 89 | } 90 | 91 | SetActionLEDOff(); 92 | 93 | delay(5000); 94 | } 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /ArduinoSignals/Credentials.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /* WiFi Network must be 2.4GHz */ 18 | const String SSID = "YOUR_WIFI_NETWORK"; 19 | const String Password = "YOUR_WIFI_PASSWORD"; 20 | 21 | /* Your Signal ID should be lowercase, and each word should be separated by dashes. 22 | If Google Assistant spoke too quickly, you can ask to repeat the identifier at any 23 | time be saying "What is the setup code for [SIGNAL NAME]?" */ 24 | const String SignalID = "your-signal-id"; 25 | 26 | /* Enter the below API keys to track the weather on Umbrella Signal and Pants Signal. */ 27 | const String DarkSkyAPIKey = "YOUR_DARK_SKY_API_KEY"; 28 | const String GeoCodingAPiKey = "YOUR_GEOCODING_API_KEY"; 29 | 30 | /* Enter the below API key to track stocks on Arrow Signal. */ 31 | const String BarChartAPIKey = "YOUR_BAR_CHART_API_KEY"; 32 | 33 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 |
4 | This project is no longer actively maintained by the Google Creative Lab but remains here in a read-only Archive mode so that it can continue to assist developers that may find the examples helpful. We aren’t able to address all pull requests or bug reports but outstanding issues will remain in read-only mode for reference purposes. Also, please note that some of the dependencies may not be up to date and there hasn’t been any QA done in a while so your mileage may vary. 5 |

6 | For more details on how Archiving affects Github repositories see this documentation . 7 |

8 | We welcome users to fork this repository should there be more useful, community-driven efforts that can help continue what this project began. 9 |
12 | 13 | 14 | # Paper Signals 15 | Paper Signals is a [Voice Experiment](https://experiments.withgoogle.com/voice) that lets you make little voice controlled objects that track simple things, like weather. You can build any of the example Paper Signals we have on [g.co/papersignals](https://papersignals.withgoogle.com/#), or use this repo to make your own Custom Signal. 16 | 17 | ![PaperSignals](./images/image2.jpg) 18 | 19 | This is an experiment, not an official Google product. We will do our best to support and maintain this experiment but your mileage may vary. 20 | 21 | ## Technology 22 | Paper Signals is built on [Actions on Google](https://developers.google.com/actions/), the platform that allows you to make things for the Google Assistant and the Google Home. It uses [Dialogflow](https://dialogflow.com/) to handle understanding what the user says, [Firebase Cloud Functions](https://firebase.google.com/docs/functions/) and [Firebase Database](https://firebase.google.com/docs/database/) for backend code and storing data, and Arduino for programming the electronics. 23 | 24 | This repo contains the Arduino files used to make API calls and move servos. This lets users set up their own individual signals using the same Firebase and Actions backend that our pre-made examples use. 25 | 26 | Instructions for how to set up one of the pre-made, example signals can be found at [g.co/papersignals](https://papersignals.withgoogle.com/#) (including a more detailed explanation for setting up the electronics). Below in this README, we include instructions on how to make a Custom Signal and set up a Custom Intent. 27 | 28 | ### Custom Intents 29 | Custom Intents allow you to program your Paper Signal to track anything with a few lines of code and voice commands. Creating and programming a custom intent can be done in 4 steps. 30 | 31 | 1. Open the Paper Signals code in Arduino and use the tabs to open the APICalls.h file. 32 | 33 | 2. Give your custom intent a name and replace the text “YOUR_CUSTOM_INTENT_NAME” with your custom intent’s name! 34 | ![Setting Custom Intent Name](./images/image6.png) 35 | 36 | 3. Use the tabs on the top of the Arduino Software to open the APICalls.cpp file. 37 | 38 | 4. Find the function called CustomExecution() 39 | ![Find CustomExecution()](./images/image3.png) 40 | 41 | 5. Any code written in this function will run when you ask Paper Signals to run your custom intent. The variable CustomIntentData is a string that represents any parameter you gave your custom intent. 42 | 43 | 6. To run your custom intent, open Paper Signals and say “Talk to Paper Signals”, then “Create a new Signal called Custom Signal.” Once you have that created, say “set my Custom Signal to a custom intent named ____” 44 | 45 | * __Example__: “Set my __Custom Signal__ to a custom intent named power.” 46 | *In this example, Custom Signal is the signal name and Power is the custom 47 | intent name.* 48 | 49 | 7. To set a parameter for your custom intent to use, say: “Set the custom Parameter for __Custom Signal__ to ____” 50 | 51 | * __Example__: set the custom parameter for Custom Signal to Fast. 52 | *In this example, __Custom Signal__’s custom intent is going to use __Fast__ as a custom Parameter* 53 | 54 | ### Programming Your Custom Intent Functions 55 | You will usually want to move servos and check online APIs with your custom intents, so there are 2 chunks of very useful code to understand. 56 | 57 | * Getting API Data 58 | * The code below shows the standard Paper Signals boilerplate for grabbing data from a remote API. This particular examples shows how we get rocket launch times as a string!![API Boilerplate](./images/image5.png) 59 | 60 | * Moving Servos 61 | * Paper Signals has a function for moving a servo with default settings. The position argument of this function takes an angle between 0 and 180 (This is the angle that the servo will move to), and speed takes an integer. Speed is actually the amount of time spent on each single angle of the servo. So a lower speed value will make the servo move faster. ![define MoveServoToPosition()](./images/image1.png) 62 | * The example below moves the servo to 20 degrees, spending 10ms on each degree between the angle the servo is started at and the destination. ![Use MoveServoToPosition()](./images/image4.png) 63 | 64 | ## Contributors 65 | Made by [Isaac Blankensmith](http://www.isaacblankensmith.com/), [Dylan Fashbaugh](https://github.com/dfashbaugh), [Jacob Keith](https://github.com/KacobJeith), and [James DeVito](https://github.com/jmzjmzjmz) with [Smooth Technology](https://smooth.technology) and friends at the Google Creative Lab. 66 | 67 | ### Other Contributors 68 | * [Nicole He](https://github.com/nicolehe) 69 | * [Dan Motzenbecker](https://github.com/dmotz) 70 | 71 | ## License 72 | Copyright 2017 Google Inc. 73 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 74 | http://www.apache.org/licenses/LICENSE-2.0 75 | 76 | 77 | Unless required by applicable law or agreed to in writing, software distributed under the License 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. 78 | 79 | ## Final Thoughts 80 | We encourage open sourcing projects as a way of learning from each other. Please respect our and other creators’ rights, including copyright and trademark rights when present, when sharing these works and creating derivative work. 81 | If you want more info on Google's policy, you can find that [here](https://www.google.com/policies/). 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecreativelab/paper-signals/42cba3ddaf14e95a26a1137a2b688e5ecfca4423/images/image1.png -------------------------------------------------------------------------------- /images/image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecreativelab/paper-signals/42cba3ddaf14e95a26a1137a2b688e5ecfca4423/images/image2.jpg -------------------------------------------------------------------------------- /images/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecreativelab/paper-signals/42cba3ddaf14e95a26a1137a2b688e5ecfca4423/images/image3.png -------------------------------------------------------------------------------- /images/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecreativelab/paper-signals/42cba3ddaf14e95a26a1137a2b688e5ecfca4423/images/image4.png -------------------------------------------------------------------------------- /images/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecreativelab/paper-signals/42cba3ddaf14e95a26a1137a2b688e5ecfca4423/images/image5.png -------------------------------------------------------------------------------- /images/image6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlecreativelab/paper-signals/42cba3ddaf14e95a26a1137a2b688e5ecfca4423/images/image6.png --------------------------------------------------------------------------------