├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.markdown ├── LICENSE ├── Makefile ├── README.markdown ├── RELEASES.md ├── examples ├── .DS_Store ├── .eslintrc ├── api │ ├── api.js │ └── api.markdown ├── arduinos_and_skynet │ ├── multiple_arduinos_multiple_skynet_connections.js │ └── multiple_arduinos_one_skynet_connection.js ├── cattoy │ ├── cattoy.js │ └── cattoy.markdown ├── conway_sphero │ ├── conway_sphero.js │ ├── conway_sphero.markdown │ └── fluent-conway_sphero.js ├── crazyflie │ ├── crazyflie.js │ └── crazyflie.markdown ├── digispark_blink │ ├── blink.js │ └── blink.markdown ├── halt │ └── halt.js ├── hello │ ├── fluent-hello.js │ ├── hello.js │ └── hello.markdown ├── keyboard │ ├── keyboard.js │ └── keyboard.markdown ├── leap_ardrone │ ├── leap_ardrone.js │ └── leap_ardrone.markdown ├── leap_arduino │ ├── leap_arduino.js │ └── leap_arduino.markdown ├── master │ ├── master.js │ └── master.markdown ├── rapiro │ └── rapiro_servo.js ├── robot_commands │ ├── robot_commands.js │ └── robot_commands.markdown ├── salesforce │ ├── salesforce.js │ └── salesforce.markdown ├── sf-sphero │ ├── sf-sphero.js │ └── sf-sphero.markdown ├── skynet │ ├── skynet-blink.js │ └── skynet-blink.markdown ├── sphero-pebble-sf │ ├── sphero-pebble-sf.js │ └── sphero-pebble-sf.markdown ├── sphero_shakeometer │ └── sphero_shakeometer.js ├── start-device │ └── start-device.js └── travis │ ├── travis.js │ └── travis.markdown ├── index.js ├── lib ├── adaptor.js ├── api.js ├── basestar.js ├── config.js ├── driver.js ├── initializer.js ├── io │ ├── digital-pin.js │ └── utils.js ├── logger.js ├── mcp.js ├── registry.js ├── robot.js ├── test │ ├── loopback.js │ ├── ping.js │ ├── test-adaptor.js │ └── test-driver.js ├── utils.js ├── utils │ ├── helpers.js │ └── monkey-patches.js └── validator.js ├── package.json └── spec ├── .eslintrc ├── helper.js ├── lib ├── adaptor.spec.js ├── api.spec.js ├── basestar.spec.js ├── config.spec.js ├── cylon.spec.js ├── digital-pin.spec.js ├── driver.spec.js ├── initializer.spec.js ├── io │ └── utils.js ├── logger.spec.js ├── mcp.spec.js ├── registry.spec.js ├── robot.spec.js ├── utils.spec.js └── utils │ ├── helpers.spec.js │ └── monkey-patches.spec.js └── support └── mock_module.js /.eslintrc: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | 4 | globals: 5 | every: true 6 | after: true 7 | constantly: true 8 | 9 | rules: 10 | camelcase: [2, {properties: "always"}] 11 | comma-dangle: "always-multiline" 12 | comma-spacing: [2, {before: false, after: true}] 13 | comma-style: [2, "last"] 14 | handle-callback-err: [2, "^.*(e|E)rr" ] 15 | indent: [2, 2] 16 | key-spacing: [2, { beforeColon: false, afterColon: true }] 17 | max-depth: [1, 3] 18 | max-len: [1, 80, 4] 19 | max-nested-callbacks: [1, 3] 20 | no-cond-assign: 2 21 | no-constant-condition: 2 22 | no-dupe-args: 2 23 | no-dupe-keys: 2 24 | no-else-return: 2 25 | no-empty: 2 26 | no-lonely-if: 2 27 | no-multiple-empty-lines: 2 28 | no-nested-ternary: 2 29 | no-reserved-keys: 2 30 | no-self-compare: 2 31 | no-sync: 1 32 | no-throw-literal: 2 33 | no-underscore-dangle: 0 34 | quote-props: [2, "as-needed"] 35 | quotes: [2, "double", "avoid-escape"] 36 | radix: 2 37 | semi-spacing: [2, {before: false, after: true}] 38 | semi: [2, "always"] 39 | space-after-keywords: [2, "always"] 40 | space-before-blocks: [2, "always"] 41 | space-before-function-paren: [1, "never"] 42 | space-in-parens: [2, "never"] 43 | spaced-line-comment: [1, "always"] 44 | strict: [2, "global"] 45 | valid-jsdoc: 2 46 | yoda: [2, "never"] 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | npm-debug.log 3 | /coverage/ 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /coverage/ 2 | /examples/ 3 | /spec/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - '0.10' 5 | - '0.12' 6 | - '4' 7 | - '5' 8 | - '6' 9 | before_install: 10 | - "mkdir -p ~/.npm" 11 | before_script: 12 | - npm install -g istanbul codeclimate-test-reporter 13 | script: 14 | - make ci 15 | - CODECLIMATE_REPO_TOKEN=d3aad610220b6eaf4f51e38393c1b62586b1d68b898b42e418d9c2a8e0a7cb0d codeclimate-test-reporter < coverage/lcov.info 16 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This Code of Conduct is adapted from [Rust's wonderful 4 | CoC](http://www.rust-lang.org/conduct.html). 5 | 6 | * We are committed to providing a friendly, safe and welcoming 7 | environment for all, regardless of gender, sexual orientation, 8 | disability, ethnicity, religion, or similar personal characteristic. 9 | * Please avoid using overtly sexual nicknames or other nicknames that 10 | might detract from a friendly, safe and welcoming environment for 11 | all. 12 | * Please be kind and courteous. There's no need to be mean or rude. 13 | * Respect that people have differences of opinion and that every 14 | design or implementation choice carries a trade-off and numerous 15 | costs. There is seldom a right answer. 16 | * Please keep unstructured critique to a minimum. If you have solid 17 | ideas you want to experiment with, make a fork and see how it works. 18 | * We will exclude you from interaction if you insult, demean or harass 19 | anyone. That is not welcome behaviour. We interpret the term 20 | "harassment" as including the definition in the [Citizen Code of 21 | Conduct](http://citizencodeofconduct.org/); if you have any lack of 22 | clarity about what might be included in that concept, please read 23 | their definition. In particular, we don't tolerate behavior that 24 | excludes people in socially marginalized groups. 25 | * Private harassment is also unacceptable. No matter who you are, if 26 | you feel you have been or are being harassed or made uncomfortable 27 | by a community member, please contact one of the channel ops or any 28 | of the TC members immediately with a capture (log, photo, email) of 29 | the harassment if possible. Whether you're a regular contributor or 30 | a newcomer, we care about making this community a safe place for you 31 | and we've got your back. 32 | * Likewise any spamming, trolling, flaming, baiting or other 33 | attention-stealing behaviour is not welcome. 34 | * Avoid the use of personal pronouns in code comments or 35 | documentation. There is no need to address persons when explaining 36 | code (e.g. "When the developer") 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Cylon.js 2 | 3 | This document is based on the [Node.js contribution guidelines](https://github.com/nodejs/node/blob/master/CONTRIBUTING.md) thank you! 4 | 5 | ## Code of Conduct 6 | 7 | The Code of Conduct explains the *bare minimum* behavior 8 | expectations the Cylon.js project requires of its contributors. 9 | [Please read it before participating.](./CODE_OF_CONDUCT.md) 10 | 11 | ## Issue Contributions 12 | 13 | When opening new issues or commenting on existing issues on this repository 14 | please make sure discussions are related to concrete technical issues with the 15 | Cylon.js software. 16 | 17 | ## Code Contributions 18 | 19 | The Cylon.js project welcomes new contributors. 20 | 21 | This document will guide you through the contribution process. 22 | 23 | What do you want to contribute? 24 | 25 | - I want to otherwise correct or improve the docs or examples 26 | - I want to report a bug 27 | - I want to add some feature or functionality to an existing hardware platform 28 | - I want to add support for a new hardware platform 29 | 30 | Descriptions for each of these will be provided below. 31 | 32 | ## General Guidelines 33 | 34 | * All patches must be provided under the Apache 2.0 License 35 | * Please use the -s option in git to "sign off" that the commit is your work and you are providing it under the Apache 2.0 License 36 | * Submit a Github Pull Request to the appropriate branch and ideally discuss the changes with us in IRC. 37 | * We will look at the patch, test it out, and give you feedback. 38 | * Avoid doing minor whitespace changes, renamings, etc. along with merged content. These will be done by the maintainers from time to time but they can complicate merges and should be done seperately. 39 | * Take care to maintain the existing coding style. 40 | * Add unit tests for any new or changed functionality & lint and test your code using `make test` and `make lint`. 41 | * All pull requests should be "fast forward" 42 | * If there are commits after yours use `git rebase -i ` 43 | * If you have local changes you may need to use `git stash` 44 | * For git help see [progit](http://git-scm.com/book) which is an awesome (and free) book on git 45 | 46 | ## Developer's Certificate of Origin 1.0 47 | 48 | By making a contribution to this project, I certify that: 49 | 50 | * (a) The contribution was created in whole or in part by me and I 51 | have the right to submit it under the open source license indicated 52 | in the file; or 53 | * (b) The contribution is based upon previous work that, to the best 54 | of my knowledge, is covered under an appropriate open source license 55 | and I have the right under that license to submit that work with 56 | modifications, whether created in whole or in part by me, under the 57 | same open source license (unless I am permitted to submit under a 58 | different license), as indicated in the file; or 59 | * (c) The contribution was provided directly to me by some other 60 | person who certified (a), (b) or (c) and I have not modified it. 61 | -------------------------------------------------------------------------------- /CONTRIBUTORS.markdown: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | Cylon.js exists thanks to the efforts of the following hardworking humans who have worked on the core, adaptors, drivers, or documentation: 4 | 5 | - Aaron Blondeau ([@aaronblondeau](https://github.com/aaronblondeau)) 6 | - Quincy Acklen ([@acklenx](https://github.com/acklenx)) 7 | - Adam Hunter ([@adamrhunter](https://github.com/adamrhunter)) 8 | - Andrew Nesbitt ([@andrew](https://github.com/andrew)) 9 | - Avner Cohen ([@AvnerCohen](https://github.com/AvnerCohen)) 10 | - Michał ([@bartmichu](https://github.com/bartmichu)) 11 | - Ben Evans ([@bencevans](https://github.com/bencevans)) 12 | - Brad Midgley ([@bmidgley](https://github.com/bmidgley)) 13 | - Sebastián González ([@brutalchrist](https://github.com/brutalchrist)) 14 | - Caleb Oller ([@caleboller](https://github.com/caleboller)) 15 | - Chris Boette ([@chrisbodhi](https://github.com/chrisbodhi)) 16 | - Christian Porzig ([@chrisfp](https://github.com/chrisfp)) 17 | - Chris Mattheiu ([@chrismatthieu](https://github.com/chrismatthieu)) 18 | - Chris Taylor ([@ChrisTheBaron](https://github.com/ChrisTheBaron)) 19 | - Daniel Portales ([@daetherius](https://github.com/daetherius)) 20 | - Daniel Lamb ([@daniellmb](https://github.com/daniellmb)) 21 | - Ron Evans ([@deadprogram](https://github.com/deadprogram)) 22 | - Derrell Lipman ([@derrell](https://github.com/derrell)) 23 | - Daniel Fischer ([@dfischer](https://github.com/dfischer)) 24 | - Edgar Silva ([@edgarSilva](https://github.com/edgarSilva)) 25 | - Eric Terpstra ([@ericterpstra](https://github.com/ericterpstra)) 26 | - Evoliofly ([@Evoliofly](https://github.com/Evoliofly)) 27 | - Fábio Franco Uechi ([@fabito](https://github.com/fabito)) 28 | - Felix Sanz ([@felixsanz](https://github.com/felixsanz)) 29 | - gorhgorh ([@gorhgorh](https://github.com/gorhgorh)) 30 | - Guillaume normand ([@gui2laume](https://github.com/gui2laume)) 31 | - Theron Boerner ([@hunterboerner](https://github.com/hunterboerner)) 32 | - James Brown ([@ibjhb](https://github.com/ibjhb)) 33 | - Yogi ([@iyogeshjoshi](https://github.com/iyogeshjoshi)) 34 | - Janaka Abeywardhana ([@janaka](https://github.com/janaka)) 35 | - Jay Wengrow ([@jaywengrow](https://github.com/jaywengrow)) 36 | - Jason Sewell ([@jaywon](https://github.com/jaywon)) 37 | - Jarrod Ribble ([@jribble](https://github.com/jribble)) 38 | - Jason Solis ([@jsolis](https://github.com/jsolis)) 39 | - Julian Cheal ([@juliancheal](https://github.com/juliancheal)) 40 | - Justin Zemlyansky ([@JustInDevelopment](https://github.com/JustInDevelopment)) 41 | - Justin Smith ([@justinisamaker](https://github.com/justinisamaker)) 42 | - Philippe Charrière ([@k33g](https://github.com/k33g)) 43 | - Kraig Walker ([@KraigWalker](https://github.com/KraigWalker)) 44 | - Loren West ([@lorenwest](https://github.com/lorenwest)) 45 | - Luis Godinez ([@luisgodinez](https://github.com/luisgodinez)) 46 | - Mario "Kuroir" Ricalde ([@MarioRicalde](https://github.com/MarioRicalde)) 47 | - Matheus Mariano ([@matheusmariano](https://github.com/matheusmariano)) 48 | - Michael Smith ([@michaelshmitty](https://github.com/michaelshmitty)) 49 | - Morten Høybye Frederiksen ([@mortenf](https://github.com/mortenf)) 50 | - Guido García ([@palmerabollo](https://github.com/palmerabollo)) 51 | - Peter deHaan ([@pdehaan](https://github.com/pdehaan)) 52 | - peterainbow ([@peterainbow](https://github.com/peterainbow)) 53 | - Rafael Magaña ([@rafmagana](https://github.com/rafmagana)) 54 | - Reid Carlberg ([@ReidCarlberg](https://github.com/ReidCarlberg)) 55 | - Sarah Hui ([@sehsarah](https://github.com/sehsarah)) 56 | - Mike Skalnik ([@skalnik](https://github.com/skalnik)) 57 | - Javier Cervantes ([@solojavier](https://github.com/solojavier)) 58 | - SonicRadish ([@sonicradish](https://github.com/sonicradish)) 59 | - Andrew Stewart ([@stewart](https://github.com/stewart)) 60 | - Tomasz Szymanski ([@szimano](https://github.com/szimano)) 61 | - Wojciech Erbetowski ([@wojtekerbetowski](https://github.com/wojtekerbetowski)) 62 | - Gize Bonilla ([@XixeBombilla](https://github.com/XixeBombilla)) 63 | - Jasson Qasqant ([@yeco](https://github.com/yeco)) 64 | - Nathan Zankich ([@zankavrogin](https://github.com/zankavrogin)) 65 | - Adrian Zankich ([@zankich](https://github.com/zankich)) 66 | 67 | Thank you! 68 | 69 | Please join us, we'd love your contribution too. 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2016 The Hybrid Group 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN := ./node_modules/.bin 2 | TEST_FILES := spec/helper.js $(shell find spec/lib -type f -name "*.js") 3 | 4 | VERSION := $(shell node -e "console.log(require('./package.json').version)") 5 | 6 | .PHONY: default cover test bdd lint ci release 7 | 8 | default: lint test 9 | 10 | test: 11 | @$(BIN)/mocha --colors -R dot $(TEST_FILES) 12 | 13 | bdd: 14 | @$(BIN)/mocha --colors -R spec $(TEST_FILES) 15 | 16 | cover: 17 | @istanbul cover $(BIN)/_mocha $(TEST_FILES) --report lcovonly -- -R spec 18 | 19 | lint: 20 | @$(BIN)/eslint lib spec examples 21 | 22 | ci: lint cover 23 | 24 | release: lint test 25 | @git push origin master 26 | @git checkout release ; git merge master ; git push ; git checkout master 27 | @git tag -m "$(VERSION)" v$(VERSION) 28 | @git push --tags 29 | @npm publish ./ 30 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | ## Release History 2 | 3 | Version | Notes 4 | ------- | ----- 5 | 1.3.0 | Smarter module loading rules, adaptor alias 6 | 1.2.0 | Dynamic connections & devices, more time helpers, simplified logging 7 | 1.1.0 | Clean ups, refactorings, misc. bug fixes. 8 | 1.0.0 | Remove deprecated Device and Connection syntax, add Basestar#respond method 9 | 0.22.2 | Bug-fix for Registry loader 10 | 0.22.1 | Remove lodash, misc. bug fixes 11 | 0.22.0 | API extraction, new devices syntax. 12 | 0.21.2 | Update Robeaux version 13 | 0.21.1 | Add back debug logging for starting/connecting devices/connections 14 | 0.21.0 | Remove Connection/Device objects, update Robot connection/device syntax, fluent syntax updates 15 | 0.20.2 | Correct API issues, possible issue with test setups 16 | 0.20.1 | Revert accidental scrict handling of param in driver initializer 17 | 0.20.0 | Browser support, new module loading, log level support, misc. development changes 18 | 0.19.1 | Correct issue with dynamic method proxying 19 | 0.19.0 | Fluent syntax, improved start/halt, various other updates 20 | 0.18.0 | Updates Robot and Driver commands structure 21 | 0.17.0 | Updates to API to match CPPP-IO spec 22 | 0.16.0 | New IO Utils, removal of Utils#bind, add Adaptor#_noop method. 23 | 0.15.1 | Fixed issue with the API on Tessel 24 | 0.15.0 | Better halting, cleaner startup, removed 'connect' and 'start' events, and misc other cleanups/refactors. 25 | 0.14.0 | Removal of node-namespace and misc. cleanup 26 | 0.13.3 | Fixes bug with disconnect functions not being called. 27 | 0.13.2 | Use pure Express, adds server-sent-events, upd API. 28 | 0.13.1 | Add API authentication and HTTPS support 29 | 0.13.0 | Set minimum Node version to 0.10.20, add utils to global namespace and improve initialization routines 30 | 0.12.0 | Extraction of CLI tooling 31 | 0.11.2 | bugfixes 32 | 0.11.0 | Refactor into pure JavaScript 33 | 0.10.4 | Add JS helper functions 34 | 0.10.3 | Fix dependency issue 35 | 0.10.2 | Create connections convenience vars, refactor config loading 36 | 0.10.1 | Updates required for test driven robotics, update Robeaux version, bugfixes 37 | 0.10.0 | Use Robeaux UX, add CLI commands for helping connect to devices, bugfixes 38 | 0.9.0 | Add AngularJS web interface to API, extensible commands for CLI 39 | 0.8.0 | Refactored Adaptor and Driver into proper base classes for easier authoring of new modules 40 | 0.7.0 | cylon command for generating new adaptors, support code for better GPIO support, literate examples 41 | 0.6.0 | API exposes robot commands, fixes issues in driver/adaptor init 42 | 0.5.0 | Improve API, add GPIO support for reuse in adaptors 43 | 0.4.0 | Refactor proxy in Cylon.Basestar, improve API 44 | 0.3.0 | Improved Cylon.Basestar, and added API 45 | 0.2.0 | Cylon.Basestar to help develop external adaptors/drivers 46 | 0.1.0 | Initial release for ongoing development 47 | -------------------------------------------------------------------------------- /examples/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hybridgroup/cylon/0ba0301318fefd337900a121dffef500ca453a09/examples/.DS_Store -------------------------------------------------------------------------------- /examples/.eslintrc: -------------------------------------------------------------------------------- 1 | rules: 2 | camelcase: 0 3 | -------------------------------------------------------------------------------- /examples/api/api.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | // ensure you install the API plugin first: 6 | // $ npm install cylon-api-http 7 | Cylon.api({ 8 | host: "0.0.0.0", 9 | port: "8080" 10 | }); 11 | 12 | var bots = { 13 | Thelma: "/dev/rfcomm0", 14 | Louise: "/dev/rfcomm1" 15 | }; 16 | 17 | Object.keys(bots).forEach(function(name) { 18 | var port = bots[name]; 19 | 20 | Cylon.robot({ 21 | name: name, 22 | 23 | connections: { 24 | sphero: { adaptor: "sphero", port: port } 25 | }, 26 | 27 | devices: { 28 | sphero: { driver: "sphero" } 29 | }, 30 | 31 | work: function(my) { 32 | every((1).seconds(), function() { 33 | console.log(my.name); 34 | my.sphero.setRandomColor(); 35 | my.sphero.roll(60, Math.floor(Math.random() * 360)); 36 | }); 37 | } 38 | }); 39 | }); 40 | 41 | Cylon.start(); 42 | -------------------------------------------------------------------------------- /examples/api/api.markdown: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | In this Cylon example, we'll be demonstrating the HTTP API that Cylon spins up 4 | when it starts. 5 | 6 | This example involves two Spheros, so before you start make sure you have the 7 | `cylon-sphero` module installed. 8 | 9 | First, let's import Cylon: 10 | 11 | var Cylon = require('../..'); 12 | 13 | Next up, we'll configure the API Cylon will serve, telling it to serve on port 14 | `8080`. 15 | 16 | // ensure you install the API plugin first: 17 | // $ npm install cylon-api-http 18 | Cylon.api({ 19 | host: '0.0.0.0', 20 | port: '8080' 21 | }); 22 | 23 | Since we're making two very similar robots (Spheros, in this case), let's put 24 | the different parts of each robot in objects so we can initialize them later. 25 | The only differences between the bots are their names and the port they'll be 26 | using. 27 | 28 | var bots = { 29 | 'Thelma': '/dev/rfcomm0', 30 | 'Louise': '/dev/rfcomm1' 31 | }; 32 | 33 | Now we can define the basic robot both of our Sphero robots will be based on. 34 | 35 | Object.keys(bots).forEach(function(name) { 36 | var port = bots[name]; 37 | 38 | Cylon.robot({ 39 | name: name, 40 | 41 | Both robots will be connecting to Spheros, and so using the cylon-sphero 42 | adaptor: 43 | 44 | connections: { 45 | sphero: { adaptor: 'sphero', port: port } 46 | }, 47 | 48 | And both will be connecting to the same kind of device (you guessed it, 49 | a Sphero). 50 | 51 | devices: { 52 | sphero: { driver: 'sphero' } 53 | }, 54 | 55 | Both robots will be performing the same kind of work as well. Every second, 56 | they'll print their name to the console, set themselves to a random color, and 57 | roll in a random direction. 58 | 59 | work: function(my) { 60 | every((1).seconds(), function() { 61 | console.log(my.name); 62 | my.sphero.setRandomColor(); 63 | my.sphero.roll(60, Math.floor(Math.random() * 360)); 64 | }); 65 | } 66 | }); 67 | 68 | And now that Cylon has all the robots we're intending to give it, let's get 69 | started! 70 | 71 | Cylon.start(); 72 | 73 | Now Cylon will start up the robots and their devices, as well as an API server 74 | listening on `0.0.0.0:8080`. 75 | -------------------------------------------------------------------------------- /examples/arduinos_and_skynet/multiple_arduinos_multiple_skynet_connections.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | var bots = [ 6 | { 7 | port: "/dev/ttyACM0", 8 | uuid: "96630051-a3dc-11e3-8442-5bf31d98c912", 9 | token: "2s67o7ek98pycik98f43reqr90t6s9k9" 10 | }, 11 | 12 | { 13 | port: "/dev/ttyACM1", 14 | uuid: "e8f942f1-a49c-11e3-9270-795e22e700d8", 15 | token: "0lpxpyafz7z7u8frgvp44g8mbr7o80k9" 16 | }, 17 | ]; 18 | 19 | bots.forEach(function(bot) { 20 | Cylon.robot({ 21 | connections: { 22 | arduino: { adaptor: "firmata", port: bot.port }, 23 | skynet: { adaptor: "skynet", uuid: bot.uuid, token: bot.port } 24 | }, 25 | 26 | devices: { 27 | led13: { driver: "led", pin: 13, connection: "arduino" } 28 | }, 29 | 30 | work: function(my) { 31 | my.skynet.on("message", function(data) { 32 | if (data.led13 === "on") { 33 | my.led13.turnOn(); 34 | } else if (data.led13 === "off") { 35 | my.led13.turnOff(); 36 | } 37 | 38 | console.log("Skynet instance on '" + my.name + "' is listening"); 39 | }); 40 | } 41 | }); 42 | }); 43 | 44 | Cylon.start(); 45 | -------------------------------------------------------------------------------- /examples/arduinos_and_skynet/multiple_arduinos_one_skynet_connection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | var arduinos = [ 6 | { 7 | name: "arduino0", 8 | port: "/dev/ttyACM0", 9 | devices: { 10 | led00: { driver: "led", pin: 13 } 11 | } 12 | }, 13 | 14 | { 15 | name: "arduin01", 16 | port: "/dev/ttyACM1", 17 | devices: { 18 | led10: { driver: "led", pin: 11 }, 19 | led11: { driver: "led", pin: 12 }, 20 | led12: { driver: "led", pin: 13 } 21 | } 22 | } 23 | ]; 24 | 25 | Cylon.robot({ 26 | name: "SkynetBot", 27 | 28 | connections: { 29 | skynet: { 30 | adaptor: "skynet", 31 | uuid: "96630051-a3dc-11e3-8442-5bf31d98c912", 32 | token: "2s67o7ek98pycik98f43reqr90t6s9k9" 33 | } 34 | }, 35 | 36 | handler: function(data) { 37 | if (data.payload == null) { 38 | return; 39 | } 40 | 41 | console.log("Data: ", data); 42 | 43 | for (var i in data.payload.robots) { 44 | var robot = data.payload.robots[i], 45 | bot = Cylon.robots[robot.name]; 46 | 47 | if (robot.cmd === "on") { 48 | bot.devices[robot.device].turnOn(); 49 | } else { 50 | bot.devices[robot.device].turnOff(); 51 | } 52 | } 53 | }, 54 | 55 | work: function(my) { 56 | my.skynet.on("message", my.handler); 57 | console.log("Skynet is listening"); 58 | } 59 | }); 60 | 61 | arduinos.forEach(function(bot) { 62 | Cylon.robot({ 63 | name: bot.name, 64 | 65 | connections: { 66 | arduino: { adaptor: "firmata", port: bot.port } 67 | }, 68 | 69 | devices: bot.devices, 70 | 71 | work: function(my) { 72 | console.log(my.name + " is online"); 73 | } 74 | }); 75 | }); 76 | 77 | Cylon.start(); 78 | -------------------------------------------------------------------------------- /examples/cattoy/cattoy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | Cylon.robot({ 6 | connections: { 7 | digispark: { adaptor: "digispark" }, 8 | leapmotion: { adaptor: "leapmotion" } 9 | }, 10 | 11 | devices: { 12 | servo1: { driver: "servo", pin: 0, connection: "digispark" }, 13 | servo2: { driver: "servo", pin: 1, connection: "digispark" }, 14 | leapmotion: { driver: "leapmotion", connection: "leapmotion" } 15 | }, 16 | 17 | work: function(my) { 18 | my.x = 90; 19 | my.z = 90; 20 | 21 | my.leapmotion.on("hand", function(hand) { 22 | my.x = hand.palmX.fromScale(-300, 300).toScale(30, 150); 23 | my.z = hand.palmZ.fromScale(-300, 300).toScale(30, 150); 24 | }); 25 | 26 | every(100, function() { 27 | my.servo1.angle(my.x); 28 | my.servo2.angle(my.z); 29 | 30 | var str = "Current Angle: "; 31 | str += my.servo1.currentAngle(); 32 | str += ", "; 33 | str += my.servo2.currentAngle(); 34 | 35 | console.log(str); 36 | }); 37 | } 38 | }).start(); 39 | -------------------------------------------------------------------------------- /examples/cattoy/cattoy.markdown: -------------------------------------------------------------------------------- 1 | # Cattoy 2 | 3 | First, let's import Cylon: 4 | 5 | var Cylon = require('../..'); 6 | 7 | Now that we have Cylon imported, we can start defining our robot 8 | 9 | Cylon.robot({ 10 | 11 | Let's define the connections and devices: 12 | 13 | connections: { 14 | digispark: { adaptor: 'digispark' }, 15 | leapmotion: { adaptor: 'leapmotion' } 16 | }, 17 | 18 | devices: { 19 | servo1: { driver: 'servo', pin: 0, connection: 'digispark' }, 20 | servo2: { driver: 'servo', pin: 1, connection: 'digispark' }, 21 | leapmotion: { driver: 'leapmotion', connection: 'leapmotion' } 22 | }, 23 | 24 | Now that Cylon knows about the necessary hardware we're going to be using, we'll 25 | tell it what work we want to do: 26 | 27 | work: function(my) { 28 | my.x = 90; 29 | my.z = 90; 30 | 31 | my.leapmotion.on('hand', function(hand) { 32 | my.x = hand.palmX.fromScale(-300, 300).toScale(30, 150); 33 | my.z = hand.palmZ.fromScale(-300, 300).toScale(30, 150); 34 | }); 35 | 36 | every(100, function() { 37 | my.servo1.angle(my.x); 38 | my.servo2.angle(my.z); 39 | 40 | console.log("Current Angle: " + my.servo1.currentAngle() + ", " + my.servo2.currentAngle()); 41 | }); 42 | 43 | Now that our robot knows what work to do, and the work it will be doing that 44 | hardware with, we can start it: 45 | 46 | }).start() 47 | -------------------------------------------------------------------------------- /examples/conway_sphero/conway_sphero.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | var Green = 0x0000FF, 6 | Red = 0xFF0000; 7 | 8 | var bots = { 9 | Thelma: "/dev/rfcomm0", 10 | Louise: "/dev/rfcomm1", 11 | Grace: "/dev/rfcomm2", 12 | Ada: "/dev/rfcomm3" 13 | }; 14 | 15 | Object.keys(bots).forEach(function(name) { 16 | var port = bots[name]; 17 | 18 | Cylon.robot({ 19 | name: name, 20 | 21 | connections: { 22 | sphero: { adaptor: "sphero", port: port } 23 | }, 24 | 25 | devices: { 26 | sphero: { driver: "sphero" } 27 | }, 28 | 29 | work: function(my) { 30 | my.born(); 31 | 32 | my.sphero.on("collision", function() { 33 | my.contacts += 1; 34 | }); 35 | 36 | every((3).seconds(), function() { 37 | if (my.alive) { 38 | my.move(); 39 | } 40 | }); 41 | 42 | every((10).seconds(), function() { 43 | my.birthday(); 44 | }); 45 | }, 46 | 47 | move: function() { 48 | this.sphero.roll(60, Math.floor(Math.random() * 360)); 49 | }, 50 | 51 | born: function() { 52 | this.contacts = 0; 53 | this.age = 0; 54 | this.life(); 55 | this.move(); 56 | }, 57 | 58 | life: function() { 59 | this.alive = true; 60 | this.sphero.setRGB(Green); 61 | }, 62 | 63 | death: function() { 64 | this.alive = false; 65 | this.sphero.setRGB(Red); 66 | this.sphero.stop(); 67 | }, 68 | 69 | enoughContacts: function() { 70 | return this.contacts >= 2 && this.contacts < 7; 71 | }, 72 | 73 | birthday: function() { 74 | this.age += 1; 75 | 76 | if (this.alive) { 77 | var str = "Happy birthday, "; 78 | str += this.name; 79 | str += ". You are "; 80 | str += this.age; 81 | str += " and had "; 82 | str += this.contacts; 83 | str += " contacts."; 84 | 85 | console.log(str); 86 | } 87 | 88 | if (this.enoughContacts()) { 89 | if (!this.alive) { 90 | this.born(); 91 | } 92 | } else { 93 | this.death(); 94 | } 95 | 96 | this.contacts = 0; 97 | } 98 | }); 99 | }); 100 | 101 | Cylon.start(); 102 | -------------------------------------------------------------------------------- /examples/conway_sphero/conway_sphero.markdown: -------------------------------------------------------------------------------- 1 | # Conway's Game Of Life - With Spheros 2 | 3 | For this Cylon example, we're going to run a version of [Conway's Game Of 4 | Life][gol], using Spheros as cells. 5 | 6 | [gol]: https://en.wikipedia.org/wiki/Conway's_Game_of_Life 7 | 8 | To account for the fact that we're now using Spheros to play the game, we need 9 | to make some changes to the mechanics. Here's how our version of Conway's Game 10 | will go: 11 | 12 | - "alive" Spheros glow green, "dead" spheros glow red. 13 | - At the start of the game, all Spheros are "alive". 14 | - On every tick, the "alive" Spheros roll randomly, and count the number of 15 | collisions they have. 16 | - After the tick, all the Spheros that had between two and six collisions are 17 | considered "alive", and those with less than two or more than six collisions 18 | are now "dead" 19 | - If a "dead" Sphero is bumped into by an "alive" one, or has collisions through 20 | other means, it can become "alive" again. 21 | 22 | With those alterations in hand, let's start building it with Cylon! Before you 23 | start, make sure you have the `cylon-sphero` module installed. 24 | 25 | First off, let's load up Cylon: 26 | 27 | var Cylon = require('../..'); 28 | 29 | For easier use later, let's define the colors we'll be using with the Spheros, 30 | green for alive and red for dead: 31 | 32 | var Green = 0x0000FF, 33 | Red = 0xFF0000; 34 | 35 | We'll be using four robots for this example, but they'll have very similar 36 | programming so we just need to define what's different between them for now. 37 | Each of the robots will have a unique name, and will communicate on their own 38 | port. 39 | 40 | var bots = { 41 | 'Thelma': '/dev/rfcomm0', 42 | 'Louise': '/dev/rfcomm1', 43 | 'Grace': '/dev/rfcomm2', 44 | 'Ada': '/dev/rfcomm3' 45 | }; 46 | 47 | That gets the basics out of the way. 48 | 49 | Since, as previously mentioned, our robots all have the same basic 50 | functionality, we can just loop over the `bots` object, and create robots as we 51 | go. 52 | 53 | Object.keys(bots).forEach(function(name) { 54 | var port = bots[name]; 55 | 56 | All of our robots will be connecting to a Sphero, and be operating via a single 57 | device (you guessed it, a Sphero). 58 | 59 | Cylon.robot({ 60 | name: name, 61 | 62 | connections: { 63 | sphero: { adaptor: 'sphero', port: port } 64 | }, 65 | 66 | devices: { 67 | sphero: { driver: 'sphero' } 68 | }, 69 | 70 | Now that the pieces are there, we can set up our robot's work. It starts by 71 | being "born", then moves every three seconds if it's alive, celebrates it's 72 | birthday every ten seconds, and increments it's contacts every time the Sphero 73 | detects a collision. 74 | 75 | work: function(my) { 76 | my.born(); 77 | 78 | my.sphero.on('collision', function() { 79 | my.contacts += 1; 80 | }); 81 | 82 | every((3).seconds(), function() { 83 | if (my.alive) { 84 | my.move(); 85 | } 86 | }); 87 | 88 | every((10).seconds(), function() { 89 | my.birthday(); 90 | }); 91 | }, 92 | 93 | When a robot is asked to move, it rolls in a random direction at speed 60. 94 | 95 | move: function() { 96 | this.sphero.roll(60, Math.floor(Math.random() * 360)); 97 | }, 98 | 99 | When the robots are first started, they are born. This sets their contacts to 100 | zero, their age to zero, makes them "alive", and starts them moving for the 101 | first tick. 102 | 103 | born: function() { 104 | this.contacts = 0; 105 | this.age = 0; 106 | this.life(); 107 | this.move(); 108 | }, 109 | 110 | In the case of our robots, "life" just means the robot's internal "alive" state 111 | is set to `true`, and the Sphero's LED is set to green. 112 | 113 | life: function() { 114 | this.alive = true; 115 | this.sphero.setRGB(Green); 116 | }, 117 | 118 | Similarly, "death" just sets the "alive" state to false, the Sphero's color to 119 | red, and stops the Sphero from moving. 120 | 121 | death: function() { 122 | this.alive = false; 123 | this.sphero.setRGB(Red); 124 | this.sphero.stop(); 125 | }, 126 | 127 | A robot is decided to have enough contacts if it has between two and six 128 | contacts. 129 | 130 | enoughContacts: function() { 131 | return this.contacts >= 2 && this.contacts < 7; 132 | }, 133 | 134 | On a robot's birthday, it increments it's age, prints it's name, age, and 135 | contacts to the console, and then determines if it's now alive or dead based on 136 | the number of contacts it had in the last tick. 137 | 138 | birthday: function() { 139 | this.age += 1; 140 | 141 | if (this.alive) { 142 | console.log("Happy birthday, " + this.name + ". You are " + this.age + " and had " + this.contacts + " contacts."); 143 | } 144 | 145 | if (this.enoughContacts()) { 146 | if (!this.alive) { 147 | this.born(); 148 | } 149 | } else { 150 | this.death(); 151 | } 152 | 153 | this.contacts = 0; 154 | } 155 | }); 156 | }); 157 | 158 | Now that Cylon knows about our robots and what they do, we can get started! 159 | 160 | Cylon.start(); 161 | -------------------------------------------------------------------------------- /examples/conway_sphero/fluent-conway_sphero.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | var Green = 0x0000FF, 6 | Red = 0xFF0000; 7 | 8 | var bots = { 9 | Thelma: "/dev/rfcomm0", 10 | Louise: "/dev/rfcomm1", 11 | Grace: "/dev/rfcomm2", 12 | Ada: "/dev/rfcomm3" 13 | }; 14 | 15 | Object.keys(bots).forEach(function(name) { 16 | var port = bots[name]; 17 | 18 | var robot = Cylon.robot({ name: name }); 19 | 20 | robot.connection("sphero", { adaptor: "sphero", port: port }); 21 | robot.device("sphero", { driver: "sphero" }); 22 | 23 | robot.move = function() { 24 | robot.sphero.roll(60, Math.floor(Math.random() * 360)); 25 | }; 26 | 27 | robot.born = function() { 28 | robot.contacts = 0; 29 | robot.age = 0; 30 | robot.life(); 31 | robot.move(); 32 | }; 33 | 34 | robot.life = function() { 35 | robot.alive = true; 36 | robot.sphero.setRGB(Green); 37 | }; 38 | 39 | robot.death = function() { 40 | robot.alive = false; 41 | robot.sphero.setRGB(Red); 42 | robot.sphero.stop(); 43 | }; 44 | 45 | robot.enoughContacts = function() { 46 | return robot.contacts >= 2 && robot.contacts < 7; 47 | }; 48 | 49 | robot.birthday = function() { 50 | robot.age += 1; 51 | 52 | if (robot.alive) { 53 | var str = "Happy birthday, "; 54 | str += robot.name; 55 | str += ". You are "; 56 | str += robot.age; 57 | str += " and had "; 58 | str += robot.contacts; 59 | str += " contacts."; 60 | 61 | console.log(str); 62 | } 63 | 64 | if (robot.enoughContacts()) { 65 | if (!robot.alive) { 66 | robot.born(); 67 | } 68 | } else { 69 | robot.death(); 70 | } 71 | 72 | robot.contacts = 0; 73 | }; 74 | 75 | robot.on("ready", function() { 76 | robot.born(); 77 | 78 | robot.sphero.on("collision", function() { 79 | robot.contacts += 1; 80 | }); 81 | 82 | every((3).seconds(), function() { 83 | if (robot.alive) { 84 | robot.move(); 85 | } 86 | }); 87 | 88 | every((10).seconds(), function() { 89 | robot.birthday(); 90 | }); 91 | }); 92 | }); 93 | 94 | Cylon.start(); 95 | -------------------------------------------------------------------------------- /examples/crazyflie/crazyflie.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | Cylon.robot({ 6 | connections: { 7 | crazyflie: { adaptor: "crazyflie", port: "radio://1/10/250KPS" } 8 | }, 9 | 10 | devices: { 11 | drone: { driver: "crazyflie" } 12 | }, 13 | 14 | work: function(my) { 15 | my.drone.on("start", function() { 16 | my.drone.takeoff(); 17 | after((10).seconds(), my.drone.land); 18 | after((15).seconds(), my.drone.stop); 19 | }); 20 | } 21 | }).start(); 22 | -------------------------------------------------------------------------------- /examples/crazyflie/crazyflie.markdown: -------------------------------------------------------------------------------- 1 | # Crazyflie 2 | 3 | For this Cylon demo, we're going to connect to a Crazyflie, and then make it 4 | take off and land. Before we get started, make sure you've got the 5 | `cylon-crazyflie` adaptor installed. 6 | 7 | To start us off, let's import Cylon: 8 | 9 | var Cylon = require('../..'); 10 | 11 | Now that we've got that set up, we can start defining our robot: 12 | 13 | Cylon.robot({ 14 | 15 | We'll be using one connection and one device for this robot, both using the 16 | crazyflie adaptor. We'll be connecting to the Crazyflie using the CrazyRadio. 17 | 18 | connections: { 19 | crazyflie: { adaptor: 'crazyflie', port: "radio://1/10/250KPS" } 20 | }, 21 | 22 | devices: { 23 | drone: { driver: 'crazyflie' } 24 | }, 25 | 26 | With the parts in place, we can start defining our robot's work. 27 | 28 | work: function(my) { 29 | 30 | When our drone is ready, we'll make it takeoff. After ten seconds, we'll tell it 31 | to land, and five seconds after that to stop. 32 | 33 | my.drone.on('start', function() { 34 | my.drone.takeoff() 35 | after((10).seconds(), my.drone.land); 36 | after((15).seconds(), my.drone.stop); 37 | }); 38 | } 39 | 40 | With all that done, we can start up the robot and get the Crazyflie flying: 41 | 42 | }).start(); 43 | -------------------------------------------------------------------------------- /examples/digispark_blink/blink.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | Cylon.robot({ 6 | connections: { 7 | digispark: { adaptor: "digispark" } 8 | }, 9 | 10 | devices: { 11 | led: { driver: "led", pin: 1 } 12 | }, 13 | 14 | work: function(my) { 15 | every((1).second(), my.led.toggle); 16 | } 17 | }).start(); 18 | -------------------------------------------------------------------------------- /examples/digispark_blink/blink.markdown: -------------------------------------------------------------------------------- 1 | # Digispark Blink 2 | 3 | First, let's import Cylon: 4 | 5 | var Cylon = require('../..'); 6 | 7 | Now that we have Cylon imported, we can start defining our robot 8 | 9 | Cylon.robot({ 10 | 11 | Let's define the connections and devices: 12 | 13 | connections: { 14 | digispark: { adaptor: 'digispark' } 15 | }, 16 | 17 | devices: { 18 | led: { driver: 'led', pin: 1 } 19 | }, 20 | 21 | Now that Cylon knows about the necessary hardware we're going to be using, we'll 22 | tell it what work we want to do: 23 | 24 | work: function(my) { 25 | every((1).second(), my.led.toggle); 26 | } 27 | 28 | Now that our robot knows what work to do, and the work it will be doing that 29 | hardware with, we can start it: 30 | 31 | }).start(); 32 | -------------------------------------------------------------------------------- /examples/halt/halt.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | Cylon.robot({ 6 | connections: { 7 | loopback: { adaptor: "loopback" } 8 | }, 9 | 10 | devices: { 11 | ping: { driver: "ping" } 12 | }, 13 | 14 | work: function() { 15 | after((1).second(), function() { 16 | console.log("Hello human!"); 17 | console.log("I'm going to automatically stop in a few seconds."); 18 | }); 19 | 20 | after((5).seconds(), function() { 21 | console.log("I'm shutting down now."); 22 | Cylon.halt(); 23 | }); 24 | } 25 | }); 26 | 27 | Cylon.start(); 28 | -------------------------------------------------------------------------------- /examples/hello/fluent-hello.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | // ensure you install the API plugin first: 6 | // $ npm install cylon-api-http 7 | Cylon.api(); 8 | 9 | Cylon 10 | .robot({ name: "test" }) 11 | .connection("loopback", { adaptor: "loopback" }) 12 | .device("ping", { driver: "ping" }) 13 | .on("ready", function(bot) { 14 | setInterval(function() { 15 | console.log("Hello, human!"); 16 | console.log(bot.ping.ping()); 17 | }, 1000); 18 | 19 | setTimeout(function() { 20 | console.log("I've been at your command for 5 seconds now."); 21 | }, 5000); 22 | }); 23 | 24 | Cylon.start(); 25 | -------------------------------------------------------------------------------- /examples/hello/hello.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | // ensure you install the API plugin first: 6 | // $ npm install cylon-api-http 7 | Cylon.api(); 8 | 9 | Cylon.robot({ 10 | name: "test", 11 | 12 | connections: { 13 | loopback: { adaptor: "loopback" } 14 | }, 15 | 16 | devices: { 17 | ping: { driver: "ping" } 18 | }, 19 | 20 | work: function(my) { 21 | every((1).seconds(), function() { 22 | console.log("Hello, human!"); 23 | console.log(my.ping.ping()); 24 | }); 25 | 26 | after((5).seconds(), function() { 27 | console.log("I've been at your command for 5 seconds now."); 28 | }); 29 | } 30 | }); 31 | 32 | Cylon.start(); 33 | -------------------------------------------------------------------------------- /examples/hello/hello.markdown: -------------------------------------------------------------------------------- 1 | # Hello 2 | 3 | For this exceedingly simple example, we're going to define a robot that has no 4 | devices, no connections, and just demonstrates the tools for performing work on 5 | an interval, and after a timeout. 6 | 7 | Let's start by importing Cylon: 8 | 9 | var Cylon = require('../..'); 10 | 11 | Now we can define our robot: 12 | 13 | Cylon.robot({ 14 | name: 'test', 15 | 16 | connections: { 17 | loopback: { adaptor: 'loopback' } 18 | }, 19 | 20 | devices: { 21 | ping: { driver: 'ping' } 22 | }, 23 | 24 | For work, it's going to print a message to the console every second, and another 25 | message after five seconds have elapsed. 26 | 27 | work: function(my) { 28 | every((1).seconds(), function(){ 29 | console.log("Hello, human!") 30 | console.log(my.ping.ping()); 31 | }); 32 | 33 | after((5).seconds(), function(){ 34 | console.log("I've been at your command for 5 seconds now.") 35 | }); 36 | } 37 | 38 | Simple as can be. Now that we're done, let's start the robot: 39 | 40 | }).start(); 41 | -------------------------------------------------------------------------------- /examples/keyboard/keyboard.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | Cylon.robot({ 6 | connections: { 7 | keyboard: { adaptor: "keyboard" } 8 | }, 9 | 10 | devices: { 11 | keyboard: { driver: "keyboard" } 12 | }, 13 | 14 | work: function(my) { 15 | my.keyboard.on("a", function() { 16 | console.log("a pressed!"); 17 | }); 18 | } 19 | }).start(); 20 | -------------------------------------------------------------------------------- /examples/keyboard/keyboard.markdown: -------------------------------------------------------------------------------- 1 | # Keyboard 2 | 3 | For this Cylon example, we're going to quickly demonstrate getting keyboard 4 | input. 5 | 6 | First, let's import Cylon: 7 | 8 | var Cylon = require('../..'); 9 | 10 | With that done, let's define our robot: 11 | 12 | Cylon.robot({ 13 | 14 | It will have a single connection and device, both to the keyboard. 15 | 16 | connections: { 17 | keyboard: { adaptor: 'keyboard' } 18 | }, 19 | 20 | devices: { 21 | keyboard: { driver: 'keyboard' } 22 | }, 23 | 24 | When we tell this robot to work, it's going to listen to the 'a' key on the 25 | keyboard and let us know when it's been pressed. 26 | 27 | work: function(my) { 28 | my.keyboard.on('a', function(key) { 29 | console.log("a pressed!") 30 | }); 31 | } 32 | 33 | With that done, let's get started! 34 | 35 | }).start(); 36 | -------------------------------------------------------------------------------- /examples/leap_ardrone/leap_ardrone.js: -------------------------------------------------------------------------------- 1 | /* 2 | * leap_ardrone.js 3 | * 4 | * Written by Giuliano Sposito and Fábio Uechi 5 | * Copyright (c) 2013-2014 CI&T Software 6 | * Licensed under the Apache 2.0 license. 7 | */ 8 | 9 | "use strict"; 10 | 11 | var Cylon = require("cylon"); 12 | 13 | var TURN_TRESHOLD = 0.2, 14 | TURN_SPEED_FACTOR = 2.0; 15 | 16 | var DIRECTION_THRESHOLD = 0.25, 17 | DIRECTION_SPEED_FACTOR = 0.05; 18 | 19 | var UP_CONTROL_THRESHOLD = 50, 20 | UP_SPEED_FACTOR = 0.01, 21 | CIRCLE_THRESHOLD = 1.5; 22 | 23 | var handStartPosition = [], 24 | handStartDirection = []; 25 | 26 | var handWasClosedInLastFrame = false; 27 | 28 | Cylon.robot({ 29 | connections: { 30 | leapmotion: { adaptor: "leapmotion" }, 31 | ardrone: { adaptor: "ardrone", port: "192.168.1.1" }, 32 | keyboard: { adaptor: "keyboard" } 33 | }, 34 | 35 | devices: { 36 | drone: { driver: "ardrone", connection: "ardrone" }, 37 | leapmotion: { driver: "leapmotion", connection: "leapmotion" }, 38 | keyboard: { driver: "keyboard", connection: "keyboard" } 39 | }, 40 | 41 | work: function(my) { 42 | my.keyboard.on("right", my.drone.rightFlip); 43 | my.keyboard.on("left", my.drone.leftFlip); 44 | my.keyboard.on("up", my.drone.frontFlip); 45 | my.keyboard.on("down", my.drone.backFlip); 46 | 47 | my.keyboard.on("w", my.drone.wave); 48 | my.keyboard.on("s", my.drone.stop); 49 | my.keyboard.on("l", my.drone.land); 50 | 51 | my.leapmotion.on("gesture", function(gesture) { 52 | var type = gesture.type, 53 | state = gesture.state, 54 | progress = gesture.progress; 55 | 56 | var stop = (state === "stop"); 57 | 58 | if (type === "circle" && stop && progress > CIRCLE_THRESHOLD) { 59 | if (gesture.normal[2] < 0) { 60 | my.drone.takeoff(); 61 | } 62 | 63 | if (gesture.normal[2] > 0) { 64 | my.drone.land(); 65 | } 66 | } 67 | 68 | // emergency stop 69 | if (type === "keyTap" || type === "screenTap") { 70 | my.drone.stop(); 71 | } 72 | }); 73 | 74 | my.leapmotion.on("hand", function(hand) { 75 | var signal, value; 76 | 77 | var handOpen = !!hand.fingers.filter(function(f) { 78 | return f.extended; 79 | }).length; 80 | 81 | if (handOpen) { 82 | if (handWasClosedInLastFrame) { 83 | handStartPosition = hand.palmPosition; 84 | handStartDirection = hand.direction; 85 | } 86 | 87 | var horizontal = Math.abs(handStartDirection[0] - hand.direction[0]), 88 | vertical = Math.abs(hand.palmPosition[1] - handStartPosition[1]); 89 | 90 | // TURNS 91 | if (horizontal > TURN_TRESHOLD) { 92 | signal = handStartDirection[0] - hand.direction[0]; 93 | value = (horizontal - TURN_TRESHOLD) * TURN_SPEED_FACTOR; 94 | 95 | if (signal > 0) { 96 | my.drone.counterClockwise(value); 97 | } 98 | 99 | if (signal < 0) { 100 | my.drone.clockwise(value); 101 | } 102 | } 103 | 104 | // UP and DOWN 105 | if (vertical > UP_CONTROL_THRESHOLD) { 106 | if ((hand.palmPosition[1] - handStartPosition[1]) >= 0) { 107 | signal = 1; 108 | } else { 109 | signal = -1; 110 | } 111 | 112 | value = Math.round(vertical - UP_CONTROL_THRESHOLD) * UP_SPEED_FACTOR; 113 | 114 | if (signal > 0) { 115 | my.drone.up(value); 116 | } 117 | 118 | if (signal < 0) { 119 | my.drone.down(value); 120 | } 121 | } 122 | 123 | // DIRECTION FRONT/BACK 124 | if ((Math.abs(hand.palmNormal[2]) > DIRECTION_THRESHOLD)) { 125 | if (hand.palmNormal[2] > 0) { 126 | value = Math.abs( 127 | Math.round(hand.palmNormal[2] * 10 + DIRECTION_THRESHOLD) * 128 | DIRECTION_SPEED_FACTOR 129 | ); 130 | 131 | my.drone.forward(value); 132 | } 133 | 134 | if (hand.palmNormal[2] < 0) { 135 | value = Math.abs( 136 | Math.round(hand.palmNormal[2] * 10 - DIRECTION_THRESHOLD) * 137 | DIRECTION_SPEED_FACTOR 138 | ); 139 | 140 | my.drone.back(value); 141 | } 142 | } 143 | 144 | // DIRECTION LEFT/RIGHT 145 | if (Math.abs(hand.palmNormal[0]) > DIRECTION_THRESHOLD) { 146 | if (hand.palmNormal[0] > 0) { 147 | value = Math.abs( 148 | Math.round(hand.palmNormal[0] * 10 + DIRECTION_THRESHOLD) * 149 | DIRECTION_SPEED_FACTOR 150 | ); 151 | 152 | my.drone.left(value); 153 | } 154 | 155 | if (hand.palmNormal[0] < 0) { 156 | value = Math.abs( 157 | Math.round(hand.palmNormal[0] * 10 - DIRECTION_THRESHOLD) * 158 | DIRECTION_SPEED_FACTOR 159 | ); 160 | 161 | my.drone.right(value); 162 | } 163 | } 164 | 165 | // AUTO FREEZE 166 | if ( 167 | // within left/right threshold 168 | (Math.abs(hand.palmNormal[0]) < DIRECTION_THRESHOLD) && 169 | 170 | // within forward/back threshold 171 | (Math.abs(hand.palmNormal[2]) < DIRECTION_THRESHOLD) && 172 | 173 | // within up/down threshold 174 | Math.abs(hand.palmPosition[1] - handStartPosition[1]) < 175 | UP_CONTROL_THRESHOLD && 176 | 177 | // within turn threshold 178 | Math.abs(handStartDirection[0] - hand.direction[0]) < 179 | TURN_TRESHOLD) { 180 | my.drone.stop(); 181 | } 182 | } 183 | 184 | if (!handOpen && !handWasClosedInLastFrame) { 185 | my.drone.stop(); 186 | } 187 | 188 | handWasClosedInLastFrame = !handOpen; 189 | }); 190 | } 191 | }).start(); 192 | -------------------------------------------------------------------------------- /examples/leap_ardrone/leap_ardrone.markdown: -------------------------------------------------------------------------------- 1 | # Leapmotion Ardrone 2.0 2 | 3 | This example illustrates how to use Leap Motion and the keyboard to control an ARDrone. We basically use one hand to drive the drone (takeoff, land, up, down, etc) and the arrow keys to perform some cool stunts (flips). 4 | 5 | First, let's import Cylon: 6 | 7 | var Cylon = require('../..'); 8 | 9 | Now that we have Cylon imported, we can start defining our robot 10 | 11 | Cylon.robot({ 12 | 13 | Let's define the connections and devices: 14 | 15 | connections: { 16 | leapmotion: { adaptor: 'leapmotion' }, 17 | ardrone: { adaptor: 'ardrone', port: '192.168.1.1' }, 18 | keyboard: { adaptor: 'keyboard' } 19 | }, 20 | 21 | devices: { 22 | drone: { driver: 'ardrone', connection:'ardrone' }, 23 | leapmotion: { driver: 'leapmotion', connection:'leapmotion' }, 24 | keyboard: { driver: 'keyboard', connection:'keyboard' } 25 | }, 26 | 27 | Now that Cylon knows about the necessary hardware we're going to be using, we'll 28 | tell it what work we want to do: 29 | 30 | work: function(my) { 31 | 32 | Lets use the circle gesture to take off and land : two rounds clockwise will trigger the takeoff() and counter clockwise will tell the drone to land: 33 | 34 | 35 | my.leapmotion.on('gesture', function(gesture) { 36 | if (gesture.type=='circle' && gesture.state=='stop' && gesture.progress > CIRCLE_THRESHOLD ){ 37 | if (gesture.normal[2] < 0) { 38 | my.drone.takeoff(); 39 | }; 40 | 41 | if (gesture.normal[2] > 0) { 42 | my.drone.land(); 43 | } 44 | } 45 | }); 46 | 47 | 48 | Whenever we get a 'hand' gesture event from Leap Motion we need to tell ARDrone what to do. 49 | 50 | 51 | my.leapmotion.on('hand', function(hand) { 52 | 53 | 54 | In case we turn our hand to the right or left we want the drone to rotate clockwise or counter clockwise respectively: 55 | 56 | 57 | if(hand.s>1.5 && Math.abs(handStartDirection[0]-hand.direction[0]) > TURN_TRESHOLD ) { 58 | var signal = handStartDirection[0]-hand.direction[0]; 59 | var value = (Math.abs(handStartDirection[0]-hand.direction[0])-TURN_TRESHOLD) * TURN_SPEED_FACTOR; 60 | if (signal>0){ 61 | my.drone.counterClockwise(value); 62 | } 63 | 64 | if (signal<0){ 65 | my.drone.clockwise(value); 66 | } 67 | } 68 | 69 | In case we raise our hand up or lower it down we tell the drone to go up or down respectively: 70 | 71 | if (hand.s>1.5 && Math.abs(hand.palmPosition[1]-handStartPosition[1]) > UP_CONTROL_THRESHOLD) { 72 | var signal = (hand.palmPosition[1]-handStartPosition[1]) >= 0 ? 1 : -1; 73 | var value = Math.round(Math.abs((hand.palmPosition[1]-handStartPosition[1]))-UP_CONTROL_THRESHOLD) * UP_SPEED_FACTOR; 74 | 75 | if (signal>0) { 76 | my.drone.up(value); 77 | }; 78 | 79 | if (signal<0) { 80 | my.drone.down(value); 81 | } 82 | } 83 | 84 | 85 | In order to move the drone forward, backward, left or right we need to detect the hand inclination. Imagine your hand is the drone and lean it towards the direction we want it to move. 86 | 87 | 88 | if (hand.s>1.5 && (Math.abs(hand.palmNormal[2])>DIRECTION_THRESHOLD)) { 89 | if (hand.palmNormal[2]>0) { 90 | var value = Math.abs(Math.round( hand.palmNormal[2] * 10 + DIRECTION_THRESHOLD ) * DIRECTION_SPEED_FACTOR); 91 | my.drone.forward( value ); 92 | }; 93 | 94 | if (hand.palmNormal[2]<0) { 95 | var value = Math.abs(Math.round( hand.palmNormal[2] * 10 - DIRECTION_THRESHOLD ) * DIRECTION_SPEED_FACTOR); 96 | my.drone.back( value ); 97 | }; 98 | } 99 | 100 | if (hand.s>1.5 && (Math.abs(hand.palmNormal[0])>DIRECTION_THRESHOLD)) { 101 | if (hand.palmNormal[0]>0) { 102 | var value = Math.abs(Math.round( hand.palmNormal[0] * 10 + DIRECTION_THRESHOLD ) * DIRECTION_SPEED_FACTOR); 103 | my.drone.left( value ); 104 | }; 105 | 106 | if (hand.palmNormal[0]<0) { 107 | var value = Math.abs(Math.round( hand.palmNormal[0] * 10 - DIRECTION_THRESHOLD ) * DIRECTION_SPEED_FACTOR); 108 | my.drone.right( value ); 109 | }; 110 | } 111 | 112 | 113 | Whenever we close our hand we tell the drone no stop: 114 | 115 | if (hand.s<=1.5 && lastS > 1.5) { // closed hand 116 | my.drone.stop(); 117 | } 118 | 119 | 120 | And finally lets use the arrow keys to tell the drone to do some cool stunts: 121 | 122 | 123 | my.keyboard.on('right', function(key) { 124 | my.drone.rightFlip(); 125 | }); 126 | 127 | my.keyboard.on('left', function(key) { 128 | my.drone.leftFlip(); 129 | }); 130 | 131 | my.keyboard.on('up', function(key) { 132 | my.drone.frontFlip(); 133 | }); 134 | 135 | my.keyboard.on('down', function(key) { 136 | my.drone.backFlip(); 137 | }); 138 | 139 | 140 | Now that our robot knows what work to do, and the work it will be doing that 141 | hardware with, we can start it: 142 | 143 | }).start(); 144 | -------------------------------------------------------------------------------- /examples/leap_arduino/leap_arduino.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | Cylon.robot({ 6 | connections: { 7 | leap: { adaptor: "leapmotion" }, 8 | arduino: { adaptor: "firmata", port: "/dev/ttyACM0" } 9 | }, 10 | 11 | devices: { 12 | led: { driver: "led", pin: 13, connection: "arduino" } 13 | }, 14 | 15 | work: function(my) { 16 | my.leapmotion.on("frame", function(frame) { 17 | if (frame.hands.length > 0) { 18 | my.led.turnOn(); 19 | } else { 20 | my.led.turnOff(); 21 | } 22 | }); 23 | } 24 | }).start(); 25 | -------------------------------------------------------------------------------- /examples/leap_arduino/leap_arduino.markdown: -------------------------------------------------------------------------------- 1 | # Leapmotion Arduino 2 | 3 | First, let's import Cylon: 4 | 5 | var Cylon = require('../..'); 6 | 7 | Now that we have Cylon imported, we can start defining our robot 8 | 9 | Cylon.robot({ 10 | 11 | Let's define the connections and devices: 12 | 13 | connections: { 14 | leap: { adaptor: 'leapmotion' }, 15 | arduino: { adaptor: 'firmata', port: '/dev/ttyACM0' } 16 | }, 17 | 18 | devices: { 19 | led: { driver: 'led', pin: 13, connection: 'arduino' } 20 | }, 21 | 22 | Now that Cylon knows about the necessary hardware we're going to be using, we'll 23 | tell it what work we want to do: 24 | 25 | work: function(my) { 26 | my.leap.on('frame', function(frame) { 27 | if (frame.hands.length > 0) { 28 | my.led.turnOn(); 29 | } else { 30 | my.led.turnOff(); 31 | } 32 | }); 33 | } 34 | 35 | Now that our robot knows what work to do, and the work it will be doing that 36 | hardware with, we can start it: 37 | 38 | }).start(); 39 | -------------------------------------------------------------------------------- /examples/master/master.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | var bots = [ "Huey", "Dewey", "Louie" ]; 6 | 7 | bots.forEach(function(name) { 8 | Cylon.robot({ 9 | name: name, 10 | 11 | work: function(my) { 12 | console.log("Robot " + my.name + " is now working!"); 13 | } 14 | }); 15 | }); 16 | 17 | Cylon.start(); 18 | -------------------------------------------------------------------------------- /examples/master/master.markdown: -------------------------------------------------------------------------------- 1 | # Master 2 | 3 | For this example, we're going to provide a simple demonstration of how Cylon's 4 | master functionality works. Cylon can be fed an arbitrary number of robots, and 5 | it will then take care of all of them, starting and stopping all of them as 6 | needed. 7 | 8 | First, let's load up Cylon: 9 | 10 | var Cylon = require('../..'); 11 | 12 | With that in place, now we can start defining our robots. They'll all behave 13 | similarly, but have unique characteristics (their name), so let's 14 | define an array to hold these names for now. Later, we can instantiate 15 | a base robot and change it's attributes as needed. 16 | 17 | var bots = [ 'Huey', 'Dewey', 'Louie' ]; 18 | 19 | Now we can define our `MinionBot` class. This will be the base 20 | class for all three of the robots. 21 | 22 | var MinionBot = (function() { 23 | function MinionBot() {}; 24 | 25 | We'll just give our robots some basic work so we can tell they're actually 26 | working: 27 | 28 | MinionBot.prototype.work = function(my) { 29 | console.log("Robot " + my.name + " is now working!"); 30 | }; 31 | 32 | return MinionBot; 33 | 34 | })(); 35 | 36 | And that's all we need for that. 37 | 38 | Next up, we'll create Cylon robots by making an instance of the `MinionBot` 39 | class, and modifying the attributes that are unique to each robot. After the 40 | customized robot is ready, we'll feed it into Cylon. 41 | 42 | for (var i = 0; i < bots.length; i++) { 43 | var robot = new MinionBot; 44 | robot.name = bots[i]; 45 | Cylon.robot(robot); 46 | } 47 | 48 | And now Cylon knows about all the robots we care about for this example, and 49 | what they do. All that's left is to start them all. The `.start()` method on 50 | Cylon triggers the `.start()` command on all the robots we've told Cylon about, 51 | so all three robots will start at once. 52 | 53 | Cylon.start(); 54 | -------------------------------------------------------------------------------- /examples/rapiro/rapiro_servo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | Cylon.robot({ 6 | connections: { 7 | arduino: { adaptor: "firmata", port: "/dev/ttyUSB0" } 8 | }, 9 | 10 | devices: { 11 | led: { driver: "led", pin: 17 }, 12 | servo: { driver: "servo", pin: 2, range: { min: 30, max: 150 } } 13 | }, 14 | 15 | work: function(my) { 16 | my.led.turnOn(); 17 | 18 | var angle = 30; 19 | var increment = 40; 20 | 21 | every((1).seconds(), function() { 22 | angle += increment; 23 | my.servo.angle(angle); 24 | 25 | console.log("Current Angle: " + (my.servo.currentAngle())); 26 | 27 | if ((angle === 30) || (angle === 150)) { increment = -increment; } 28 | }); 29 | } 30 | }).start(); 31 | -------------------------------------------------------------------------------- /examples/robot_commands/robot_commands.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | // ensure you install the API plugin first: 6 | // $ npm install cylon-api-http 7 | Cylon.api(); 8 | 9 | Cylon.robot({ 10 | name: "Frankie", 11 | 12 | sayRelax: function() { 13 | return this.name + " says relax"; 14 | }, 15 | 16 | work: function(my) { 17 | every((5).seconds(), function() { 18 | console.log(my.sayRelax()); 19 | }); 20 | }, 21 | 22 | commands: function() { 23 | return { 24 | say_relax: this.sayRelax 25 | }; 26 | } 27 | }); 28 | 29 | Cylon.start(); 30 | -------------------------------------------------------------------------------- /examples/robot_commands/robot_commands.markdown: -------------------------------------------------------------------------------- 1 | # Robot Commands 2 | 3 | This example demonstrates a feature of the Cylon API: running arbitrary commands 4 | on robots over HTTP. To demonstrate this, we're going to make a basic robot, 5 | with a custom command. This in and of itself is nothing to write home about, but 6 | you'll be able to trigger the custom command by visiting this URL in your 7 | browser: 8 | 9 | ``` 10 | http://localhost:8080/robots/frankie/commands/relax 11 | ``` 12 | 13 | First, let's make sure to load up Cylon: 14 | 15 | var Cylon = require('../..'); 16 | 17 | Now that we've got that, let's set up the api: 18 | 19 | // ensure you install the API plugin first: 20 | // $ npm install cylon-api-http 21 | Cylon.api(); 22 | 23 | And with that done let's define our robot: 24 | 25 | Cylon.robot({ 26 | name: 'Frankie', 27 | 28 | The result of this method will be returned to the HTTP client as part of a JSON 29 | object. 30 | 31 | sayRelax: function() { 32 | return this.name + " says relax"); 33 | }, 34 | 35 | Since we don't really care what actual work this robot does, but need to keep it 36 | busy, we'll just tell it to print it's name every five seconds. 37 | 38 | work: function(my) { 39 | every((5).seconds(), function() { 40 | console.log(my.sayRelax()); 41 | }); 42 | }, 43 | 44 | We'll then set up the `commands` object, which tells the API which commands the 45 | Robot has should be publically accessible: 46 | 47 | commands: function() { 48 | return { 49 | say_relax: this.sayRelax 50 | }; 51 | } 52 | }); 53 | 54 | And now that all the pieces are in place, we can start up Cylon: 55 | 56 | Cylon.start(); 57 | 58 | Now the robot will print it's name to the console, and Cylon will serve an API 59 | to `localhost:8080`. Check it out!. 60 | -------------------------------------------------------------------------------- /examples/salesforce/salesforce.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | Cylon.robot({ 6 | connections: { 7 | sfcon: { 8 | adaptor: "force", 9 | sfuser: process.env.SF_USERNAME, 10 | sfpass: process.env.SF_SECURITY_TOKEN, 11 | orgCreds: { 12 | clientId: process.env.SF_CLIENT_ID, 13 | clientSecret: process.env.SF_CLIENT_SECRET, 14 | redirectUri: "http://localhost:3000/oauth/_callback" 15 | } 16 | } 17 | }, 18 | 19 | devices: { 20 | salesforce: { driver: "force" } 21 | }, 22 | 23 | work: function(my) { 24 | my.salesforce.on("start", function() { 25 | my.salesforce.subscribe("/topic/SpheroMsgOutbound", function(data) { 26 | var msg = "Sphero: " + data.sobject.Sphero_Name__c + ","; 27 | msg += "Bucks: " + data.sobject.Bucks__c + ","; 28 | msg += "SM_Id: " + data.sobject.Id; 29 | 30 | console.log(msg); 31 | }); 32 | }); 33 | 34 | var i = 0; 35 | 36 | every((2).seconds(), function() { 37 | var data = JSON.stringify({ 38 | spheroName: "" + my.name, 39 | bucks: "" + i 40 | }); 41 | 42 | my.salesforce.push("SpheroController", "POST", data); 43 | }); 44 | } 45 | }).start(); 46 | -------------------------------------------------------------------------------- /examples/salesforce/salesforce.markdown: -------------------------------------------------------------------------------- 1 | # Sales Force 2 | 3 | First, let's import Cylon: 4 | 5 | var Cylon = require('../..'); 6 | 7 | Now that we have Cylon imported, we can start defining our robot 8 | 9 | Cylon.robot({ 10 | 11 | Let's define the connections and devices: 12 | 13 | connections: { 14 | sfcon: { 15 | adaptor: 'force', 16 | sfuser: process.env.SF_USERNAME, 17 | sfpass: process.env.SF_SECURITY_TOKEN, 18 | orgCreds: { 19 | clientId: process.env.SF_CLIENT_ID, 20 | clientSecret: process.env.SF_CLIENT_SECRET, 21 | redirectUri: 'http://localhost:3000/oauth/_callback' 22 | } 23 | } 24 | }, 25 | 26 | devices: { 27 | salesforce: { driver: 'force' } 28 | }, 29 | 30 | Now that Cylon knows about the necessary hardware we're going to be using, we'll 31 | tell it what work we want to do: 32 | 33 | work: function(my) { 34 | my.salesforce.on('start', function() { 35 | my.salesforce.subscribe('/topic/SpheroMsgOutbound', function(data) { 36 | var msg = "Sphero: " + data.sobject.Sphero_Name__c + ","; 37 | msg += "Bucks: " + data.sobject.Bucks__c + ","; 38 | msg += "SM_Id: " + data.sobject.Id; 39 | 40 | console.log(msg); 41 | }); 42 | }); 43 | 44 | var i = 0; 45 | 46 | every((2).seconds(), function() { 47 | var data = JSON.stringify({ 48 | spheroName: "" + my.name, 49 | bucks: "" + i 50 | }); 51 | 52 | my.salesforce.push('SpheroController', 'POST', data); 53 | }); 54 | } 55 | 56 | Now that our robot knows what work to do, and the work it will be doing that 57 | hardware with, we can start it: 58 | 59 | }).start(); 60 | -------------------------------------------------------------------------------- /examples/sf-sphero/sf-sphero.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | Cylon.robot({ 6 | name: "salesforce", 7 | 8 | connections: { 9 | sfcon: { 10 | adaptor: "force", 11 | sfuser: process.env.SF_USERNAME, 12 | sfpass: process.env.SF_SECURITY_TOKEN, 13 | orgCreds: { 14 | clientId: process.env.SF_CLIENT_ID, 15 | clientSecret: process.env.SF_CLIENT_SECRET, 16 | redirectUri: "http://localhost:3000/oauth/_callback" 17 | } 18 | } 19 | }, 20 | 21 | devices: { 22 | salesforce: { driver: "force" } 23 | }, 24 | 25 | work: function(my) { 26 | my.salesforce.on("start", function() { 27 | my.salesforce.subscribe("/topic/SpheroMsgOutbound", function(data) { 28 | var msg = "Sphero: " + data.sobject.Sphero_Name__c + ","; 29 | 30 | msg += "Bucks: " + data.sobject.Bucks__c + ","; 31 | msg += "SM_Id: " + data.sobject.Id; 32 | 33 | console.log(msg); 34 | 35 | var sphero = Cylon.robots[data.sobject.Sphero_Name__c]; 36 | sphero.react(); 37 | }); 38 | }); 39 | } 40 | }); 41 | 42 | Cylon.robot({ 43 | name: "ROY", 44 | 45 | connections: { 46 | sphero: { adaptor: "sphero" } 47 | }, 48 | 49 | devices: { 50 | sphero: { driver: "sphero" } 51 | }, 52 | 53 | react: function() { 54 | this.sphero.setRGB(0x00FF00); 55 | this.sphero.roll(90, Math.floor(Math.random() * 360)); 56 | }, 57 | 58 | work: function(my) { 59 | console.log("Setting up collision detection."); 60 | my.sphero.detectCollisions(); 61 | 62 | my.sphero.stop(); 63 | my.sphero.setRGB(0x00FF00); 64 | 65 | my.sphero.roll(90, Math.floor(Math.random() * 360)); 66 | 67 | my.sphero.on("collision", function() { 68 | my.sphero.setRGB(0x0000FF, my); 69 | my.sphero.stop(); 70 | 71 | var data = JSON.stringify({ 72 | spheroName: my.name, 73 | bucks: "" + (my.totalBucks++) 74 | }); 75 | 76 | var sf = Cylon.robots.salesforce; 77 | sf.devices.salesforce.push("SpheroController", "POST", data); 78 | }); 79 | } 80 | }); 81 | 82 | Cylon.start(); 83 | -------------------------------------------------------------------------------- /examples/sf-sphero/sf-sphero.markdown: -------------------------------------------------------------------------------- 1 | # Sales Force Sphero 2 | 3 | First, let's import Cylon: 4 | 5 | var Cylon = require('../..'); 6 | 7 | With that done, let's define the Robot we'll use to communicate with Salesforce: 8 | 9 | Cylon.robot({ 10 | name: 'salesforce', 11 | 12 | connections: { 13 | sfcon: { 14 | adaptor: 'force', 15 | sfuser: process.env.SF_USERNAME, 16 | sfpass: process.env.SF_SECURITY_TOKEN, 17 | orgCreds: { 18 | clientId: process.env.SF_CLIENT_ID, 19 | clientSecret: process.env.SF_CLIENT_SECRET, 20 | redirectUri: 'http://localhost:3000/oauth/_callback' 21 | } 22 | } 23 | }, 24 | 25 | devices: { 26 | salesforce: { driver: 'force' } 27 | }, 28 | 29 | work: function(my) { 30 | my.salesforce.on('start', function() { 31 | my.salesforce.subscribe('/topic/SpheroMsgOutbound', function(data) { 32 | var msg = "Sphero: " + data.sobject.Sphero_Name__c + ","; 33 | msg += "Bucks: " + data.sobject.Bucks__c + ","; 34 | msg += "SM_Id: " + data.sobject.Id; 35 | 36 | console.log(msg); 37 | 38 | var sphero = Cylon.robots[data.sobject.Sphero_Name__c]; 39 | sphero.react(); 40 | }); 41 | }); 42 | } 43 | }); 44 | 45 | Next up, the shape our Sphero Robot will take: 46 | 47 | Cylon.robot({ 48 | name: 'ROY', 49 | 50 | connections: { 51 | sphero: { adaptor: 'sphero' } 52 | }, 53 | 54 | devices: { 55 | sphero: { driver: 'sphero' } 56 | }, 57 | 58 | react: function() { 59 | this.sphero.setRGB(0x00FF00); 60 | this.sphero.roll(90, Math.floor(Math.random() * 360)); 61 | }, 62 | 63 | work: function(my) { 64 | console.log('Setting up collision detection.'); 65 | my.sphero.detectCollisions(); 66 | 67 | my.sphero.stop(); 68 | my.sphero.setRGB(0x00FF00); 69 | 70 | my.sphero.roll(90, Math.floor(Math.random() * 360)); 71 | 72 | my.sphero.on('collision', function() { 73 | my.sphero.setRGB(0x0000FF, my); 74 | my.sphero.stop(); 75 | 76 | var data = JSON.stringify({ 77 | spheroName: my.name, 78 | bucks: "" + (my.totalBucks++) 79 | }); 80 | 81 | var sf = Cylon.robots.salesforce; 82 | sf.devices.salesforce.push('SpheroController', 'POST', data); 83 | }); 84 | } 85 | }); 86 | 87 | Now that our robot knows what work to do, and the work it will be doing that 88 | hardware with, we can start it: 89 | 90 | Cylon.start(); 91 | -------------------------------------------------------------------------------- /examples/skynet/skynet-blink.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | Cylon.robot({ 6 | connections: { 7 | arduino: { adaptor: "firmata", port: "/dev/ttyACM0" }, 8 | 9 | skynet: { 10 | adaptor: "skynet", 11 | uuid: "96630051-a3dc-11e3-8442-5bf31d98c912", 12 | token: "2s67o7ek98pycik98f43reqr90t6s9k9" 13 | } 14 | }, 15 | 16 | devices: { 17 | led13: { driver: "led", pin: 13, connection: "arduino" } 18 | }, 19 | 20 | work: function(my) { 21 | console.log("Skynet is listening"); 22 | 23 | my.skynet.on("message", function(data) { 24 | data = JSON.parse(data); 25 | 26 | console.log(data); 27 | 28 | if (data.message.red === "on") { 29 | my.led13.turnOn(); 30 | } else { 31 | my.led13.turnOff(); 32 | } 33 | }); 34 | 35 | } 36 | }).start(); 37 | -------------------------------------------------------------------------------- /examples/skynet/skynet-blink.markdown: -------------------------------------------------------------------------------- 1 | # Skynet Blink 2 | 3 | First, let's import Cylon: 4 | 5 | var Cylon = require('../..'); 6 | 7 | Now that we have Cylon imported, we can start defining our robot 8 | 9 | Cylon.robot({ 10 | 11 | Let's define the connections and devices: 12 | 13 | connections: { 14 | arduino: { adaptor: 'firmata', port: '/dev/ttyACM0' }, 15 | 16 | skynet: { 17 | adaptor: 'skynet', 18 | uuid: "96630051-a3dc-11e3-8442-5bf31d98c912", 19 | token: "2s67o7ek98pycik98f43reqr90t6s9k9" 20 | } 21 | }, 22 | 23 | devices: { 24 | led13: { driver: 'led', pin: 13, connection: 'arduino' } 25 | }, 26 | 27 | Now that Cylon knows about the necessary hardware we're going to be using, we'll 28 | tell it what work we want to do: 29 | 30 | work: function(my) { 31 | console.log("Skynet is listening"); 32 | 33 | my.skynet.on('message', function(data) { 34 | data = JSON.parse(data); 35 | 36 | console.log(data); 37 | 38 | if (data.message.red === 'on') { 39 | my.led13.turnOn(); 40 | } else { 41 | my.led13.turnOff(); 42 | } 43 | }); 44 | } 45 | 46 | Now that our robot knows what work to do, and the work it will be doing that 47 | hardware with, we can start it: 48 | 49 | }).start(): 50 | -------------------------------------------------------------------------------- /examples/sphero-pebble-sf/sphero-pebble-sf.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."); 4 | 5 | // ensure you install the API plugin first: 6 | // $ npm install cylon-api-http 7 | Cylon.api({ host: "0.0.0.0", port: "8080" }); 8 | 9 | Cylon.robot({ 10 | name: "pebble", 11 | 12 | connections: { 13 | pebble: { adaptor: "pebble" } 14 | }, 15 | 16 | devices: { 17 | pebble: { driver: "pebble" } 18 | }, 19 | 20 | message: function(msg) { 21 | this.message_queue().push(msg); 22 | }, 23 | 24 | work: function() { 25 | console.log("Pebble connected"); 26 | } 27 | }); 28 | 29 | Cylon.robot({ 30 | name: "salesforce", 31 | 32 | connections: { 33 | sfcon: { 34 | adaptor: "force", 35 | sfuser: process.env.SF_USERNAME, 36 | sfpass: process.env.SF_SECURITY_TOKEN, 37 | orgCreds: { 38 | clientId: process.env.SF_CLIENT_ID, 39 | clientSecret: process.env.SF_CLIENT_SECRET, 40 | redirectUri: "http://localhost:3000/oauth/_callback" 41 | } 42 | } 43 | }, 44 | 45 | devices: { 46 | salesforce: { driver: "force" } 47 | }, 48 | 49 | spheroReport: {}, 50 | 51 | work: function(my) { 52 | my.salesforce.on("start", function() { 53 | my.salesforce.subscribe("/topic/SpheroMsgOutbound", function(data) { 54 | var toPebble = "", 55 | name = data.sobject.Sphero_Name__c, 56 | bucks = data.sobject.Bucks__c; 57 | 58 | var msg = "Sphero: " + name + ","; 59 | msg += "data Bucks: " + bucks + ","; 60 | msg += "SM_Id: " + data.sobject.Id; 61 | 62 | console.log(msg); 63 | 64 | var sphero = Cylon.robots[name]; 65 | sphero.react(); 66 | 67 | my.spheroReport[name] = bucks; 68 | 69 | for (var key in my.spheroReport) { 70 | var val = my.spheroReport[key]; 71 | toPebble += key + ": $" + val + "\n"; 72 | } 73 | 74 | var pebble = Cylon.robots.pebble; 75 | pebble.message(toPebble); 76 | }); 77 | }); 78 | } 79 | }); 80 | 81 | var bots = [ 82 | { port: "/dev/tty.Sphero-ROY-AMP-SPP", name: "ROY" }, 83 | { port: "/dev/tty.Sphero-GBO-AMP-SPP", name: "GBO" }, 84 | { port: "/dev/tty.Sphero-RRY-AMP-SPP", name: "RRY" } 85 | ]; 86 | 87 | bots.forEach(function(bot) { 88 | Cylon.robot({ 89 | name: bot.name, 90 | 91 | connections: { 92 | sphero: { adaptor: "sphero", port: bot.port } 93 | }, 94 | 95 | devices: { 96 | sphero: { driver: "sphero" } 97 | }, 98 | 99 | totalBucks: 1, 100 | payingPower: true, 101 | 102 | react: function() { 103 | this.sphero.setRGB(0x00FF00); 104 | this.sphero.roll(90, Math.floor(Math.random() * 360)); 105 | 106 | this.payingPower = true; 107 | }, 108 | 109 | bankrupt: function() { 110 | var my = this; 111 | 112 | every((3).seconds(), function() { 113 | if (my.payingPower && my.totalBucks > 0) { 114 | my.totalBucks += -1; 115 | 116 | if (my.totalBucks === 0) { 117 | my.sphero.setRGB(0xFF000); 118 | my.sphero.stop(); 119 | } 120 | } 121 | }); 122 | }, 123 | 124 | changeDirection: function() { 125 | var my = this; 126 | 127 | every((1).seconds(), function() { 128 | if (my.payingPower) { 129 | my.sphero.roll(90, Math.floor(Math.random() * 360)); 130 | } 131 | }); 132 | }, 133 | 134 | work: function(my) { 135 | console.log("Setting up collision detection for " + my.name); 136 | 137 | my.sphero.detectCollisions(); 138 | 139 | my.sphero.stop(); 140 | 141 | my.sphero.setRGB(0x00FF00); 142 | 143 | my.sphero.roll(90, Math.floor(Math.random() * 360)); 144 | 145 | my.bankrupt(); 146 | my.changeDirection(); 147 | 148 | my.sphero.on("collision", function() { 149 | my.sphero.setRGB(0x0000FF); 150 | my.sphero.stop(); 151 | my.payingPower = false; 152 | 153 | var data = JSON.stringify({ 154 | spheroName: my.name, 155 | bucks: "" + (my.totalBucks++) 156 | }); 157 | 158 | var sf = Cylon.robots.salesforce; 159 | sf.devices.salesforce.push("SpheroController", "POST", data); 160 | }); 161 | } 162 | }); 163 | }); 164 | 165 | Cylon.start(); 166 | -------------------------------------------------------------------------------- /examples/sphero-pebble-sf/sphero-pebble-sf.markdown: -------------------------------------------------------------------------------- 1 | # Sphero + Pebble + SalesForce 2 | 3 | First, let's import Cylon: 4 | 5 | var Cylon = require('../..'); 6 | 7 | Next up, we'll configure the API Cylon will serve, telling it to serve on port 8 | `8080`. 9 | 10 | // ensure you install the API plugin first: 11 | // $ npm install cylon-api-http 12 | Cylon.api({ host: '0.0.0.0', port: '8080' }); 13 | 14 | Now that we have Cylon imported, we can start defining our Pebble robot: 15 | 16 | Cylon.robot({ 17 | name: 'pebble', 18 | 19 | Let's define the connections and devices: 20 | 21 | connections: { 22 | pebble: { adaptor: 'pebble' } 23 | }, 24 | 25 | devices: { 26 | pebble: { driver: 'pebble' } 27 | }, 28 | 29 | Now that Cylon knows about the necessary hardware we're going to be using, we'll 30 | tell it what work we want to do: 31 | 32 | message: function(msg) { 33 | this.message_queue().push(msg); 34 | }, 35 | 36 | work: function(my) { 37 | console.log('Pebble connected'); 38 | } 39 | }); 40 | 41 | Next, let's define our SalesForce robot: 42 | 43 | Cylon.robot({ 44 | name: 'salesforce', 45 | 46 | Let's define the connections and devices: 47 | 48 | connections: { 49 | sfcon: { 50 | adaptor: 'force', 51 | sfuser: process.env.SF_USERNAME, 52 | sfpass: process.env.SF_SECURITY_TOKEN, 53 | orgCreds: { 54 | clientId: process.env.SF_CLIENT_ID, 55 | clientSecret: process.env.SF_CLIENT_SECRET, 56 | redirectUri: 'http://localhost:3000/oauth/_callback' 57 | } 58 | } 59 | }, 60 | 61 | devices: { 62 | salesforce: { driver: 'force' } 63 | }, 64 | 65 | Tell it what work we want to do: 66 | 67 | spheroReport: {}, 68 | 69 | work: function(my) { 70 | my.salesforce.on('start', function() { 71 | my.salesforce.subscribe('/topic/SpheroMsgOutbound', function(data) { 72 | var toPebble = "", 73 | name = data.sobject.Sphero_Name__c, 74 | bucks = data.sobject.Bucks__c; 75 | 76 | var msg = "Sphero: " + name + ","; 77 | msg += "data Bucks: " + bucks + ","; 78 | msg += "SM_Id: " + data.sobject.Id; 79 | 80 | console.log(msg); 81 | 82 | var sphero = Cylon.robots[name]; 83 | sphero.react(); 84 | 85 | my.spheroReport[name] = bucks; 86 | 87 | for (var key in my.spheroReport) { 88 | var val = my.spheroReport[key]; 89 | toPebble += key + ": $" + val + "\n"; 90 | } 91 | 92 | var pebble = Cylon.robots.pebble; 93 | pebble.message(toPebble); 94 | }); 95 | }); 96 | } 97 | }); 98 | 99 | Now, Let's define our Sphero robots: 100 | 101 | var bots = [ 102 | { port: '/dev/tty.Sphero-ROY-AMP-SPP', name: 'ROY' }, 103 | { port: '/dev/tty.Sphero-GBO-AMP-SPP', name: 'GBO' }, 104 | { port: '/dev/tty.Sphero-RRY-AMP-SPP', name: 'RRY' } 105 | ]; 106 | 107 | bots.forEach(function(bot) { 108 | Cylon.robot({ 109 | name: bot.name, 110 | 111 | connections: { 112 | sphero: { adaptor: 'sphero', port: bot.port } 113 | }, 114 | 115 | devices: { 116 | sphero: { driver: 'sphero' } 117 | }, 118 | 119 | totalBucks: 1, 120 | payingPower: true, 121 | 122 | 123 | Tell them what work we want to do: 124 | 125 | react: function() { 126 | this.sphero.setRGB(0x00FF00); 127 | this.sphero.roll(90, Math.floor(Math.random() * 360)); 128 | 129 | this.payingPower = true; 130 | }, 131 | 132 | bankrupt: function() { 133 | var my = this; 134 | 135 | every((3).seconds(), function() { 136 | if (my.payingPower && my.totalBucks > 0) { 137 | my.totalBucks += -1; 138 | 139 | if (my.totalBucks === 0) { 140 | my.sphero.setRGB(0xFF000); 141 | my.sphero.stop(); 142 | } 143 | } 144 | }); 145 | }, 146 | 147 | changeDirection: function() { 148 | var my = this; 149 | 150 | every((1).seconds(), function() { 151 | if (my.payingPower) { 152 | my.sphero.roll(90, Math.floor(Math.random() * 360)); 153 | } 154 | }); 155 | }, 156 | 157 | work: function(my) { 158 | console.log("Setting up collision detection for " + my.name); 159 | 160 | my.sphero.detectCollisions(); 161 | 162 | my.sphero.stop(); 163 | 164 | my.sphero.setRGB(0x00FF00); 165 | 166 | my.sphero.roll(90, Math.floor(Math.random() * 360)); 167 | 168 | my.bankrupt(); 169 | my.changeDirection(); 170 | 171 | my.sphero.on('collision', function() { 172 | my.sphero.setRGB(0x0000FF); 173 | my.sphero.stop(); 174 | my.payingPower = false; 175 | 176 | var data = JSON.stringify({ 177 | spheroName: my.name, 178 | bucks: "" + (my.totalBucks++) 179 | }); 180 | 181 | var sf = Cylon.robots['salesforce']; 182 | sf.devices.salesforce.push("SpheroController", "POST", data); 183 | }); 184 | } 185 | }); 186 | }); 187 | 188 | Now that Cylon knows about all our robots, and what they'll be doing, we can start: 189 | 190 | Cylon.start(); 191 | -------------------------------------------------------------------------------- /examples/sphero_shakeometer/sphero_shakeometer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("cylon"); 4 | 5 | Cylon.robot({ 6 | connections: { 7 | sphero: { adaptor: "sphero", port: "/dev/tty.Sphero-YBW-RN-SPP" } 8 | }, 9 | 10 | devices: { 11 | sphero: { driver: "sphero" } 12 | }, 13 | 14 | work: function(my) { 15 | var max = 0; 16 | var changingColor = false; 17 | 18 | my.sphero.setDataStreaming(["velocity"], { n: 40, m: 1, pcnt: 0 }); 19 | my.sphero.on("data", function(data) { 20 | if (!changingColor) { 21 | var x = Math.abs(data[0]), 22 | y = Math.abs(data[1]); 23 | 24 | if (x > max) { 25 | max = x; 26 | } 27 | 28 | if (y > max) { 29 | max = y; 30 | } 31 | } 32 | }); 33 | 34 | every((0.6).second(), function() { 35 | changingColor = true; 36 | 37 | if (max < 10) { 38 | my.sphero.setColor("white"); 39 | } else if (max < 100) { 40 | my.sphero.setColor("lightyellow"); 41 | } else if (max < 150) { 42 | my.sphero.setColor("yellow"); 43 | } else if (max < 250) { 44 | my.sphero.setColor("orange"); 45 | } else if (max < 350) { 46 | my.sphero.setColor("orangered"); 47 | } else if (max < 450) { 48 | my.sphero.setColor("red"); 49 | } else { 50 | my.sphero.setColor("darkred"); 51 | } 52 | 53 | max = 0; 54 | changingColor = false; 55 | }); 56 | 57 | } 58 | }).start(); 59 | -------------------------------------------------------------------------------- /examples/start-device/start-device.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("cylon"); 4 | 5 | Cylon.robot({ 6 | connections: { 7 | loopback: { adaptor: "loopback" } 8 | }, 9 | 10 | connectPinger: function() { 11 | this.device("pinger", 12 | {connection: "loopback", driver: "ping"}); 13 | this.startDevice(this.devices.pinger, function() { 14 | console.log("Get ready to ping..."); 15 | }); 16 | }, 17 | 18 | work: function(my) { 19 | my.connectPinger(); 20 | 21 | every((1).second(), function() { 22 | console.log(my.pinger.ping()); 23 | }); 24 | } 25 | }).start(); 26 | -------------------------------------------------------------------------------- /examples/travis/travis.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = require("../.."), 4 | Travis = require("travis-ci"); 5 | 6 | var travis = new Travis({version: "2.0.0"}); 7 | 8 | Cylon.robot({ 9 | connections: { 10 | sphero: { adaptor: "sphero", port: "/dev/rfcomm0" } 11 | }, 12 | 13 | devices: { 14 | sphero: { driver: "sphero" } 15 | }, 16 | 17 | work: function(my) { 18 | var user = "hybridgroup", 19 | name = "cylon"; 20 | 21 | var checkTravis = function() { 22 | console.log("Checking repo " + user + "/" + name); 23 | my.sphero.setColor("blue", true); 24 | 25 | travis.repos({ owner_name: user, name: name }, function(err, res) { 26 | if (err) { 27 | console.log(err); 28 | } 29 | 30 | if (res.repo === undefined) { my.sphero.setColor("blue", true); } 31 | 32 | switch (res.repo.last_build_state) { 33 | case "passed": 34 | my.sphero.setColor("green", true); 35 | break; 36 | case "failed": 37 | my.sphero.setColor("red", true); 38 | break; 39 | default: 40 | my.sphero.setColor("blue", true); 41 | } 42 | }); 43 | }; 44 | 45 | checkTravis(); 46 | 47 | every((10).seconds(), checkTravis); 48 | } 49 | }).start(); 50 | -------------------------------------------------------------------------------- /examples/travis/travis.markdown: -------------------------------------------------------------------------------- 1 | # Cylon and Travis 2 | 3 | For this Cylon example, we're going to check on a Travis build every ten 4 | seconds, and change the color of a Sphero depending on the result. 5 | 6 | Before you run this, make sure you install the following dependencies: 7 | 8 | - **travis-ci** (npm install travis-ci) 9 | - **cylon-sphero** (npm install cylon-sphero) 10 | 11 | First of all, let's load up Cylon. We're going to load the version directly from 12 | the repo, since we're here already: 13 | 14 | var Cylon = require('../..'); 15 | 16 | Next, we'll set up Travis. We're going to be using the very useful [travis-ci][] 17 | module. 18 | 19 | [travis-ci]: https://github.com/pwmckenna/node-travis-ci 20 | 21 | var Travis = require('travis-ci') 22 | 23 | Now that we've got our Travis module imported, let's set it up: 24 | 25 | var travis = new Travis({version: '2.0.0'}); 26 | 27 | Now we have a working interface to the Travis-CI API. Let's set up a username 28 | and repo to query Travis about later, as long as we're here. Feel free to change 29 | these if you want to try with your own repositories. 30 | 31 | var user = "hybridgroup", 32 | name = "cylon"; 33 | 34 | ## Robot 35 | 36 | And with that last bit of setup done, let's start setting up our robot! 37 | 38 | Cylon.robot({ 39 | 40 | We use a connection to tell Cylon what port it can use to communicate with our 41 | Sphero, along with what adaptor it should require (`cylon-sphero`) to connect to 42 | it. We give it a name to make it easier to reference later on. 43 | 44 | connections: { 45 | sphero: { adaptor: 'sphero', port: '/dev/rfcomm0' ;n 46 | }, 47 | 48 | Devices are set up in a similar fashion, but allow us to directly issue commands 49 | to the sphero. These are added to the robot's namespace directly to make them 50 | easy to access. 51 | 52 | devices: { 53 | sphero: { driver: 'sphero' } 54 | }, 55 | 56 | Now that we've told our robot what hardware it has access to, we can start 57 | telling it what it should do. The work function passes along one argument, 58 | a reference to the robot so we can access it's state and hardware. 59 | 60 | work: function(my) { 61 | 62 | We'll define a function to check Travis and change the Sphero's color depending 63 | on the state of the last build. 64 | 65 | var checkTravis = function() { 66 | 67 | First, it will log that it's checking Travis to the logger: 68 | 69 | console.log("Checking repo " + user + "/" + name); 70 | 71 | Let's set the default color of the Sphero to blue until we know what the build 72 | status is: 73 | 74 | my.sphero.setColor('blue', true); 75 | 76 | Now we'll fetch the Travis build status: 77 | 78 | travis.repos({ owner_name: user, name: name }, function(err, res) { 79 | 80 | If we were returned a response containing a repo, we'll check the status of the 81 | build and use that to determine what color we should make the Sphero. 82 | 83 | if (res.repo === undefined) { my.sphero.setColor('blue', true); } 84 | 85 | switch (res.repo.last_build_state) { 86 | 87 | When the build state is passed, then we'll set the Sphero's color to green: 88 | 89 | case 'passed': 90 | my.sphero.setColor('green', true); 91 | break; 92 | 93 | And if the build has failed, let's set the Sphero's color to red: 94 | 95 | case 'failed': 96 | my.sphero.setColor('red', true); 97 | break; 98 | 99 | Otherwise, we'll just set it to blue: 100 | 101 | else me.sphero.setColor 'blue', true 102 | default: 103 | my.sphero.setColor('blue', true); 104 | } 105 | }); 106 | }; 107 | 108 | Now that we've got that function defined, let's call it to set the initial color 109 | of the Sphero: 110 | 111 | checkTravis(); 112 | 113 | And every ten seconds, let's keep checking Travis: 114 | 115 | every((10).seconds(), checkTravis); 116 | } 117 | 118 | And now that we've got our work defined, let's start the robot! 119 | 120 | }).start(); 121 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var MCP = require("./lib/mcp"); 4 | 5 | module.exports = { 6 | MCP: require("./lib/mcp"), 7 | 8 | Robot: require("./lib/robot"), 9 | 10 | Driver: require("./lib/driver"), 11 | Adaptor: require("./lib/adaptor"), 12 | 13 | Utils: require("./lib/utils"), 14 | Logger: require("./lib/logger"), 15 | 16 | IO: { 17 | DigitalPin: require("./lib/io/digital-pin"), 18 | Utils: require("./lib/io/utils") 19 | }, 20 | 21 | robot: MCP.create, 22 | api: require("./lib/api").create, 23 | config: require("./lib/config").update, 24 | 25 | start: MCP.start, 26 | halt: MCP.halt 27 | }; 28 | 29 | process.on("SIGINT", function() { 30 | MCP.halt(process.kill.bind(process, process.pid)); 31 | }); 32 | 33 | if (process.platform === "win32") { 34 | var io = { input: process.stdin, output: process.stdout }, 35 | quit = process.emit.bind(process, "SIGINT"); 36 | 37 | require("readline").createInterface(io).on("SIGINT", quit); 38 | } 39 | -------------------------------------------------------------------------------- /lib/adaptor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Basestar = require("./basestar"), 4 | Utils = require("./utils"), 5 | _ = require("./utils/helpers"); 6 | 7 | function formatErrorMessage(name, message) { 8 | return ["Error in connection", "'" + name + "'", "- " + message].join(" "); 9 | } 10 | 11 | /** 12 | * Adaptor class 13 | * 14 | * @constructor Adaptor 15 | * 16 | * @param {Object} [opts] adaptor options 17 | * @param {String} [opts.name] the adaptor's name 18 | * @param {Object} [opts.robot] the robot the adaptor belongs to 19 | * @param {Object} [opts.host] the host the adaptor will connect to 20 | * @param {Object} [opts.port] the port the adaptor will connect to 21 | */ 22 | var Adaptor = module.exports = function Adaptor(opts) { 23 | Adaptor.__super__.constructor.apply(this, arguments); 24 | 25 | opts = opts || {}; 26 | 27 | this.name = opts.name; 28 | 29 | // the Robot the adaptor belongs to 30 | this.robot = opts.robot; 31 | 32 | // some default options 33 | this.host = opts.host; 34 | this.port = opts.port; 35 | 36 | // misc. details provided in args hash 37 | this.details = {}; 38 | 39 | _.each(opts, function(opt, name) { 40 | if (!_.includes(["robot", "name", "adaptor", "events"], name)) { 41 | this.details[name] = opt; 42 | } 43 | }, this); 44 | }; 45 | 46 | Utils.subclass(Adaptor, Basestar); 47 | 48 | /** 49 | * A base connect function. Must be overwritten by a descendent. 50 | * 51 | * @throws Error if not overridden by a child class 52 | * @return {void} 53 | */ 54 | Adaptor.prototype.connect = function() { 55 | var message = formatErrorMessage( 56 | this.name, 57 | "Adaptor#connect method must be overwritten by descendant classes." 58 | ); 59 | 60 | throw new Error(message); 61 | }; 62 | 63 | /** 64 | * A base disconnect function. Must be overwritten by a descendent. 65 | * 66 | * @throws Error if not overridden by a child class 67 | * @return {void} 68 | */ 69 | Adaptor.prototype.disconnect = function() { 70 | var message = formatErrorMessage( 71 | this.name, 72 | "Adaptor#disconnect method must be overwritten by descendant classes." 73 | ); 74 | 75 | throw new Error(message); 76 | }; 77 | 78 | /** 79 | * Expresses the Adaptor in a JSON-serializable format 80 | * 81 | * @return {Object} a representation of the Adaptor in a serializable format 82 | */ 83 | Adaptor.prototype.toJSON = function() { 84 | return { 85 | name: this.name, 86 | adaptor: this.constructor.name || this.name, 87 | details: this.details 88 | }; 89 | }; 90 | -------------------------------------------------------------------------------- /lib/api.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var MCP = require("./mcp"), 4 | Logger = require("./logger"), 5 | _ = require("./utils/helpers"); 6 | 7 | var api = module.exports = {}; 8 | 9 | api.instances = []; 10 | 11 | /** 12 | * Creates a new API instance 13 | * 14 | * @param {String} [Server] which API plugin to use (e.g. "http" loads 15 | * cylon-api-http) 16 | * @param {Object} opts options for the new API instance 17 | * @return {void} 18 | */ 19 | api.create = function create(Server, opts) { 20 | // if only passed options (or nothing), assume HTTP server 21 | if (Server == null || _.isObject(Server) && !_.isFunction(Server)) { 22 | opts = Server; 23 | Server = "http"; 24 | } 25 | 26 | opts = opts || {}; 27 | 28 | if (_.isString(Server)) { 29 | var req = "cylon-api-" + Server; 30 | 31 | try { 32 | Server = require(req); 33 | } catch (e) { 34 | if (e.code !== "MODULE_NOT_FOUND") { 35 | throw e; 36 | } 37 | 38 | [ 39 | "Cannot find the " + req + " API module.", 40 | "You may be able to install it: `npm install " + req + "`" 41 | ].forEach(Logger.log); 42 | 43 | throw new Error("Missing API plugin - cannot proceed"); 44 | } 45 | } 46 | 47 | opts.mcp = MCP; 48 | 49 | var instance = new Server(opts); 50 | api.instances.push(instance); 51 | instance.start(); 52 | }; 53 | -------------------------------------------------------------------------------- /lib/basestar.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var EventEmitter = require("events").EventEmitter; 4 | 5 | var Utils = require("./utils"), 6 | _ = require("./utils/helpers"); 7 | 8 | /** 9 | * The Basestar class is a wrapper class around EventEmitter that underpins most 10 | * other Cylon adaptor/driver classes, providing useful external base methods 11 | * and functionality. 12 | * 13 | * @constructor Basestar 14 | */ 15 | var Basestar = module.exports = function Basestar() { 16 | Utils.classCallCheck(this, Basestar); 17 | }; 18 | 19 | Utils.subclass(Basestar, EventEmitter); 20 | 21 | /** 22 | * Proxies calls from all methods in the source to a target object 23 | * 24 | * @param {String[]} methods methods to proxy 25 | * @param {Object} target object to proxy methods to 26 | * @param {Object} source object to proxy methods from 27 | * @param {Boolean} [force=false] whether or not to overwrite existing methods 28 | * @return {Object} the source 29 | */ 30 | Basestar.prototype.proxyMethods = Utils.proxyFunctionsToObject; 31 | 32 | /** 33 | * Triggers the provided callback, and emits an event with the provided data. 34 | * 35 | * If an error is provided, emits the 'error' event. 36 | * 37 | * @param {String} event what event to emit 38 | * @param {Function} callback function to be invoked with error/data 39 | * @param {*} err possible error value 40 | * @param {...*} data data values to be passed to error/callback 41 | * @return {void} 42 | */ 43 | Basestar.prototype.respond = function(event, callback, err) { 44 | var args = Array.prototype.slice.call(arguments, 3); 45 | 46 | if (err) { 47 | this.emit("error", err); 48 | } else { 49 | this.emit.apply(this, [event].concat(args)); 50 | } 51 | 52 | if (typeof callback === "function") { 53 | callback.apply(this, [err].concat(args)); 54 | } 55 | }; 56 | 57 | /** 58 | * Defines an event handler to proxy events from a source object to a target 59 | * 60 | * @param {Object} opts event options 61 | * @param {EventEmitter} opts.source source of events to listen for 62 | * @param {EventEmitter} opts.target target new events should be emitted from 63 | * @param {String} opts.eventName name of event to listen for, and proxy 64 | * @param {Bool} [opts.sendUpdate=false] whether to emit the 'update' event 65 | * @param {String} [opts.targetEventName] new event name to emit from target 66 | * @return {EventEmitter} the source object 67 | */ 68 | Basestar.prototype.defineEvent = function(opts) { 69 | opts.sendUpdate = opts.sendUpdate || false; 70 | opts.targetEventName = opts.targetEventName || opts.eventName; 71 | 72 | opts.source.on(opts.eventName, function() { 73 | var args = arguments.length >= 1 ? [].slice.call(arguments, 0) : []; 74 | args.unshift(opts.targetEventName); 75 | opts.target.emit.apply(opts.target, args); 76 | 77 | if (opts.sendUpdate) { 78 | args.unshift("update"); 79 | opts.target.emit.apply(opts.target, args); 80 | } 81 | }); 82 | 83 | return opts.source; 84 | }; 85 | 86 | /** 87 | * A #defineEvent shorthand for adaptors. 88 | * 89 | * Proxies events from an adaptor's connector to the adaptor itself. 90 | * 91 | * @param {Object} opts proxy options 92 | * @return {EventEmitter} the adaptor's connector 93 | */ 94 | Basestar.prototype.defineAdaptorEvent = function(opts) { 95 | return this._proxyEvents(opts, this.connector, this); 96 | }; 97 | 98 | /** 99 | * A #defineEvent shorthand for drivers. 100 | * 101 | * Proxies events from an driver's connection to the driver itself. 102 | * 103 | * @param {Object} opts proxy options 104 | * @return {EventEmitter} the driver's connection 105 | */ 106 | Basestar.prototype.defineDriverEvent = function(opts) { 107 | return this._proxyEvents(opts, this.connection, this); 108 | }; 109 | 110 | Basestar.prototype._proxyEvents = function(opts, source, target) { 111 | opts = _.isString(opts) ? { eventName: opts } : opts; 112 | 113 | opts.source = source; 114 | opts.target = target; 115 | 116 | return this.defineEvent(opts); 117 | }; 118 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _ = require("./utils/helpers"); 4 | 5 | var config = module.exports = {}, 6 | callbacks = []; 7 | 8 | // default data 9 | config.haltTimeout = 3000; 10 | config.testMode = false; 11 | config.logger = null; 12 | config.silent = false; 13 | config.debug = false; 14 | 15 | /** 16 | * Updates the Config, and triggers handler callbacks 17 | * 18 | * @param {Object} data new configuration information to set 19 | * @return {Object} the updated configuration 20 | */ 21 | config.update = function update(data) { 22 | var forbidden = ["update", "subscribe", "unsubscribe"]; 23 | 24 | Object.keys(data).forEach(function(key) { 25 | if (~forbidden.indexOf(key)) { delete data[key]; } 26 | }); 27 | 28 | if (!Object.keys(data).length) { 29 | return config; 30 | } 31 | 32 | _.extend(config, data); 33 | 34 | callbacks.forEach(function(callback) { callback(data); }); 35 | 36 | return config; 37 | }; 38 | 39 | /** 40 | * Subscribes a function to be called whenever the config is updated 41 | * 42 | * @param {Function} callback function to be called with updated data 43 | * @return {void} 44 | */ 45 | config.subscribe = function subscribe(callback) { 46 | callbacks.push(callback); 47 | }; 48 | 49 | /** 50 | * Unsubscribes a callback from configuration changes 51 | * 52 | * @param {Function} callback function to unsubscribe from changes 53 | * @return {void} 54 | */ 55 | config.unsubscribe = function unsubscribe(callback) { 56 | var idx = callbacks.indexOf(callback); 57 | if (idx >= 0) { callbacks.splice(idx, 1); } 58 | }; 59 | -------------------------------------------------------------------------------- /lib/driver.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Basestar = require("./basestar"), 4 | Utils = require("./utils"), 5 | _ = require("./utils/helpers"); 6 | 7 | function formatErrorMessage(name, message) { 8 | return ["Error in driver", "'" + name + "'", "- " + message].join(" "); 9 | } 10 | 11 | /** 12 | * Driver class 13 | * 14 | * @constructor Driver 15 | * @param {Object} [opts] driver options 16 | * @param {String} [opts.name] the driver's name 17 | * @param {Object} [opts.robot] the robot the driver belongs to 18 | * @param {Object} [opts.connection] the adaptor the driver works through 19 | * @param {Number} [opts.pin] the pin number the driver should have 20 | * @param {Number} [opts.interval=10] read interval in milliseconds 21 | */ 22 | var Driver = module.exports = function Driver(opts) { 23 | Driver.__super__.constructor.apply(this, arguments); 24 | 25 | opts = opts || {}; 26 | 27 | this.name = opts.name; 28 | this.robot = opts.robot; 29 | 30 | this.connection = opts.connection; 31 | 32 | this.commands = {}; 33 | this.events = []; 34 | 35 | // some default options 36 | this.pin = opts.pin; 37 | this.interval = opts.interval || 10; 38 | 39 | this.details = {}; 40 | 41 | _.each(opts, function(opt, name) { 42 | var banned = ["robot", "name", "connection", "driver", "events"]; 43 | 44 | if (!_.includes(banned, name)) { 45 | this.details[name] = opt; 46 | } 47 | }, this); 48 | }; 49 | 50 | Utils.subclass(Driver, Basestar); 51 | 52 | /** 53 | * A base start function. Must be overwritten by a descendent. 54 | * 55 | * @throws Error if not overridden by a child class 56 | * @return {void} 57 | */ 58 | Driver.prototype.start = function() { 59 | var message = formatErrorMessage( 60 | this.name, 61 | "Driver#start method must be overwritten by descendant classes." 62 | ); 63 | 64 | throw new Error(message); 65 | }; 66 | 67 | /** 68 | * A base halt function. Must be overwritten by a descendent. 69 | * 70 | * @throws Error if not overridden by a child class 71 | * @return {void} 72 | */ 73 | Driver.prototype.halt = function() { 74 | var message = formatErrorMessage( 75 | this.name, 76 | "Driver#halt method must be overwritten by descendant classes." 77 | ); 78 | 79 | throw new Error(message); 80 | }; 81 | 82 | /** 83 | * Sets up an array of commands for the Driver. 84 | * 85 | * Proxies commands from the Driver to its connection (or a manually specified 86 | * proxy), and adds a snake_cased version to the driver's commands object. 87 | * 88 | * @param {String[]} commands an array of driver commands 89 | * @param {Object} [proxy=this.connection] proxy target 90 | * @return {void} 91 | */ 92 | Driver.prototype.setupCommands = function(commands, proxy) { 93 | if (proxy == null) { 94 | proxy = this.connection; 95 | } 96 | 97 | Utils.proxyFunctionsToObject(commands, proxy, this); 98 | 99 | function endsWith(string, substr) { 100 | return string.indexOf(substr, string.length - substr.length) !== -1; 101 | } 102 | 103 | commands.forEach(function(command) { 104 | var snakeCase = command.replace(/[A-Z]+/g, function(match) { 105 | if (match.length > 1 && !endsWith(command, match)) { 106 | match = match.replace(/[A-Z]$/, function(m) { 107 | return "_" + m.toLowerCase(); 108 | }); 109 | } 110 | 111 | return "_" + match.toLowerCase(); 112 | }).replace(/^_/, ""); 113 | 114 | this.commands[snakeCase] = this[command]; 115 | }, this); 116 | }; 117 | 118 | /** 119 | * Expresses the Driver in a JSON-serializable format 120 | * 121 | * @return {Object} a representation of the Driver in a serializable format 122 | */ 123 | Driver.prototype.toJSON = function() { 124 | return { 125 | name: this.name, 126 | driver: this.constructor.name || this.name, 127 | connection: this.connection.name, 128 | commands: Object.keys(this.commands), 129 | events: this.events, 130 | details: this.details 131 | }; 132 | }; 133 | -------------------------------------------------------------------------------- /lib/initializer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Registry = require("./registry"), 4 | Config = require("./config"), 5 | _ = require("./utils/helpers"); 6 | 7 | function testMode() { 8 | return process.env.NODE_ENV === "test" && Config.testMode; 9 | } 10 | 11 | module.exports = function Initializer(type, opts) { 12 | var mod; 13 | 14 | mod = Registry.findBy(type, opts[type]); 15 | 16 | if (!mod) { 17 | if (opts.module) { 18 | Registry.register(opts.module); 19 | } else { 20 | Registry.register("cylon-" + opts[type]); 21 | } 22 | 23 | mod = Registry.findBy(type, opts[type]); 24 | } 25 | 26 | if (!mod) { 27 | var err = [ "Unable to find", type, "for", opts[type] ].join(" "); 28 | throw new Error(err); 29 | } 30 | 31 | var obj = mod[type](opts); 32 | 33 | _.each(obj, function(prop, name) { 34 | if (name === "constructor") { 35 | return; 36 | } 37 | 38 | if (_.isFunction(prop)) { 39 | obj[name] = prop.bind(obj); 40 | } 41 | }); 42 | 43 | if (testMode()) { 44 | var test = Registry.findBy(type, "test")[type](opts); 45 | 46 | _.each(obj, function(prop, name) { 47 | if (_.isFunction(prop) && !test[name]) { 48 | test[name] = function() { return true; }; 49 | } 50 | }); 51 | 52 | return test; 53 | } 54 | 55 | return obj; 56 | }; 57 | -------------------------------------------------------------------------------- /lib/io/digital-pin.js: -------------------------------------------------------------------------------- 1 | /* eslint no-sync: 0 */ 2 | 3 | "use strict"; 4 | 5 | var FS = require("fs"), 6 | EventEmitter = require("events").EventEmitter; 7 | 8 | var Utils = require("../utils"); 9 | 10 | var GPIO_PATH = "/sys/class/gpio"; 11 | 12 | var GPIO_READ = "in"; 13 | var GPIO_WRITE = "out"; 14 | 15 | /** 16 | * The DigitalPin class provides an interface with the Linux GPIO system present 17 | * in single-board computers such as Raspberry Pi, or Beaglebone Black. 18 | * 19 | * @constructor DigitalPin 20 | * @param {Object} opts digital pin options 21 | * @param {String} pin which pin number to use 22 | * @param {String} mode which pin mode to use 23 | */ 24 | var DigitalPin = module.exports = function DigitalPin(opts) { 25 | this.pinNum = opts.pin.toString(); 26 | this.status = "low"; 27 | this.ready = false; 28 | this.mode = opts.mode; 29 | }; 30 | 31 | Utils.subclass(DigitalPin, EventEmitter); 32 | 33 | DigitalPin.prototype.connect = function(mode) { 34 | if (this.mode == null) { 35 | this.mode = mode; 36 | } 37 | 38 | FS.exists(this._pinPath(), function(exists) { 39 | if (exists) { 40 | this._openPin(); 41 | } else { 42 | this._createGPIOPin(); 43 | } 44 | }.bind(this)); 45 | }; 46 | 47 | DigitalPin.prototype.close = function() { 48 | FS.writeFile(this._unexportPath(), this.pinNum, function(err) { 49 | this._closeCallback(err); 50 | }.bind(this)); 51 | }; 52 | 53 | DigitalPin.prototype.closeSync = function() { 54 | FS.writeFileSync(this._unexportPath(), this.pinNum); 55 | this._closeCallback(false); 56 | }; 57 | 58 | DigitalPin.prototype.digitalWrite = function(value) { 59 | if (this.mode !== "w") { 60 | this._setMode("w"); 61 | } 62 | 63 | this.status = value === 1 ? "high" : "low"; 64 | 65 | FS.writeFile(this._valuePath(), value, function(err) { 66 | if (err) { 67 | var str = "Error occurred while writing value "; 68 | str += value + " to pin " + this.pinNum; 69 | 70 | this.emit("error", str); 71 | } else { 72 | this.emit("digitalWrite", value); 73 | } 74 | }.bind(this)); 75 | 76 | return value; 77 | }; 78 | 79 | // Public: Reads the digial pin"s value periodicly on a supplied interval, 80 | // and emits the result or an error 81 | // 82 | // interval - time (in milliseconds) to read from the pin at 83 | // 84 | // Returns the defined interval 85 | DigitalPin.prototype.digitalRead = function(interval) { 86 | if (this.mode !== "r") { this._setMode("r"); } 87 | 88 | Utils.every(interval, function() { 89 | FS.readFile(this._valuePath(), function(err, data) { 90 | if (err) { 91 | var error = "Error occurred while reading from pin " + this.pinNum; 92 | this.emit("error", error); 93 | } else { 94 | var readData = parseInt(data.toString(), 10); 95 | this.emit("digitalRead", readData); 96 | } 97 | }.bind(this)); 98 | }.bind(this)); 99 | }; 100 | 101 | DigitalPin.prototype.setHigh = function() { 102 | return this.digitalWrite(1); 103 | }; 104 | 105 | DigitalPin.prototype.setLow = function() { 106 | return this.digitalWrite(0); 107 | }; 108 | 109 | DigitalPin.prototype.toggle = function() { 110 | return (this.status === "low") ? this.setHigh() : this.setLow(); 111 | }; 112 | 113 | // Creates the GPIO file to read/write from 114 | DigitalPin.prototype._createGPIOPin = function() { 115 | FS.writeFile(this._exportPath(), this.pinNum, function(err) { 116 | if (err) { 117 | this.emit("error", "Error while creating pin files"); 118 | } else { 119 | this._openPin(); 120 | } 121 | }.bind(this)); 122 | }; 123 | 124 | DigitalPin.prototype._openPin = function() { 125 | this._setMode(this.mode, true); 126 | this.emit("open"); 127 | }; 128 | 129 | DigitalPin.prototype._closeCallback = function(err) { 130 | if (err) { 131 | this.emit("error", "Error while closing pin files"); 132 | } else { 133 | this.emit("close", this.pinNum); 134 | } 135 | }; 136 | 137 | // Sets the mode for the pin by writing the values to the pin reference files 138 | DigitalPin.prototype._setMode = function(mode, emitConnect) { 139 | if (emitConnect == null) { emitConnect = false; } 140 | 141 | this.mode = mode; 142 | 143 | var data = (mode === "w") ? GPIO_WRITE : GPIO_READ; 144 | 145 | FS.writeFile(this._directionPath(), data, function(err) { 146 | this._setModeCallback(err, emitConnect); 147 | }.bind(this)); 148 | }; 149 | 150 | DigitalPin.prototype._setModeCallback = function(err, emitConnect) { 151 | if (err) { 152 | return this.emit("error", "Setting up pin direction failed"); 153 | } 154 | 155 | this.ready = true; 156 | 157 | if (emitConnect) { 158 | this.emit("connect", this.mode); 159 | } 160 | }; 161 | 162 | DigitalPin.prototype._directionPath = function() { 163 | return this._pinPath() + "/direction"; 164 | }; 165 | 166 | DigitalPin.prototype._valuePath = function() { 167 | return this._pinPath() + "/value"; 168 | }; 169 | 170 | DigitalPin.prototype._pinPath = function() { 171 | return GPIO_PATH + "/gpio" + this.pinNum; 172 | }; 173 | 174 | DigitalPin.prototype._exportPath = function() { 175 | return GPIO_PATH + "/export"; 176 | }; 177 | 178 | DigitalPin.prototype._unexportPath = function() { 179 | return GPIO_PATH + "/unexport"; 180 | }; 181 | -------------------------------------------------------------------------------- /lib/io/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | /** 5 | * Calculates PWM Period and Duty based on provided params. 6 | * 7 | * @param {Number} scaledDuty the scaled duty value 8 | * @param {Number} freq frequency to use 9 | * @param {Number} pulseWidth pulse width 10 | * @param {String} [polarity=high] polarity value (high or low) 11 | * @return {Object} calculated period and duty encapsulated in an object 12 | */ 13 | periodAndDuty: function(scaledDuty, freq, pulseWidth, polarity) { 14 | var period, duty, maxDuty; 15 | 16 | polarity = polarity || "high"; 17 | period = Math.round(1.0e9 / freq); 18 | 19 | if (pulseWidth != null) { 20 | var pulseWidthMin = pulseWidth.min * 1000, 21 | pulseWidthMax = pulseWidth.max * 1000; 22 | 23 | maxDuty = pulseWidthMax - pulseWidthMin; 24 | duty = Math.round(pulseWidthMin + (maxDuty * scaledDuty)); 25 | } else { 26 | duty = Math.round(period * scaledDuty); 27 | } 28 | 29 | if (polarity === "low") { 30 | duty = period - duty; 31 | } 32 | 33 | return { period: period, duty: duty }; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* eslint no-use-before-define: 0 */ 4 | 5 | var Config = require("./config"), 6 | _ = require("./utils/helpers"); 7 | 8 | var BasicLogger = function basiclogger(str) { 9 | var prefix = new Date().toISOString() + " : "; 10 | console.log(prefix + str); 11 | }; 12 | 13 | var NullLogger = function nulllogger() {}; 14 | 15 | var Logger = module.exports = { 16 | setup: setup, 17 | 18 | should: { 19 | log: true, 20 | debug: false 21 | }, 22 | 23 | log: function log(str) { 24 | if (Logger.should.log) { 25 | Logger.logger.call(Logger.logger, str); 26 | } 27 | }, 28 | 29 | debug: function debug(str) { 30 | if (Logger.should.log && Logger.should.debug) { 31 | Logger.logger.call(Logger.logger, str); 32 | } 33 | } 34 | }; 35 | 36 | function setup(opts) { 37 | if (_.isObject(opts)) { _.extend(Config, opts); } 38 | 39 | var logger = Config.logger; 40 | 41 | // if no logger supplied, use basic logger 42 | if (logger == null) { logger = BasicLogger; } 43 | 44 | // if logger is still falsy, use NullLogger 45 | Logger.logger = logger || NullLogger; 46 | 47 | Logger.should.log = !Config.silent; 48 | Logger.should.debug = Config.debug; 49 | 50 | // --silent CLI flag overrides 51 | if (_.includes(process.argv, "--silent")) { 52 | Logger.should.log = false; 53 | } 54 | 55 | // --debug CLI flag overrides 56 | if (_.includes(process.argv, "--debug")) { 57 | Logger.should.debug = false; 58 | } 59 | 60 | return Logger; 61 | } 62 | 63 | setup(); 64 | Config.subscribe(setup); 65 | 66 | // deprecated holdovers 67 | ["info", "warn", "error", "fatal"].forEach(function(method) { 68 | var called = false; 69 | 70 | function showDeprecationNotice() { 71 | console.log("The method Logger#" + method + " has been deprecated."); 72 | console.log("It will be removed in Cylon 2.0.0."); 73 | console.log("Please switch to using the #log or #debug Logger methods"); 74 | 75 | called = true; 76 | } 77 | 78 | Logger[method] = function() { 79 | if (!called) { showDeprecationNotice(); } 80 | Logger.log.apply(null, arguments); 81 | }; 82 | }); 83 | -------------------------------------------------------------------------------- /lib/mcp.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var EventEmitter = require("events").EventEmitter; 4 | 5 | var Config = require("./config"), 6 | Logger = require("./logger"), 7 | Utils = require("./utils"), 8 | Robot = require("./robot"), 9 | _ = require("./utils/helpers"); 10 | 11 | var mcp = module.exports = new EventEmitter(); 12 | 13 | mcp.robots = {}; 14 | mcp.commands = {}; 15 | mcp.events = [ "robot_added", "robot_removed" ]; 16 | 17 | /** 18 | * Creates a new Robot with the provided options. 19 | * 20 | * @param {Object} opts robot options 21 | * @return {Robot} the new robot 22 | */ 23 | mcp.create = function create(opts) { 24 | opts = opts || {}; 25 | 26 | // check if a robot with the same name exists already 27 | if (opts.name && mcp.robots[opts.name]) { 28 | var original = opts.name; 29 | opts.name = Utils.makeUnique(original, Object.keys(mcp.robots)); 30 | 31 | var str = "Robot names must be unique. Renaming '"; 32 | str += original + "' to '" + opts.name + "'"; 33 | 34 | Logger.log(str); 35 | } 36 | 37 | var bot = new Robot(opts); 38 | mcp.robots[bot.name] = bot; 39 | mcp.emit("robot_added", bot.name); 40 | 41 | return bot; 42 | }; 43 | 44 | mcp.start = function start(callback) { 45 | var fns = _.pluck(mcp.robots, "start"); 46 | 47 | _.parallel(fns, function() { 48 | var mode = Utils.fetch(Config, "workMode", "async"); 49 | if (mode === "sync") { _.invoke(mcp.robots, "startWork"); } 50 | callback(); 51 | }); 52 | }; 53 | 54 | /** 55 | * Halts all MCP robots. 56 | * 57 | * @param {Function} callback function to call when done halting robots 58 | * @return {void} 59 | */ 60 | mcp.halt = function halt(callback) { 61 | callback = callback || function() {}; 62 | 63 | var timeout = setTimeout(callback, Config.haltTimeout || 3000); 64 | 65 | _.parallel(_.pluck(mcp.robots, "halt"), function() { 66 | clearTimeout(timeout); 67 | callback(); 68 | }); 69 | }; 70 | 71 | /** 72 | * Serializes MCP robots, commands, and events into a JSON-serializable Object. 73 | * 74 | * @return {Object} a serializable representation of the MCP 75 | */ 76 | mcp.toJSON = function() { 77 | return { 78 | robots: _.invoke(mcp.robots, "toJSON"), 79 | commands: Object.keys(mcp.commands), 80 | events: mcp.events 81 | }; 82 | }; 83 | -------------------------------------------------------------------------------- /lib/registry.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Logger = require("./logger"), 4 | _ = require("./utils/helpers"), 5 | path = require("path"); 6 | 7 | // Explicitly these modules here, so Browserify can grab them later 8 | require("./test/loopback"); 9 | require("./test/test-adaptor"); 10 | require("./test/test-driver"); 11 | require("./test/ping"); 12 | 13 | var missingModuleError = function(module) { 14 | var str = "Cannot find the '" + module + "' module.\n"; 15 | str += "This problem might be fixed by installing it with "; 16 | str += "'npm install " + module + "' and trying again."; 17 | 18 | console.log(str); 19 | 20 | process.emit("SIGINT"); 21 | }; 22 | 23 | var Registry = module.exports = { 24 | data: {}, 25 | 26 | register: function(module) { 27 | if (this.data[module]) { 28 | return this.data[module].module; 29 | } 30 | 31 | var pkg; 32 | 33 | try { 34 | if (this.isModuleInDevelopment(module)) { 35 | pkg = require(path.resolve(".") + "/index"); 36 | } else { 37 | pkg = require(module); 38 | } 39 | } catch (e) { 40 | if (e.code === "MODULE_NOT_FOUND") { 41 | missingModuleError(module); 42 | } 43 | 44 | throw e; 45 | } 46 | 47 | this.data[module] = { 48 | module: pkg, 49 | adaptors: pkg.adaptors || [], 50 | drivers: pkg.drivers || [], 51 | dependencies: pkg.dependencies || [] 52 | }; 53 | 54 | this.logRegistration(module, this.data[module]); 55 | 56 | this.data[module].dependencies.forEach(function(dep) { 57 | Registry.register(dep); 58 | }); 59 | 60 | return this.data[module].module; 61 | }, 62 | 63 | findBy: function(prop, name) { 64 | // pluralize, if necessary 65 | if (prop.slice(-1) !== "s") { 66 | prop += "s"; 67 | } 68 | 69 | return this.search(prop, name); 70 | }, 71 | 72 | findByModule: function(module) { 73 | if (!this.data[module]) { 74 | return null; 75 | } 76 | 77 | return this.data[module].module; 78 | }, 79 | 80 | logRegistration: function(name) { 81 | var module = this.data[name]; 82 | 83 | Logger.debug("Registering module " + name); 84 | 85 | ["adaptors", "drivers", "dependencies"].forEach(function(field) { 86 | if (module[field].length) { 87 | Logger.debug(" " + field + ":"); 88 | module[field].forEach(function(item) { 89 | Logger.debug(" - " + item); 90 | }); 91 | } 92 | }); 93 | }, 94 | 95 | search: function(entry, value) { 96 | for (var name in this.data) { 97 | if (this.data.hasOwnProperty(name)) { 98 | var repo = this.data[name]; 99 | 100 | if (repo[entry] && _.includes(repo[entry], value)) { 101 | return repo.module; 102 | } 103 | } 104 | } 105 | 106 | return false; 107 | }, 108 | 109 | isModuleInDevelopment: function(module) { 110 | return (path.basename(path.resolve(".")) === module); 111 | } 112 | }; 113 | 114 | // Default drivers/adaptors: 115 | ["loopback", "ping", "test-adaptor", "test-driver"].forEach(function(module) { 116 | Registry.register("./test/" + module); 117 | }); 118 | -------------------------------------------------------------------------------- /lib/robot.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var initializer = require("./initializer"), 4 | Logger = require("./logger"), 5 | Utils = require("./utils"), 6 | Config = require("./config"), 7 | _ = require("./utils/helpers"); 8 | 9 | var validator = require("./validator"); 10 | 11 | var EventEmitter = require("events").EventEmitter; 12 | 13 | // used when creating default robot names 14 | var ROBOT_ID = 1; 15 | 16 | /** 17 | * Creates a new Robot instance based on provided options 18 | * 19 | * @constructor 20 | * @param {Object} opts object with Robot options 21 | * @param {String} [name] the name the robot should have 22 | * @param {Object} [connections] object containing connection info for the Robot 23 | * @param {Object} [devices] object containing device information for the Robot 24 | * @param {Function} [work] a function the Robot will run when started 25 | * @returns {Robot} new Robot instance 26 | */ 27 | var Robot = module.exports = function Robot(opts) { 28 | Utils.classCallCheck(this, Robot); 29 | 30 | opts = opts || {}; 31 | 32 | validator.validate(opts); 33 | 34 | // auto-bind prototype methods 35 | for (var prop in Object.getPrototypeOf(this)) { 36 | if (this[prop] && prop !== "constructor") { 37 | this[prop] = this[prop].bind(this); 38 | } 39 | } 40 | 41 | this.initRobot(opts); 42 | 43 | _.each(opts, function(opt, name) { 44 | if (this[name] !== undefined) { 45 | return; 46 | } 47 | 48 | if (_.isFunction(opt)) { 49 | this[name] = opt.bind(this); 50 | 51 | if (opts.commands == null) { 52 | this.commands[name] = opt.bind(this); 53 | } 54 | } else { 55 | this[name] = opt; 56 | } 57 | }, this); 58 | 59 | if (opts.commands) { 60 | var cmds; 61 | 62 | if (_.isFunction(opts.commands)) { 63 | cmds = opts.commands.call(this); 64 | } else { 65 | cmds = opts.commands; 66 | } 67 | 68 | if (_.isObject(cmds)) { 69 | this.commands = cmds; 70 | } else { 71 | var err = "#commands must be an object "; 72 | err += "or a function that returns an object"; 73 | throw new Error(err); 74 | } 75 | } 76 | 77 | var mode = Utils.fetch(Config, "mode", "manual"); 78 | 79 | if (mode === "auto") { 80 | // run on the next tick, to allow for "work" event handlers to be set up 81 | setTimeout(this.start, 0); 82 | } 83 | }; 84 | 85 | Utils.subclass(Robot, EventEmitter); 86 | 87 | /** 88 | * Condenses information on a Robot to a JSON-serializable format 89 | * 90 | * @return {Object} serializable information on the Robot 91 | */ 92 | Robot.prototype.toJSON = function() { 93 | return { 94 | name: this.name, 95 | connections: _.invoke(this.connections, "toJSON"), 96 | devices: _.invoke(this.devices, "toJSON"), 97 | commands: Object.keys(this.commands), 98 | events: _.isArray(this.events) ? this.events : [] 99 | }; 100 | }; 101 | 102 | /** 103 | * Adds a new Connection to the Robot with the provided name and details. 104 | * 105 | * @param {String} name string name for the Connection to use 106 | * @param {Object} conn options for the Connection initializer 107 | * @return {Object} the robot 108 | */ 109 | Robot.prototype.connection = function(name, conn) { 110 | conn.robot = this; 111 | conn.name = name; 112 | 113 | if (this.connections[conn.name]) { 114 | var original = conn.name, 115 | str; 116 | 117 | conn.name = Utils.makeUnique(original, Object.keys(this.connections)); 118 | 119 | str = "Connection names must be unique."; 120 | str += "Renaming '" + original + "' to '" + conn.name + "'"; 121 | this.log(str); 122 | } 123 | if ("adapter" in conn) { 124 | conn.adaptor = conn.adapter; 125 | } 126 | this.connections[conn.name] = initializer("adaptor", conn); 127 | 128 | return this; 129 | }; 130 | 131 | /** 132 | * Initializes all values for a new Robot. 133 | * 134 | * @param {Object} opts object passed to Robot constructor 135 | * @return {void} 136 | */ 137 | Robot.prototype.initRobot = function(opts) { 138 | this.name = opts.name || "Robot " + ROBOT_ID++; 139 | this.running = false; 140 | 141 | this.connections = {}; 142 | this.devices = {}; 143 | 144 | this.work = opts.work || opts.play; 145 | 146 | this.commands = {}; 147 | 148 | if (!this.work) { 149 | this.work = function() { this.log("No work yet."); }; 150 | } 151 | 152 | _.each(opts.connections, function(conn, key) { 153 | var name = _.isString(key) ? key : conn.name; 154 | 155 | if (conn.devices) { 156 | opts.devices = opts.devices || {}; 157 | 158 | _.each(conn.devices, function(device, d) { 159 | device.connection = name; 160 | opts.devices[d] = device; 161 | }); 162 | 163 | delete conn.devices; 164 | } 165 | 166 | this.connection(name, _.extend({}, conn)); 167 | }, this); 168 | 169 | _.each(opts.devices, function(device, key) { 170 | var name = _.isString(key) ? key : device.name; 171 | this.device(name, _.extend({}, device)); 172 | }, this); 173 | }; 174 | 175 | /** 176 | * Adds a new Device to the Robot with the provided name and details. 177 | * 178 | * @param {String} name string name for the Device to use 179 | * @param {Object} device options for the Device initializer 180 | * @return {Object} the robot 181 | */ 182 | Robot.prototype.device = function(name, device) { 183 | var str; 184 | 185 | device.robot = this; 186 | device.name = name; 187 | 188 | if (this.devices[device.name]) { 189 | var original = device.name; 190 | device.name = Utils.makeUnique(original, Object.keys(this.devices)); 191 | 192 | str = "Device names must be unique."; 193 | str += "Renaming '" + original + "' to '" + device.name + "'"; 194 | this.log(str); 195 | } 196 | 197 | if (_.isString(device.connection)) { 198 | if (this.connections[device.connection] == null) { 199 | str = "No connection found with the name " + device.connection + ".\n"; 200 | this.log(str); 201 | process.emit("SIGINT"); 202 | } 203 | 204 | device.connection = this.connections[device.connection]; 205 | } else { 206 | for (var c in this.connections) { 207 | device.connection = this.connections[c]; 208 | break; 209 | } 210 | } 211 | 212 | this.devices[device.name] = initializer("driver", device); 213 | 214 | return this; 215 | }; 216 | 217 | /** 218 | * Starts the Robot's connections, then devices, then work. 219 | * 220 | * @param {Function} callback function to be triggered when the Robot has 221 | * started working 222 | * @return {Object} the Robot 223 | */ 224 | Robot.prototype.start = function(callback) { 225 | if (this.running) { 226 | return this; 227 | } 228 | 229 | var mode = Utils.fetch(Config, "workMode", "async"); 230 | 231 | var start = function() { 232 | if (mode === "async") { 233 | this.startWork(); 234 | } 235 | }.bind(this); 236 | 237 | _.series([ 238 | this.startConnections, 239 | this.startDevices, 240 | start 241 | ], function(err, results) { 242 | if (err) { 243 | this.log("An error occured while trying to start the robot:"); 244 | this.log(err); 245 | 246 | this.halt(function() { 247 | if (_.isFunction(this.error)) { 248 | this.error.call(this, err); 249 | } 250 | 251 | if (this.listeners("error").length) { 252 | this.emit("error", err); 253 | } 254 | }.bind(this)); 255 | } 256 | 257 | if (_.isFunction(callback)) { 258 | callback(err, results); 259 | } 260 | }.bind(this)); 261 | 262 | return this; 263 | }; 264 | 265 | /** 266 | * Starts the Robot's work function 267 | * 268 | * @return {void} 269 | */ 270 | Robot.prototype.startWork = function() { 271 | this.log("Working."); 272 | 273 | this.emit("ready", this); 274 | this.work.call(this, this); 275 | this.running = true; 276 | }; 277 | 278 | /** 279 | * Starts the Robot's connections 280 | * 281 | * @param {Function} callback function to be triggered after the connections are 282 | * started 283 | * @return {void} 284 | */ 285 | Robot.prototype.startConnections = function(callback) { 286 | this.log("Starting connections."); 287 | 288 | var starters = _.map(this.connections, function(conn) { 289 | return function(cb) { 290 | return this.startConnection(conn, cb); 291 | }.bind(this); 292 | }, this); 293 | 294 | return _.parallel(starters, callback); 295 | }; 296 | 297 | /** 298 | * Starts a single connection on Robot 299 | * 300 | * @param {Object} connection to start 301 | * @param {Function} callback function to be triggered after the connection is 302 | * started 303 | * @return {void} 304 | */ 305 | Robot.prototype.startConnection = function(connection, callback) { 306 | if (connection.connected === true) { 307 | return callback.call(connection); 308 | } 309 | 310 | var str = "Starting connection '" + connection.name + "'"; 311 | 312 | if (connection.host) { 313 | str += " on host " + connection.host; 314 | } else if (connection.port) { 315 | str += " on port " + connection.port; 316 | } 317 | 318 | this.log(str + "."); 319 | this[connection.name] = connection; 320 | connection.connect.call(connection, callback); 321 | connection.connected = true; 322 | return true; 323 | }; 324 | 325 | /** 326 | * Starts the Robot's devices 327 | * 328 | * @param {Function} callback function to be triggered after the devices are 329 | * started 330 | * @return {void} 331 | */ 332 | Robot.prototype.startDevices = function(callback) { 333 | var log = this.log; 334 | 335 | log("Starting devices."); 336 | 337 | var starters = _.map(this.devices, function(device) { 338 | return function(cb) { 339 | return this.startDevice(device, cb); 340 | }.bind(this); 341 | }, this); 342 | 343 | return _.parallel(starters, callback); 344 | }; 345 | 346 | /** 347 | * Starts a single device on Robot 348 | * 349 | * @param {Object} device to start 350 | * @param {Function} callback function to be triggered after the device is 351 | * started 352 | * @return {void} 353 | */ 354 | Robot.prototype.startDevice = function(device, callback) { 355 | if (device.started === true) { 356 | return callback.call(device); 357 | } 358 | 359 | var log = this.log; 360 | var str = "Starting device '" + device.name + "'"; 361 | 362 | if (device.pin || device.pin === 0) { 363 | str += " on pin " + device.pin; 364 | } 365 | 366 | log(str + "."); 367 | this[device.name] = device; 368 | device.start.call(device, callback); 369 | device.started = true; 370 | 371 | return device.started; 372 | }; 373 | 374 | /** 375 | * Halts the Robot, attempting to gracefully stop devices and connections. 376 | * 377 | * @param {Function} callback to be triggered when the Robot has stopped 378 | * @return {void} 379 | */ 380 | Robot.prototype.halt = function(callback) { 381 | callback = callback || function() {}; 382 | 383 | if (!this.running) { 384 | return callback(); 385 | } 386 | 387 | // ensures callback(err) won't prevent others from halting 388 | function wrap(fn) { 389 | return function(cb) { fn.call(null, cb.bind(null, null)); }; 390 | } 391 | 392 | var devices = _.pluck(this.devices, "halt").map(wrap), 393 | connections = _.pluck(this.connections, "disconnect").map(wrap); 394 | 395 | try { 396 | _.parallel(devices, function() { 397 | _.parallel(connections, callback); 398 | }); 399 | } catch (e) { 400 | var msg = "An error occured while attempting to safely halt the robot"; 401 | this.log(msg); 402 | this.log(e.message); 403 | } 404 | 405 | this.running = false; 406 | }; 407 | 408 | /** 409 | * Generates a String representation of a Robot 410 | * 411 | * @return {String} representation of a Robot 412 | */ 413 | Robot.prototype.toString = function() { 414 | return "[Robot name='" + this.name + "']"; 415 | }; 416 | 417 | Robot.prototype.log = function(str) { 418 | Logger.log("[" + this.name + "] - " + str); 419 | }; 420 | -------------------------------------------------------------------------------- /lib/test/loopback.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Adaptor = require("../adaptor"), 4 | Utils = require("../utils"); 5 | 6 | var Loopback = module.exports = function Loopback() { 7 | Loopback.__super__.constructor.apply(this, arguments); 8 | }; 9 | 10 | Utils.subclass(Loopback, Adaptor); 11 | 12 | Loopback.prototype.connect = function(callback) { 13 | callback(); 14 | }; 15 | 16 | Loopback.prototype.disconnect = function(callback) { 17 | callback(); 18 | }; 19 | 20 | Loopback.adaptors = ["loopback"]; 21 | Loopback.adaptor = function(opts) { return new Loopback(opts); }; 22 | -------------------------------------------------------------------------------- /lib/test/ping.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Driver = require("../driver"), 4 | Utils = require("../utils"); 5 | 6 | var Ping = module.exports = function Ping() { 7 | Ping.__super__.constructor.apply(this, arguments); 8 | 9 | this.commands = { 10 | ping: this.ping 11 | }; 12 | 13 | this.events = ["ping"]; 14 | }; 15 | 16 | Utils.subclass(Ping, Driver); 17 | 18 | Ping.prototype.ping = function() { 19 | this.emit("ping", "ping"); 20 | return "pong"; 21 | }; 22 | 23 | Ping.prototype.start = function(callback) { 24 | callback(); 25 | }; 26 | 27 | Ping.prototype.halt = function(callback) { 28 | callback(); 29 | }; 30 | 31 | Ping.drivers = ["ping"]; 32 | Ping.driver = function(opts) { return new Ping(opts); }; 33 | -------------------------------------------------------------------------------- /lib/test/test-adaptor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Adaptor = require("../adaptor"), 4 | Utils = require("../utils"); 5 | 6 | var TestAdaptor = module.exports = function TestAdaptor() { 7 | TestAdaptor.__super__.constructor.apply(this, arguments); 8 | }; 9 | 10 | Utils.subclass(TestAdaptor, Adaptor); 11 | 12 | 13 | TestAdaptor.prototype.connect = function(callback) { 14 | callback(); 15 | }; 16 | 17 | TestAdaptor.prototype.disconnect = function(callback) { 18 | callback(); 19 | }; 20 | 21 | TestAdaptor.adaptors = ["test"]; 22 | TestAdaptor.adaptor = function(opts) { return new TestAdaptor(opts); }; 23 | -------------------------------------------------------------------------------- /lib/test/test-driver.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Driver = require("../driver"), 4 | Utils = require("../utils"); 5 | 6 | var TestDriver = module.exports = function TestDriver() { 7 | TestDriver.__super__.constructor.apply(this, arguments); 8 | }; 9 | 10 | Utils.subclass(TestDriver, Driver); 11 | 12 | TestDriver.prototype.start = function(callback) { 13 | callback(); 14 | }; 15 | 16 | TestDriver.prototype.halt = function(callback) { 17 | callback(); 18 | }; 19 | 20 | TestDriver.drivers = ["test"]; 21 | TestDriver.driver = function(opts) { return new TestDriver(opts); }; 22 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _ = require("./utils/helpers"); 4 | 5 | var monkeyPatches = require("./utils/monkey-patches"); 6 | 7 | var Utils = module.exports = { 8 | /** 9 | * A wrapper around setInterval to provide a more english-like syntax 10 | * (e.g. "every 5 seconds, do this thing") 11 | * 12 | * @param {Number} interval delay between action invocations 13 | * @param {Function} action function to trigger every time interval elapses 14 | * @example every((5).seconds(), function() {}); 15 | * @return {intervalID} setInterval ID to pass to clearInterval() 16 | */ 17 | every: function every(interval, action) { 18 | return setInterval(action, interval); 19 | }, 20 | 21 | /** 22 | * A wrapper around setInterval to provide a more english-like syntax 23 | * (e.g. "after 5 seconds, do this thing") 24 | * 25 | * @param {Number} delay how long to wait 26 | * @param {Function} action action to perform after delay 27 | * @example after((5).seconds(), function() {}); 28 | * @return {timeoutId} setTimeout ID to pass to clearTimeout() 29 | */ 30 | after: function after(delay, action) { 31 | return setTimeout(action, delay); 32 | }, 33 | 34 | /** 35 | * A wrapper around setInterval, with a delay of 0ms 36 | * 37 | * @param {Function} action function to invoke constantly 38 | * @example constantly(function() {}); 39 | * @return {intervalID} setInterval ID to pass to clearInterval() 40 | */ 41 | constantly: function constantly(action) { 42 | return every(0, action); 43 | }, 44 | 45 | /** 46 | * A wrapper around clearInterval 47 | * 48 | * @param {intervalID} intervalID ID of every/after/constantly 49 | * @example finish(blinking); 50 | * @return {void} 51 | */ 52 | finish: function finish(intervalID) { 53 | clearInterval(intervalID); 54 | }, 55 | 56 | /** 57 | * Sleeps the program for a period of time. 58 | * 59 | * Use of this is not advised, as your program can't do anything else while 60 | * it's running. 61 | * 62 | * @param {Number} ms number of milliseconds to sleep 63 | * @return {void} 64 | */ 65 | sleep: function sleep(ms) { 66 | var start = Date.now(), 67 | i = 0; 68 | 69 | while (Date.now() < start + ms) { 70 | i = i.toString(); 71 | } 72 | }, 73 | 74 | /** 75 | * Utility for providing class inheritance. 76 | * 77 | * Based on CoffeeScript's implementation of inheritance. 78 | * 79 | * Parent class methods/properites are available on Child.__super__. 80 | * 81 | * @param {Function} child the descendent class 82 | * @param {Function} parent the parent class 83 | * @return {Function} the child class 84 | */ 85 | subclass: function subclass(child, parent) { 86 | var Ctor = function() { 87 | this.constructor = child; 88 | }; 89 | 90 | for (var key in parent) { 91 | if (parent.hasOwnProperty(key)) { 92 | child[key] = parent[key]; 93 | } 94 | } 95 | 96 | Ctor.prototype = parent.prototype; 97 | child.prototype = new Ctor(); 98 | child.__super__ = parent.prototype; 99 | return child; 100 | }, 101 | 102 | proxyFunctions: function proxyFunctions(source, target) { 103 | _.each(source, function(prop, key) { 104 | if (_.isFunction(prop) && !target[key]) { 105 | target[key] = prop.bind(source); 106 | } 107 | }); 108 | }, 109 | 110 | /** 111 | * Proxies calls from all methods in the source to a target object 112 | * 113 | * @param {String[]} methods methods to proxy 114 | * @param {Object} target object to proxy methods to 115 | * @param {Object} [base=this] object to proxy methods from 116 | * @param {Boolean} [force=false] whether to overwrite existing methods 117 | * @return {Object} the base 118 | */ 119 | proxyFunctionsToObject: function(methods, target, base, force) { 120 | if (base == null) { 121 | base = this; 122 | } 123 | 124 | force = force || false; 125 | 126 | methods.forEach(function(method) { 127 | if (_.isFunction(base[method]) && !force) { 128 | return; 129 | } 130 | 131 | base[method] = function() { 132 | return target[method].apply(target, arguments); 133 | }; 134 | }); 135 | 136 | return base; 137 | }, 138 | 139 | classCallCheck: function(instance, Constructor) { 140 | if (!(instance instanceof Constructor)) { 141 | throw new TypeError("Cannot call a class as a function"); 142 | } 143 | }, 144 | 145 | /** 146 | * Approximation of Ruby's Hash#fetch method for object property lookup 147 | * 148 | * @param {Object} obj object to do lookup on 149 | * @param {String} property property name to attempt to access 150 | * @param {*} fallback a fallback value if property can't be found. if 151 | * a function, will be invoked with the string property name. 152 | * @throws Error if fallback needed but not provided, or fallback fn doesn't 153 | * return anything 154 | * @example 155 | * fetch({ property: "hello world" }, "property"); //=> "hello world" 156 | * @example 157 | * fetch({}, "notaproperty", "default value"); //=> "default value" 158 | * @example 159 | * var notFound = function(prop) { return prop + " not found!" }; 160 | * fetch({}, "notaproperty", notFound); //=> "notaproperty not found!" 161 | * @example 162 | * var badFallback = function(prop) { prop + " not found!" }; 163 | * fetch({}, "notaproperty", badFallback); 164 | * // Error: no return value from provided callback function 165 | * @example 166 | * fetch(object, "notaproperty"); 167 | * // Error: key not found: "notaproperty" 168 | * @return {*} fetched property, fallback, or fallback function return value 169 | */ 170 | fetch: function(obj, property, fallback) { 171 | if (obj.hasOwnProperty(property)) { 172 | return obj[property]; 173 | } 174 | 175 | if (fallback === void 0) { 176 | throw new Error("key not found: \"" + property + "\""); 177 | } 178 | 179 | if (_.isFunction(fallback)) { 180 | var value = fallback(property); 181 | 182 | if (value === void 0) { 183 | throw new Error("no return value from provided fallback function"); 184 | } 185 | 186 | return value; 187 | } 188 | 189 | return fallback; 190 | }, 191 | 192 | /** 193 | * Given a name, and an array of existing names, returns a unique new name 194 | * 195 | * @param {String} name the name that's colliding with existing names 196 | * @param {String[]} arr array of existing names 197 | * @example 198 | * makeUnique("hello", ["hello", "hello-1", "hello-2"]); //=> "hello3" 199 | * @return {String} the new name 200 | */ 201 | makeUnique: function(name, arr) { 202 | var newName; 203 | 204 | if (!~arr.indexOf(name)) { 205 | return name; 206 | } 207 | 208 | for (var n = 1; ; n++) { 209 | newName = name + "-" + n; 210 | if (!~arr.indexOf(newName)) { 211 | return newName; 212 | } 213 | } 214 | }, 215 | 216 | /** 217 | * Adds every/after/constantly to the global namespace, and installs 218 | * monkey-patches. 219 | * 220 | * @return {Object} utils object 221 | */ 222 | bootstrap: function bootstrap() { 223 | global.every = this.every; 224 | global.after = this.after; 225 | global.constantly = this.constantly; 226 | 227 | monkeyPatches.install(); 228 | 229 | return this; 230 | } 231 | }; 232 | 233 | Utils.bootstrap(); 234 | -------------------------------------------------------------------------------- /lib/utils/helpers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* eslint no-use-before-define: 0 */ 4 | 5 | var __slice = Array.prototype.slice; 6 | 7 | var H = module.exports = {}; 8 | 9 | function identity(value) { 10 | return value; 11 | } 12 | 13 | function extend(base, source) { 14 | var isArray = Array.isArray(source); 15 | 16 | if (base == null) { 17 | base = isArray ? [] : {}; 18 | } 19 | 20 | if (isArray) { 21 | source.forEach(function(e, i) { 22 | if (typeof base[i] === "undefined") { 23 | base[i] = e; 24 | } else if (typeof e === "object") { 25 | base[i] = extend(base[i], e); 26 | } else if (!~base.indexOf(e)) { 27 | base.push(e); 28 | } 29 | }); 30 | } else { 31 | var key; 32 | 33 | for (key in source) { 34 | if (typeof source[key] !== "object" || !source[key]) { 35 | base[key] = source[key]; 36 | } else if (base[key]) { 37 | extend(base[key], source[key]); 38 | } else { 39 | base[key] = source[key]; 40 | } 41 | } 42 | } 43 | 44 | return base; 45 | } 46 | 47 | extend(H, { 48 | identity: identity, 49 | extend: extend 50 | }); 51 | 52 | function kind(thing) { 53 | return Object.prototype.toString.call(thing).slice(8, -1); 54 | } 55 | 56 | function isA(type) { 57 | return function(thing) { 58 | return kind(thing) === type; 59 | }; 60 | } 61 | 62 | extend(H, { 63 | isObject: isA("Object"), 64 | isObjectLoose: function(thing) { return typeof thing === "object"; }, 65 | isFunction: isA("Function"), 66 | isArray: isA("Array"), 67 | isString: isA("String"), 68 | isNumber: isA("Number"), 69 | isArguments: isA("Arguments"), 70 | isUndefined: isA("Undefined") 71 | }); 72 | 73 | function iterate(thing, fn, thisVal) { 74 | if (H.isArray(thing)) { 75 | thing.forEach(fn, thisVal); 76 | return; 77 | } 78 | 79 | if (H.isObject(thing)) { 80 | for (var key in thing) { 81 | var value = thing[key]; 82 | fn.call(thisVal, value, key); 83 | } 84 | } 85 | } 86 | 87 | function pluck(collection, key) { 88 | var keys = []; 89 | 90 | iterate(collection, function(object) { 91 | if (H.isObject(object)) { 92 | if (H.isFunction(object[key])) { 93 | keys.push(object[key].bind(object)); 94 | } else { 95 | keys.push(object[key]); 96 | } 97 | } 98 | }); 99 | 100 | return keys; 101 | } 102 | 103 | function map(collection, fn, thisVal) { 104 | var vals = []; 105 | 106 | if (fn == null) { 107 | fn = identity; 108 | } 109 | 110 | iterate(collection, function(object, index) { 111 | vals.push(fn.call(thisVal, object, index)); 112 | }); 113 | 114 | return vals; 115 | } 116 | 117 | function invoke(collection, fn) { 118 | var args = __slice.call(arguments, 2), 119 | vals = []; 120 | 121 | iterate(collection, function(object) { 122 | if (H.isFunction(fn)) { 123 | vals.push(fn.apply(object, args)); 124 | return; 125 | } 126 | 127 | vals.push(object[fn].apply(object, arguments)); 128 | }); 129 | 130 | return vals; 131 | } 132 | 133 | function reduce(collection, iteratee, accumulator, thisVal) { 134 | var isArray = H.isArray(collection); 135 | 136 | if (!isArray && !H.isObjectLoose(collection)) { 137 | return null; 138 | } 139 | 140 | if (iteratee == null) { 141 | iteratee = identity; 142 | } 143 | 144 | if (accumulator == null) { 145 | if (isArray) { 146 | accumulator = collection.shift(); 147 | } else { 148 | for (var key in collection) { 149 | accumulator = collection[key]; 150 | delete collection[key]; 151 | break; 152 | } 153 | } 154 | } 155 | 156 | iterate(collection, function(object, name) { 157 | accumulator = iteratee.call(thisVal, accumulator, object, name); 158 | }); 159 | 160 | return accumulator; 161 | } 162 | 163 | extend(H, { 164 | pluck: pluck, 165 | each: iterate, 166 | map: map, 167 | invoke: invoke, 168 | reduce: reduce 169 | }); 170 | 171 | function arity(fn, n) { 172 | return function() { 173 | var args = __slice.call(arguments, 0, n); 174 | return fn.apply(null, args); 175 | }; 176 | } 177 | 178 | function partial(fn) { 179 | var args = __slice.call(arguments, 1); 180 | 181 | return function() { 182 | return fn.apply(null, args.concat(__slice.call(arguments))); 183 | }; 184 | } 185 | 186 | function partialRight(fn) { 187 | var args = __slice.call(arguments, 1); 188 | 189 | return function() { 190 | return fn.apply(null, __slice.call(arguments).concat(args)); 191 | }; 192 | } 193 | 194 | extend(H, { 195 | arity: arity, 196 | partial: partial, 197 | partialRight: partialRight 198 | }); 199 | 200 | function includes(arr, value) { 201 | return !!~arr.indexOf(value); 202 | } 203 | 204 | extend(H, { 205 | includes: includes 206 | }); 207 | 208 | function parallel(functions, done) { 209 | var total = functions.length, 210 | completed = 0, 211 | results = [], 212 | error; 213 | 214 | if (typeof done !== "function") { done = function() {}; } 215 | 216 | function callback(err, result) { 217 | if (error) { 218 | return; 219 | } 220 | 221 | if (err || error) { 222 | error = err; 223 | done(err); 224 | return; 225 | } 226 | 227 | completed++; 228 | results.push(result); 229 | 230 | if (completed === total) { 231 | done(null, results); 232 | } 233 | } 234 | 235 | if (!functions.length) { done(); } 236 | 237 | functions.forEach(function(fn) { fn(callback); }); 238 | } 239 | 240 | extend(H, { 241 | parallel: parallel 242 | }); 243 | 244 | function series(functions, done) { 245 | var results = [], 246 | error; 247 | 248 | if (typeof done !== "function") { done = function() {}; } 249 | 250 | function callback(err, result) { 251 | if (err || error) { 252 | error = err; 253 | return done(err); 254 | } 255 | 256 | results.push(result); 257 | 258 | if (!functions.length) { 259 | return done(null, results); 260 | } 261 | 262 | next(); 263 | } 264 | 265 | function next() { 266 | functions.shift()(callback); 267 | } 268 | 269 | if (!functions.length) { done(null, results); } 270 | next(); 271 | } 272 | 273 | extend(H, { 274 | series: series 275 | }); 276 | -------------------------------------------------------------------------------- /lib/utils/monkey-patches.js: -------------------------------------------------------------------------------- 1 | /* eslint no-extend-native: 0 key-spacing: 0 */ 2 | 3 | "use strict"; 4 | 5 | var max = Math.max, 6 | min = Math.min; 7 | 8 | var originals = { 9 | seconds: Number.prototype.seconds, 10 | second: Number.prototype.second, 11 | fromScale: Number.prototype.fromScale, 12 | toScale: Number.prototype.toScale 13 | }; 14 | 15 | module.exports.uninstall = function() { 16 | for (var opt in originals) { 17 | if (originals[opt] == null) { 18 | Number.prototype[opt] = originals[opt]; 19 | } else { 20 | delete Number.prototype[opt]; 21 | } 22 | } 23 | }; 24 | 25 | module.exports.install = function() { 26 | /** 27 | * Multiplies a number by 60000 to convert minutes 28 | * to milliseconds 29 | * 30 | * @example 31 | * (2).minutes(); //=> 120000 32 | * @return {Number} time in milliseconds 33 | */ 34 | Number.prototype.minutes = function() { 35 | return this * 60000; 36 | }; 37 | 38 | /** 39 | * Alias for Number.prototype.minutes 40 | * 41 | * @see Number.prototype.minute 42 | * @example 43 | * (1).minute(); //=>60000 44 | * @return {Number} time in milliseconds 45 | */ 46 | Number.prototype.minute = Number.prototype.minutes; 47 | 48 | /** 49 | * Multiplies a number by 1000 to convert seconds 50 | * to milliseconds 51 | * 52 | * @example 53 | * (2).seconds(); //=> 2000 54 | * @return {Number} time in milliseconds 55 | */ 56 | Number.prototype.seconds = function() { 57 | return this * 1000; 58 | }; 59 | 60 | /** 61 | * Alias for Number.prototype.seconds 62 | * 63 | * @see Number.prototype.seconds 64 | * @example 65 | * (1).second(); //=> 1000 66 | * @return {Number} time in milliseconds 67 | */ 68 | Number.prototype.second = Number.prototype.seconds; 69 | 70 | /** 71 | * Passthru to get time in milliseconds 72 | * 73 | * @example 74 | * (200).milliseconds(); //=> 200 75 | * @return {Number} time in milliseconds 76 | */ 77 | Number.prototype.milliseconds = function() { 78 | return this; 79 | }; 80 | 81 | /** 82 | * Alias for Number.prototype.milliseconds 83 | * 84 | * @see Number.prototype.milliseconds 85 | * @example 86 | * (100).ms(); //=> 100 87 | * @return {Number} time in milliseconds 88 | */ 89 | Number.prototype.ms = Number.prototype.milliseconds; 90 | 91 | /** 92 | * Converts microseconds to milliseconds. 93 | * Note that timing of events in terms of microseconds 94 | * is not very accurate in JS. 95 | * 96 | * @example 97 | * (2000).microseconds(); //=> 2 98 | * @return {Number} time in milliseconds 99 | */ 100 | Number.prototype.microseconds = function() { 101 | return this / 1000; 102 | }; 103 | 104 | /** 105 | * Converts a number from a current scale to a 0 - 1 scale. 106 | * 107 | * @param {Number} start low point of scale to convert from 108 | * @param {Number} end high point of scale to convert from 109 | * @example 110 | * (5).fromScale(0, 10) //=> 0.5 111 | * @return {Number} the scaled value 112 | */ 113 | Number.prototype.fromScale = function(start, end) { 114 | var val = (this - min(start, end)) / (max(start, end) - min(start, end)); 115 | 116 | if (val > 1) { 117 | return 1; 118 | } 119 | 120 | if (val < 0) { 121 | return 0; 122 | } 123 | 124 | return val; 125 | }; 126 | 127 | /** 128 | * Converts a number from a 0 - 1 scale to the specified scale. 129 | * 130 | * @param {Number} start low point of scale to convert to 131 | * @param {Number} end high point of scale to convert to 132 | * @example 133 | * (0.5).toScale(0, 10) //=> 5 134 | * @return {Number} the scaled value 135 | */ 136 | Number.prototype.toScale = function(start, end) { 137 | var i = this * (max(start, end) - min(start, end)) + min(start, end); 138 | 139 | if (i < start) { 140 | return start; 141 | } 142 | 143 | if (i > end) { 144 | return end; 145 | } 146 | 147 | return i; 148 | }; 149 | }; 150 | -------------------------------------------------------------------------------- /lib/validator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // validates an Object containing Robot parameters 4 | 5 | var Logger = require("./logger"), 6 | _ = require("./utils/helpers"); 7 | 8 | function hasProp(object, prop) { 9 | return object.hasOwnProperty(prop); 10 | } 11 | 12 | function die() { 13 | var RobotDSLError = new Error("Unable to start robot due to a syntax error"); 14 | RobotDSLError.name = "RobotDSLError"; 15 | throw RobotDSLError; 16 | } 17 | 18 | function warn(messages) { 19 | messages = [].concat(messages); 20 | messages.map(function(msg) { Logger.log(msg); }); 21 | } 22 | 23 | function fatal(messages) { 24 | messages = [].concat(messages); 25 | messages.map(function(msg) { Logger.log(msg); }); 26 | die(); 27 | } 28 | 29 | var checks = {}; 30 | 31 | checks.singleObjectSyntax = function(opts, key) { 32 | var single = hasProp(opts, key), 33 | plural = hasProp(opts, key + "s"); 34 | 35 | if (single && !plural) { 36 | fatal([ 37 | "The single-object '" + key + "' syntax for robots is not valid.", 38 | "Instead, use the multiple-value '" + key + "s' key syntax.", 39 | "Details: http://cylonjs.com/documentation/guides/working-with-robots/" 40 | ]); 41 | } 42 | }; 43 | 44 | checks.singleObjectSyntax = function(opts) { 45 | ["connection", "device"].map(function(key) { 46 | var single = hasProp(opts, key), 47 | plural = hasProp(opts, key + "s"); 48 | 49 | if (single && !plural) { 50 | fatal([ 51 | "The single-object '" + key + "' syntax for robots is not valid.", 52 | "Instead, use the multiple-value '" + key + "s' key syntax.", 53 | "Details: http://cylonjs.com/documentation/guides/working-with-robots/" 54 | ]); 55 | } 56 | }); 57 | }; 58 | 59 | checks.deviceWithoutDriver = function(opts) { 60 | if (opts.devices) { 61 | _.each(opts.devices, function(device, name) { 62 | if (!device.driver || device.driver === "") { 63 | fatal("No driver supplied for device " + name); 64 | } 65 | }); 66 | } 67 | }; 68 | 69 | checks.devicesWithoutConnection = function(opts) { 70 | var connections = opts.connections, 71 | devices = opts.devices; 72 | 73 | if (devices && connections && Object.keys(connections).length > 1) { 74 | var first = Object.keys(connections)[0]; 75 | 76 | _.each(devices, function(device, name) { 77 | if (!device.connection || device.connection === "") { 78 | warn([ 79 | "No explicit connection provided for device " + name, 80 | "Will default to using connection " + first 81 | ]); 82 | } 83 | }); 84 | } 85 | }; 86 | 87 | checks.noConnections = function(opts) { 88 | var connections = Object.keys(opts.connections || {}).length, 89 | devices = Object.keys(opts.devices || {}).length; 90 | 91 | if (devices && !connections) { 92 | fatal(["No connections provided for devices"]); 93 | } 94 | }; 95 | 96 | module.exports.validate = function validate(opts) { 97 | opts = opts || {}; 98 | 99 | _.each(checks, function(check) { 100 | check(opts); 101 | }); 102 | }; 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cylon", 3 | "version": "1.3.0", 4 | "description": "JavaScript framework for robotics, drones, and the Internet of Things (IoT) using Node.js", 5 | "homepage": "http://cylonjs.com", 6 | "bugs": "https://github.com/hybridgroup/cylon/issues", 7 | 8 | "author": "The Hybrid Group ", 9 | 10 | "contributors": [ 11 | "Contributors List (https://github.com/hybridgroup/cylon/blob/master/CONTRIBUTORS.markdown)" 12 | ], 13 | 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/hybridgroup/cylon" 17 | }, 18 | 19 | "license": "Apache-2.0", 20 | 21 | "keywords": [ 22 | "cylon", 23 | "cylonjs", 24 | "cylons", 25 | "robot", 26 | "robots", 27 | "robotics", 28 | "iot", 29 | "hardware", 30 | "drones", 31 | "internet of things" 32 | ], 33 | 34 | "hardware": { 35 | "*": false, 36 | "./": false, 37 | "./lib": true, 38 | "index.js": true 39 | }, 40 | 41 | "engines" : { 42 | "node" : ">= 0.10.20" 43 | }, 44 | 45 | "devDependencies": { 46 | "sinon-chai": "2.7.0", 47 | "chai": "2.2.0", 48 | "mocha": "2.2.4", 49 | "sinon": "1.14.1", 50 | "eslint": "0.22.1" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /spec/.eslintrc: -------------------------------------------------------------------------------- 1 | env: 2 | mocha: true 3 | 4 | globals: 5 | expect: true 6 | lib: true 7 | stub: true 8 | spy: true 9 | chai: true 10 | sinon: true 11 | 12 | rules: 13 | no-unused-expressions: 0 14 | max-nested-callbacks: 0 15 | camelcase: 0 16 | -------------------------------------------------------------------------------- /spec/helper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | process.env.NODE_ENV = "test"; 4 | 5 | var path = require("path"); 6 | 7 | var chai = require("chai"), 8 | sinon = require("sinon"), 9 | sinonChai = require("sinon-chai"); 10 | 11 | chai.use(sinonChai); 12 | 13 | global.chai = chai; 14 | global.sinon = sinon; 15 | 16 | global.should = chai.should(); 17 | global.expect = chai.expect; 18 | global.assert = chai.assert; 19 | global.AssertionError = chai.AssertionError; 20 | 21 | global.spy = sinon.spy; 22 | global.stub = sinon.stub; 23 | 24 | // convenience function to require modules in lib directory 25 | global.lib = function(module) { 26 | return require(path.normalize("./../lib/" + module)); 27 | }; 28 | 29 | var Cylon = require("./../"); 30 | 31 | Cylon.config({ 32 | mode: "manual", 33 | silent: true 34 | }); 35 | 36 | Cylon.Logger.setup(); 37 | -------------------------------------------------------------------------------- /spec/lib/adaptor.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Adaptor = lib("adaptor"), 4 | Utils = lib("utils"); 5 | 6 | describe("Adaptor", function() { 7 | var adaptor; 8 | 9 | beforeEach(function() { 10 | adaptor = new Adaptor({ name: "adaptor" }); 11 | }); 12 | 13 | describe("#constructor", function() { 14 | it("sets @name to the provided name", function() { 15 | expect(adaptor.name).to.be.eql("adaptor"); 16 | }); 17 | }); 18 | 19 | describe("#interface methods", function() { 20 | var child; 21 | 22 | var Child = function Child() {}; 23 | 24 | Utils.subclass(Child, Adaptor); 25 | 26 | beforeEach(function() { 27 | child = new Child(); 28 | }); 29 | 30 | describe("#connect", function() { 31 | it("throws an error unless overwritten", function() { 32 | expect(child.connect).to.throw(); 33 | child.connect = function() {}; 34 | expect(child.connect).to.not.throw(); 35 | }); 36 | }); 37 | 38 | describe("#disconnect", function() { 39 | it("throws an error unless overwritten", function() { 40 | expect(child.disconnect).to.throw(); 41 | child.disconnect = function() {}; 42 | expect(child.disconnect).to.not.throw(); 43 | }); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /spec/lib/api.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var API = lib("api"), 4 | MCP = lib("mcp"); 5 | 6 | describe("API", function() { 7 | describe("#create", function() { 8 | afterEach(function() { 9 | API.instances = []; 10 | }); 11 | 12 | context("with a provided API server and opts", function() { 13 | var Server, opts, instance; 14 | 15 | beforeEach(function() { 16 | instance = { start: spy() }; 17 | opts = { https: false }; 18 | Server = stub().returns(instance); 19 | 20 | API.create(Server, opts); 21 | }); 22 | 23 | it("creates an API instance", function() { 24 | expect(Server).to.be.calledWithNew; 25 | expect(Server).to.be.calledWith(opts); 26 | }); 27 | 28 | it("passes MCP through to the instance as opts.mcp", function() { 29 | expect(opts.mcp).to.be.eql(MCP); 30 | }); 31 | 32 | it("stores the API instance in @instances", function() { 33 | expect(API.instances).to.be.eql([instance]); 34 | }); 35 | 36 | it("tells the API instance to start", function() { 37 | expect(instance.start).to.be.called; 38 | }); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /spec/lib/basestar.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Basestar = lib("basestar"), 4 | Utils = lib("utils"); 5 | 6 | var EventEmitter = require("events").EventEmitter; 7 | 8 | describe("Basestar", function() { 9 | describe("#proxyMethods", function() { 10 | var methods = ["asString", "toString", "returnString"]; 11 | 12 | var ProxyClass = function ProxyClass() {}; 13 | 14 | ProxyClass.prototype.asString = function() { 15 | return "[object ProxyClass]"; 16 | }; 17 | 18 | ProxyClass.prototype.toString = function() { 19 | return "[object ProxyClass]"; 20 | }; 21 | 22 | ProxyClass.prototype.returnString = function(string) { 23 | return string; 24 | }; 25 | 26 | var TestClass = function TestClass() { 27 | this.testInstance = new ProxyClass(); 28 | this.proxyMethods(methods, this.testInstance, this, true); 29 | }; 30 | 31 | Utils.subclass(TestClass, Basestar); 32 | 33 | it("can alias methods", function() { 34 | var testclass = new TestClass(); 35 | expect(testclass.asString).to.be.a("function"); 36 | expect(testclass.asString()).to.be.equal("[object ProxyClass]"); 37 | }); 38 | 39 | it("can alias existing methods if forced to", function() { 40 | var testclass = new TestClass(); 41 | expect(testclass.toString).to.be.a("function"); 42 | expect(testclass.toString()).to.be.equal("[object ProxyClass]"); 43 | }); 44 | 45 | it("can alias methods with arguments", function() { 46 | var testclass = new TestClass(); 47 | expect(testclass.returnString).to.be.a("function"); 48 | expect(testclass.returnString("testString")).to.be.equal("testString"); 49 | }); 50 | }); 51 | 52 | describe("#respond", function() { 53 | var listener, callback, child; 54 | var Child = function Child() {}; 55 | 56 | Utils.subclass(Child, Basestar); 57 | 58 | beforeEach(function() { 59 | child = new Child(); 60 | 61 | listener = spy(); 62 | callback = spy(); 63 | 64 | child.on("event", listener); 65 | 66 | child.respond("event", callback, null, "arg1", 2, { three: true }); 67 | }); 68 | 69 | it("triggers the callback with all additional arguments", function() { 70 | expect(callback).to.be.calledWith(null, "arg1", 2, { three: true }); 71 | }); 72 | 73 | it("emits an event with all additional arguments", function() { 74 | expect(listener).to.be.calledWith("arg1", 2, { three: true }); 75 | }); 76 | 77 | context("when err is not null", function() { 78 | var errListener; 79 | 80 | beforeEach(function() { 81 | errListener = spy(); 82 | child = new Child(); 83 | child.on("error", errListener); 84 | child.respond( 85 | "event", 86 | callback, 87 | "Error on event!", 88 | "arg1", 89 | 2, 90 | { three: true }); 91 | }); 92 | 93 | it("emits an error event", function() { 94 | expect(errListener).to.be.calledWith("Error on event!"); 95 | }); 96 | }); 97 | }); 98 | 99 | describe("#defineEvent", function() { 100 | var ProxyClass = function ProxyClass() {}; 101 | 102 | var EmitterClass = function EmitterClass(update) { 103 | update || (update = false); 104 | this.proxy = new ProxyClass(); 105 | this.defineEvent({ 106 | eventName: "testevent", 107 | source: this, 108 | target: this.proxy, 109 | sendUpdate: update 110 | }); 111 | }; 112 | 113 | Utils.subclass(ProxyClass, Basestar); 114 | Utils.subclass(EmitterClass, Basestar); 115 | 116 | it("proxies events from one class to another", function() { 117 | var eventSpy = spy(), 118 | testclass = new EmitterClass(), 119 | proxy = testclass.proxy; 120 | 121 | proxy.on("testevent", eventSpy); 122 | testclass.emit("testevent", "data"); 123 | 124 | expect(eventSpy).to.be.calledWith("data"); 125 | }); 126 | 127 | it("emits an 'update' event if told to", function() { 128 | var updateSpy = spy(), 129 | testclass = new EmitterClass(true), 130 | proxy = testclass.proxy; 131 | 132 | proxy.on("update", updateSpy); 133 | testclass.emit("testevent", "data"); 134 | 135 | expect(updateSpy).to.be.calledWith("testevent", "data"); 136 | }); 137 | 138 | it("does not emit an 'update' event by default", function() { 139 | var updateSpy = spy(), 140 | testclass = new EmitterClass(), 141 | proxy = testclass.proxy; 142 | 143 | proxy.on("update", updateSpy); 144 | testclass.emit("testevent", "data"); 145 | 146 | expect(updateSpy).to.not.be.calledWith("testevent", "data"); 147 | }); 148 | }); 149 | 150 | describe("#defineAdaptorEvent", function() { 151 | var basestar; 152 | 153 | beforeEach(function() { 154 | basestar = new Basestar(); 155 | basestar.connector = new EventEmitter(); 156 | }); 157 | 158 | it("proxies events between the connector and connection", function() { 159 | var eventSpy = spy(); 160 | 161 | basestar.on("testevent", eventSpy); 162 | basestar.defineAdaptorEvent({ eventName: "testevent" }); 163 | 164 | basestar.connector.emit("testevent", "data"); 165 | expect(eventSpy).to.be.calledWith("data"); 166 | }); 167 | 168 | context("when given a string", function() { 169 | it("uses it as the eventName", function() { 170 | var eventSpy = spy(); 171 | 172 | basestar.on("testevent", eventSpy); 173 | basestar.defineAdaptorEvent("testevent"); 174 | 175 | basestar.connector.emit("testevent", "data"); 176 | expect(eventSpy).to.be.calledWith("data"); 177 | }); 178 | }); 179 | }); 180 | 181 | describe("#defineDriverEvent", function() { 182 | var basestar; 183 | 184 | beforeEach(function() { 185 | basestar = new Basestar(); 186 | basestar.connection = new EventEmitter(); 187 | }); 188 | 189 | it("proxies events between the connection and device", function() { 190 | var eventSpy = spy(); 191 | 192 | basestar.on("testevent", eventSpy); 193 | basestar.defineDriverEvent({ eventName: "testevent" }); 194 | 195 | basestar.connection.emit("testevent", "data"); 196 | expect(eventSpy).to.be.calledWith("data"); 197 | }); 198 | 199 | context("when given a string", function() { 200 | it("uses it as the eventName", function() { 201 | var eventSpy = spy(); 202 | 203 | basestar.on("testevent", eventSpy); 204 | basestar.defineDriverEvent("testevent"); 205 | 206 | basestar.connection.emit("testevent", "data"); 207 | expect(eventSpy).to.be.calledWith("data"); 208 | }); 209 | }); 210 | }); 211 | }); 212 | -------------------------------------------------------------------------------- /spec/lib/config.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var config = lib("config"); 4 | 5 | describe("config", function() { 6 | it("contains configuration options", function() { 7 | expect(config.testMode).to.be.eql(false); 8 | }); 9 | 10 | describe("#update", function() { 11 | var callback; 12 | 13 | beforeEach(function() { 14 | callback = spy(); 15 | config.subscribe(callback); 16 | }); 17 | 18 | afterEach(function() { 19 | config.unsubscribe(callback); 20 | delete config.newValue; 21 | }); 22 | 23 | it("updates the configuration", function() { 24 | expect(config.newValue).to.be.eql(undefined); 25 | config.update({ newValue: "value" }); 26 | expect(config.newValue).to.be.eql("value"); 27 | }); 28 | 29 | it("notifies subscribers of changes", function() { 30 | var update = { newValue: "value" }; 31 | expect(callback).to.not.be.called; 32 | config.update(update); 33 | expect(callback).to.be.calledWith(update); 34 | }); 35 | 36 | it("rejects changes that conflict with config functions", function() { 37 | config.update({ update: null }); 38 | expect(config.update).to.be.a("function"); 39 | }); 40 | 41 | it("does nothing with empty changesets", function() { 42 | config.update({}); 43 | expect(callback).to.not.be.called; 44 | }); 45 | }); 46 | 47 | describe("#subscribe", function() { 48 | var callback = spy(); 49 | 50 | afterEach(function() { 51 | delete config.test; 52 | config.unsubscribe(callback); 53 | }); 54 | 55 | it("subscribes a callback to change updates", function() { 56 | config.subscribe(callback); 57 | config.update({ test: true }); 58 | expect(callback).to.be.calledWith({ test: true }); 59 | }); 60 | }); 61 | 62 | describe("#unsubscribe", function() { 63 | var callback; 64 | 65 | beforeEach(function() { 66 | callback = spy(); 67 | config.subscribe(callback); 68 | }); 69 | 70 | afterEach(function() { 71 | delete config.test; 72 | }); 73 | 74 | it("unsubscribes a callback from change updates", function() { 75 | config.update({ test: true }); 76 | expect(callback).to.be.called; 77 | 78 | config.unsubscribe(callback); 79 | 80 | config.update({ test: false }); 81 | expect(callback).to.be.calledOnce; 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /spec/lib/cylon.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Cylon = lib("../index"); 4 | 5 | var MCP = lib("mcp"), 6 | API = lib("api"), 7 | Robot = lib("robot"), 8 | Driver = lib("driver"), 9 | Adaptor = lib("adaptor"), 10 | Utils = lib("utils"), 11 | Config = lib("config"), 12 | Logger = lib("logger"); 13 | 14 | var IO = { 15 | DigitalPin: lib("io/digital-pin"), 16 | Utils: lib("io/utils") 17 | }; 18 | 19 | describe("Cylon", function() { 20 | it("exports the MCP as Cylon.MCP", function() { 21 | expect(Cylon.MCP).to.be.eql(MCP); 22 | }); 23 | 24 | it("exports the Robot as Cylon.Robot", function() { 25 | expect(Cylon.Robot).to.be.eql(Robot); 26 | }); 27 | 28 | it("exports the Driver as Cylon.Driver", function() { 29 | expect(Cylon.Driver).to.be.eql(Driver); 30 | }); 31 | 32 | it("exports the Adaptor as Cylon.Adaptor", function() { 33 | expect(Cylon.Adaptor).to.be.eql(Adaptor); 34 | }); 35 | 36 | it("exports the Utils as Cylon.Utils", function() { 37 | expect(Cylon.Utils).to.be.eql(Utils); 38 | }); 39 | 40 | 41 | it("exports the Logger as Cylon.Logger", function() { 42 | expect(Cylon.Logger).to.be.eql(Logger); 43 | }); 44 | 45 | it("exports the IO DigitalPin and Utils as Cylon.IO", function() { 46 | expect(Cylon.IO).to.be.eql(IO); 47 | }); 48 | 49 | describe("#robot", function() { 50 | it("proxies to MCP.create", function() { 51 | expect(Cylon.robot).to.be.eql(MCP.create); 52 | }); 53 | }); 54 | 55 | describe("#start", function() { 56 | it("proxies to MCP.start", function() { 57 | expect(Cylon.start).to.be.eql(MCP.start); 58 | }); 59 | }); 60 | 61 | describe("#halt", function() { 62 | it("proxies to MCP.halt", function() { 63 | expect(Cylon.halt).to.be.eql(MCP.halt); 64 | }); 65 | }); 66 | 67 | describe("#api", function() { 68 | it("proxies to API.create", function() { 69 | expect(Cylon.api).to.be.eql(API.create); 70 | }); 71 | }); 72 | 73 | describe("#config", function() { 74 | it("proxies to Config.update", function() { 75 | expect(Cylon.config).to.be.eql(Config.update); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /spec/lib/driver.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Driver = lib("driver"), 4 | Utils = lib("utils"); 5 | 6 | describe("Driver", function() { 7 | var connection, driver; 8 | 9 | beforeEach(function() { 10 | connection = { 11 | connection: "connection" 12 | }; 13 | 14 | driver = new Driver({ 15 | name: "driver", 16 | connection: connection, 17 | }); 18 | }); 19 | 20 | describe("#constructor", function() { 21 | it("sets @name to the provided name", function() { 22 | expect(driver.name).to.be.eql("driver"); 23 | }); 24 | 25 | it("sets @connection to the provided connection", function() { 26 | expect(driver.connection).to.be.eql(connection); 27 | }); 28 | 29 | it("sets @commands to an empty object by default", function() { 30 | expect(driver.commands).to.be.eql({}); 31 | }); 32 | 33 | it("sets @events to an empty array by default", function() { 34 | expect(driver.events).to.be.eql([]); 35 | }); 36 | 37 | it("sets @interval to 10ms by default, or the provided value", function() { 38 | expect(driver.interval).to.be.eql(10); 39 | 40 | driver = new Driver({ 41 | name: "driver", 42 | connection: connection, 43 | interval: 2000, 44 | }); 45 | 46 | expect(driver.interval).to.be.eql(2000); 47 | }); 48 | }); 49 | 50 | describe("#interface methods", function() { 51 | var child; 52 | 53 | var Child = function Child() {}; 54 | 55 | Utils.subclass(Child, Driver); 56 | 57 | beforeEach(function() { 58 | child = new Child(); 59 | }); 60 | 61 | describe("#start", function() { 62 | it("throws an error unless overwritten", function() { 63 | expect(child.start).to.throw(); 64 | child.start = function() {}; 65 | expect(child.start).to.not.throw(); 66 | }); 67 | }); 68 | 69 | describe("#halt", function() { 70 | it("throws an error unless overwritten", function() { 71 | expect(child.halt).to.throw(); 72 | child.halt = function() {}; 73 | expect(child.halt).to.not.throw(); 74 | }); 75 | }); 76 | }); 77 | 78 | describe("#toJSON", function() { 79 | var json; 80 | 81 | beforeEach(function() { 82 | driver = new Driver({ 83 | connection: { name: "conn" }, 84 | name: "name", 85 | port: "3000" 86 | }); 87 | 88 | json = driver.toJSON(); 89 | }); 90 | 91 | it("returns an object", function() { 92 | expect(json).to.be.a("object"); 93 | }); 94 | 95 | it("contains the driver's name", function() { 96 | expect(json.name).to.eql("name"); 97 | }); 98 | 99 | it("contains the driver's constructor name", function() { 100 | expect(json.driver).to.eql("Driver"); 101 | }); 102 | 103 | it("contains the driver's connection name", function() { 104 | expect(json.connection).to.eql("conn"); 105 | }); 106 | 107 | it("contains the driver's commands", function() { 108 | expect(json.commands).to.eql(Object.keys(driver.commands)); 109 | }); 110 | 111 | it("contains the driver's events, or an empty array", function() { 112 | expect(json.events).to.eql([]); 113 | driver.events = ["hello", "world"]; 114 | expect(driver.toJSON().events).to.be.eql(["hello", "world"]); 115 | }); 116 | }); 117 | 118 | describe("#setupCommands", function() { 119 | beforeEach(function() { 120 | driver.proxyMethods = spy(); 121 | }); 122 | 123 | it("snake_cases and proxies methods to @commands for the API", function() { 124 | var commands = ["helloWorld", "otherTestCommand"], 125 | snake_case = ["hello_world", "other_test_command"]; 126 | 127 | commands.forEach(function(name) { 128 | driver[name] = spy(); 129 | }); 130 | 131 | driver.setupCommands(commands); 132 | 133 | for (var i = 0; i < commands.length; i++) { 134 | var cmd = commands[i], 135 | snake = snake_case[i]; 136 | 137 | expect(driver.commands[snake]).to.be.eql(driver[cmd]); 138 | } 139 | }); 140 | 141 | it("handles edge cases", function() { 142 | var commands = [ 143 | "HelloWorld", 144 | "getPNGStream", 145 | "getHSetting", 146 | "getRGB", 147 | "convertRGBToHSL", 148 | "getTemperatureInF" 149 | ]; 150 | 151 | var snake_case = [ 152 | "hello_world", 153 | "get_png_stream", 154 | "get_h_setting", 155 | "get_rgb", 156 | "convert_rgb_to_hsl", 157 | "get_temperature_in_f" 158 | ]; 159 | 160 | commands.forEach(function(cmd) { 161 | driver[cmd] = function() {}; 162 | }); 163 | 164 | driver.setupCommands(commands); 165 | 166 | expect(Object.keys(driver.commands)).to.be.eql(snake_case); 167 | }); 168 | }); 169 | }); 170 | -------------------------------------------------------------------------------- /spec/lib/initializer.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var initializer = lib("initializer"), 4 | Registry = lib("registry"), 5 | Config = lib("config"); 6 | 7 | var Loopback = lib("test/loopback"), 8 | Ping = lib("test/ping"), 9 | TestAdaptor = lib("test/test-adaptor"), 10 | TestDriver = lib("test/test-driver"); 11 | 12 | describe("Initializer", function() { 13 | beforeEach(function() { 14 | spy(Registry, "findBy"); 15 | stub(Registry, "register"); 16 | }); 17 | 18 | afterEach(function() { 19 | Registry.findBy.restore(); 20 | Registry.register.restore(); 21 | }); 22 | 23 | it("creates an instance of the requested adaptor/driver", function() { 24 | var adaptor = initializer("adaptor", { adaptor: "loopback" }); 25 | expect(adaptor).to.be.an.instanceOf(Loopback); 26 | 27 | var driver = initializer("driver", { driver: "ping" }); 28 | expect(driver).to.be.an.instanceOf(Ping); 29 | }); 30 | 31 | context("if the module isn't registered", function() { 32 | var module; 33 | 34 | beforeEach(function() { 35 | Registry.findBy.restore(); 36 | 37 | module = { 38 | adaptor: stub(), 39 | driver: stub() 40 | }; 41 | 42 | stub(Registry, "findBy") 43 | .onFirstCall().returns(false) 44 | .onSecondCall().returns(module); 45 | }); 46 | 47 | context("if a module key was provided", function() { 48 | it("attempts to register it", function() { 49 | initializer("adaptor", { adaptor: "adaptor", module: "test" }); 50 | expect(Registry.register).to.be.calledWith("test"); 51 | expect(module.adaptor).to.be.called; 52 | }); 53 | }); 54 | 55 | context("if no module key was provided", function() { 56 | it("attempts to find it automatically", function() { 57 | initializer("driver", { driver: "driver" }); 58 | expect(Registry.register).to.be.calledWith("cylon-driver"); 59 | expect(module.driver).to.be.called; 60 | }); 61 | }); 62 | 63 | context("if the module still can't be found", function() { 64 | beforeEach(function() { 65 | Registry.findBy.onSecondCall().returns(false); 66 | }); 67 | 68 | it("throws an error", function() { 69 | function fn() { 70 | return initializer("adaptor", { adaptor: "badadaptor" }); 71 | } 72 | 73 | expect(fn).to.throw("Unable to find adaptor for badadaptor"); 74 | }); 75 | }); 76 | }); 77 | 78 | context("if in test mode", function() { 79 | var tm = Config.testMode, adaptor, driver; 80 | 81 | beforeEach(function() { 82 | Config.testMode = true; 83 | 84 | driver = initializer("driver", { driver: "ping" }); 85 | adaptor = initializer("adaptor", { adaptor: "loopback" }); 86 | }); 87 | 88 | afterEach(function() { 89 | Config.testMode = tm; 90 | }); 91 | 92 | it("creates a test adaptor/driver", function() { 93 | expect(driver).to.be.an.instanceOf(TestDriver); 94 | expect(adaptor).to.be.an.instanceOf(TestAdaptor); 95 | }); 96 | 97 | it("stubs out the driver/adaptor behaviour", function() { 98 | expect(driver.ping).to.be.a("function"); 99 | }); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /spec/lib/io/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Utils = lib("io/utils.js"); 4 | 5 | describe("IOUtils", function() { 6 | describe("#periodAndDuty", function() { 7 | var fn = Utils.periodAndDuty; 8 | 9 | it("calculates values for PWM", function() { 10 | var value = fn(0.5, 2000, null, null); 11 | expect(value).to.be.eql({ period: 500000, duty: 250000 }); 12 | }); 13 | 14 | it("calculates values for servos", function() { 15 | var value = fn(0.5, 50, { min: 500, max: 2400 }, "high"); 16 | expect(value).to.be.eql({ duty: 1450000, period: 20000000 }); 17 | }); 18 | 19 | it("calculates values for different polarities", function() { 20 | var value = fn(0.5, 50, { min: 500, max: 2400 }, "low"); 21 | expect(value).to.be.eql({ duty: 18550000, period: 20000000 }); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /spec/lib/logger.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Logger = lib("logger"), 4 | Config = lib("config"); 5 | 6 | describe("Logger", function() { 7 | afterEach(function() { 8 | // to be friendly to other specs 9 | Config.logger = false; 10 | Config.silent = false; 11 | Logger.setup(); 12 | }); 13 | 14 | describe("#setup", function() { 15 | context("with no arguments", function() { 16 | it("sets up a BasicLogger", function() { 17 | Config.logger = null; 18 | Logger.setup(); 19 | expect(Logger.logger.name).to.be.eql("basiclogger"); 20 | }); 21 | }); 22 | 23 | context("with false", function() { 24 | it("sets up a NullLogger", function() { 25 | Config.logger = false; 26 | Logger.setup(); 27 | expect(Logger.logger.name).to.be.eql("nulllogger"); 28 | }); 29 | }); 30 | 31 | context("with a custom logger", function() { 32 | it("uses the custom logger", function() { 33 | function customlogger() {} 34 | Config.logger = customlogger; 35 | Logger.setup(); 36 | expect(Logger.logger.name).to.be.eql("customlogger"); 37 | }); 38 | }); 39 | }); 40 | 41 | describe("proxies", function() { 42 | var logger; 43 | 44 | beforeEach(function() { 45 | logger = spy(); 46 | Logger.logger = logger; 47 | }); 48 | 49 | describe("#debug", function() { 50 | it("proxies to the logger method", function() { 51 | Logger.should.debug = true; 52 | Logger.debug("debug message"); 53 | Logger.should.debug = false; 54 | expect(logger).to.be.calledWith("debug message"); 55 | }); 56 | }); 57 | 58 | describe("#log", function() { 59 | it("proxies to the logger method", function() { 60 | Logger.log("log message"); 61 | expect(logger).to.be.calledWith("log message"); 62 | }); 63 | }); 64 | }); 65 | 66 | it("automatically updates if configuration changed", function() { 67 | var custom = spy(); 68 | expect(Logger.logger.name).to.be.eql("basiclogger"); 69 | Config.update({ logger: custom }); 70 | expect(Logger.logger).to.be.eql(custom); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /spec/lib/mcp.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var MCP = lib("mcp"), 4 | Robot = lib("robot"); 5 | 6 | describe("MCP", function() { 7 | it("contains a collection of robots", function() { 8 | expect(MCP.robots).to.be.eql({}); 9 | }); 10 | 11 | it("contains a collection of commands", function() { 12 | expect(MCP.commands).to.be.eql({}); 13 | }); 14 | 15 | it("contains a collection of events", function() { 16 | expect(MCP.events).to.be.eql(["robot_added", "robot_removed"]); 17 | }); 18 | 19 | describe("#create", function() { 20 | afterEach(function() { 21 | MCP.robots = {}; 22 | }); 23 | 24 | it("uses passed options to create a new Robot", function() { 25 | var opts = { name: "Ultron" }; 26 | var robot = MCP.create(opts); 27 | 28 | expect(robot.toString()).to.be.eql("[Robot name='Ultron']"); 29 | expect(MCP.robots.Ultron).to.be.eql(robot); 30 | }); 31 | 32 | it("avoids duplicating names", function() { 33 | MCP.create({ name: "Ultron" }); 34 | MCP.create({ name: "Ultron" }); 35 | 36 | var bots = Object.keys(MCP.robots); 37 | expect(bots).to.be.eql(["Ultron", "Ultron-1"]); 38 | }); 39 | }); 40 | 41 | describe("#start", function() { 42 | it("calls #start() on all robots", function() { 43 | var bot1 = { start: spy() }, 44 | bot2 = { start: spy() }; 45 | 46 | MCP.robots = { 47 | bot1: bot1, 48 | bot2: bot2 49 | }; 50 | 51 | MCP.start(); 52 | 53 | expect(bot1.start).to.be.called; 54 | expect(bot2.start).to.be.called; 55 | }); 56 | }); 57 | 58 | describe("#halt", function() { 59 | it("calls #halt() on all robots", function() { 60 | var bot1 = { halt: spy() }, 61 | bot2 = { halt: spy() }; 62 | 63 | MCP.robots = { 64 | bot1: bot1, 65 | bot2: bot2 66 | }; 67 | 68 | MCP.halt(); 69 | 70 | expect(bot1.halt).to.be.called; 71 | expect(bot2.halt).to.be.called; 72 | }); 73 | }); 74 | 75 | describe("#toJSON", function() { 76 | var json, bot1, bot2; 77 | 78 | beforeEach(function() { 79 | bot1 = new Robot(); 80 | bot2 = new Robot(); 81 | 82 | MCP.robots = { bot1: bot1, bot2: bot2 }; 83 | MCP.commands.echo = function(arg) { return arg; }; 84 | 85 | json = MCP.toJSON(); 86 | }); 87 | 88 | it("contains all robots the MCP knows about", function() { 89 | expect(json.robots).to.be.eql([bot1.toJSON(), bot2.toJSON()]); 90 | }); 91 | 92 | it("contains an array of MCP commands", function() { 93 | expect(json.commands).to.be.eql(["echo"]); 94 | }); 95 | 96 | it("contains an array of MCP events", function() { 97 | expect(json.events).to.be.eql(["robot_added", "robot_removed"]); 98 | }); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /spec/lib/registry.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Registry = lib("registry"); 4 | 5 | var path = "./../spec/support/mock_module.js"; 6 | 7 | var mod = require("./../support/mock_module.js"); 8 | 9 | describe("Registry", function() { 10 | var original; 11 | 12 | beforeEach(function() { 13 | original = Registry.data; 14 | Registry.data = {}; 15 | }); 16 | 17 | afterEach(function() { 18 | Registry.data = original; 19 | }); 20 | 21 | describe("#register", function() { 22 | it("adds the supplied module to the Registry", function() { 23 | expect(Registry.data).to.be.eql({}); 24 | 25 | Registry.register(path); 26 | 27 | expect(Registry.data).to.be.eql({ 28 | "./../spec/support/mock_module.js": { 29 | module: mod, 30 | drivers: ["test-driver"], 31 | adaptors: ["test-adaptor"], 32 | dependencies: [] 33 | } 34 | }); 35 | }); 36 | 37 | context("when the module already exists", function() { 38 | it("returns the module", function() { 39 | expect(Registry.data).to.be.eql({}); 40 | 41 | var result = Registry.register(path); 42 | 43 | // should register the module and return it 44 | expect(result).to.be.eql(mod); 45 | 46 | result = Registry.register(path); 47 | 48 | // should just return the existing module 49 | expect(result).to.be.eql(mod); 50 | 51 | }); 52 | }); 53 | }); 54 | 55 | describe("#findBy", function() { 56 | beforeEach(function() { 57 | stub(Registry, "search"); 58 | }); 59 | 60 | afterEach(function() { 61 | Registry.search.restore(); 62 | }); 63 | 64 | it("calls #search, pluralizing if necessary", function() { 65 | Registry.findBy("adaptors", "testing"); 66 | expect(Registry.search).to.be.calledWith("adaptors", "testing"); 67 | 68 | Registry.findBy("driver", "testing"); 69 | expect(Registry.search).to.be.calledWith("drivers", "testing"); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /spec/lib/utils.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var utils = lib("utils"); 4 | 5 | describe("Utils", function() { 6 | describe("#makeUnique", function() { 7 | it("returns the original name if it's not a conflict", function() { 8 | var res = utils.makeUnique("hello", ["world"]); 9 | expect(res).to.be.eql("hello"); 10 | }); 11 | 12 | it("generates a unique name if it does collide", function() { 13 | var res = utils.makeUnique("hello", ["hello"]); 14 | expect(res).to.be.eql("hello-1"); 15 | }); 16 | 17 | it("will ignore existing duplicates", function() { 18 | var res = utils.makeUnique("hello", ["hello", "hello-1", "hello-2"]); 19 | expect(res).to.be.eql("hello-3"); 20 | }); 21 | }); 22 | 23 | describe("#every", function() { 24 | beforeEach(function() { 25 | this.clock = sinon.useFakeTimers(); 26 | }); 27 | 28 | afterEach(function() { 29 | this.clock.restore(); 30 | }); 31 | 32 | it("sets a function to be called when an interval passes", function() { 33 | var func = spy(); 34 | utils.every(10, func); 35 | this.clock.tick(25); 36 | expect(func).to.be.calledTwice; 37 | }); 38 | }); 39 | 40 | describe("#after", function() { 41 | beforeEach(function() { 42 | this.clock = sinon.useFakeTimers(); 43 | }); 44 | 45 | afterEach(function() { 46 | this.clock.restore(); 47 | }); 48 | 49 | it("sets a function to be called after an interval passes", function() { 50 | var func = spy(); 51 | utils.after(10, func); 52 | this.clock.tick(15); 53 | expect(func).to.be.called; 54 | }); 55 | }); 56 | 57 | describe("constantly", function() { 58 | beforeEach(function() { 59 | stub(global, "every").returns(0); 60 | }); 61 | 62 | afterEach(function() { 63 | global.every.restore(); 64 | }); 65 | 66 | it("schedules a task to run continuously with #every", function() { 67 | var func = function() {}; 68 | utils.constantly(func); 69 | 70 | expect(global.every).to.be.calledWith(0, func); 71 | }); 72 | }); 73 | 74 | describe("#finish", function() { 75 | beforeEach(function() { 76 | this.clock = sinon.useFakeTimers(); 77 | }); 78 | 79 | afterEach(function() { 80 | this.clock.restore(); 81 | }); 82 | 83 | it("stops calling an interval function", function() { 84 | var func = spy(); 85 | var interval = utils.every(10, func); 86 | this.clock.tick(15); 87 | utils.finish(interval); 88 | this.clock.tick(15); 89 | expect(func).to.be.calledOnce; 90 | }); 91 | }); 92 | 93 | describe("#subclass", function() { 94 | var BaseClass = function BaseClass(opts) { 95 | this.greeting = opts.greeting; 96 | }; 97 | 98 | BaseClass.prototype.sayHi = function() { 99 | return "Hi!"; 100 | }; 101 | 102 | var SubClass = function SubClass() { 103 | SubClass.__super__.constructor.apply(this, arguments); 104 | }; 105 | 106 | utils.subclass(SubClass, BaseClass); 107 | 108 | it("adds inheritance to Javascript classes", function() { 109 | var sub = new SubClass({greeting: "Hello World"}); 110 | expect(sub.greeting).to.be.eql("Hello World"); 111 | expect(sub.sayHi()).to.be.eql("Hi!"); 112 | }); 113 | }); 114 | 115 | describe("#proxyFunctionsToObject", function() { 116 | var methods = ["asString", "toString", "returnString"]; 117 | 118 | var ProxyClass = function ProxyClass() {}; 119 | 120 | ProxyClass.prototype.asString = function() { 121 | return "[object ProxyClass]"; 122 | }; 123 | 124 | ProxyClass.prototype.toString = function() { 125 | return "[object ProxyClass]"; 126 | }; 127 | 128 | ProxyClass.prototype.returnString = function(string) { 129 | return string; 130 | }; 131 | 132 | var TestClass = function TestClass() { 133 | this.testInstance = new ProxyClass(); 134 | utils.proxyFunctionsToObject(methods, this.testInstance, this, true); 135 | }; 136 | 137 | var testclass = new TestClass(); 138 | 139 | it("can alias methods", function() { 140 | expect(testclass.asString()).to.be.eql("[object ProxyClass]"); 141 | }); 142 | 143 | it("can alias existing methods if forced to", function() { 144 | expect(testclass.toString()).to.be.eql("[object ProxyClass]"); 145 | }); 146 | 147 | it("can alias methods with arguments", function() { 148 | expect(testclass.returnString).to.be.a("function"); 149 | }); 150 | }); 151 | 152 | describe("#fetch", function() { 153 | var fetch = utils.fetch, 154 | obj = { property: "hello world", false: false, null: null }; 155 | 156 | context("if the property exists on the object", function() { 157 | it("returns the value", function() { 158 | expect(fetch(obj, "property")).to.be.eql("hello world"); 159 | expect(fetch(obj, "false")).to.be.eql(false); 160 | expect(fetch(obj, "null")).to.be.eql(null); 161 | }); 162 | }); 163 | 164 | context("if the property doesn't exist on the object", function() { 165 | context("and no fallback value has been provided", function() { 166 | it("throws an Error", function() { 167 | var fn = function() { return fetch(obj, "notaproperty"); }; 168 | expect(fn).to.throw(Error, "key not found: \"notaproperty\""); 169 | }); 170 | }); 171 | 172 | context("and a fallback value has been provided", function() { 173 | it("returns the fallback value", function() { 174 | expect(fetch(obj, "notakey", "fallback")).to.be.eql("fallback"); 175 | }); 176 | }); 177 | 178 | context("and a fallback function has been provided", function() { 179 | context("if the function has no return value", function() { 180 | it("throws an Error", function() { 181 | var fn = function() { fetch(obj, "notakey", function() {}); }, 182 | str = "no return value from provided fallback function"; 183 | 184 | expect(fn).to.throw(Error, str); 185 | }); 186 | }); 187 | 188 | context("if the function returns a value", function() { 189 | it("returns the value returned by the fallback function", function() { 190 | var fn = function(key) { return "Couldn't find " + key; }, 191 | value = "Couldn't find notakey"; 192 | 193 | expect(fetch(obj, "notakey", fn)).to.be.eql(value); 194 | }); 195 | }); 196 | }); 197 | }); 198 | }); 199 | 200 | describe("#classCallCheck", function() { 201 | it("checks that an object is an instance of a constructor", function() { 202 | var fn = function(instance, Constructor) { 203 | return utils.classCallCheck.bind(null, instance, Constructor); 204 | }; 205 | 206 | function Class() { 207 | utils.classCallCheck(this, Class); 208 | } 209 | 210 | expect(fn([], Array)).to.not.throw; 211 | expect(fn([], Number)).to.throw(TypeError); 212 | 213 | expect(Class).to.throw(TypeError); 214 | expect(function() { return new Class(); }).not.to.throw(TypeError); 215 | }); 216 | }); 217 | }); 218 | -------------------------------------------------------------------------------- /spec/lib/utils/helpers.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _ = lib("utils/helpers"); 4 | 5 | describe("Helpers", function() { 6 | describe("extend", function() { 7 | var extend = _.extend; 8 | 9 | var base = { 10 | fruits: ["apple"], 11 | vegetables: ["beet"], 12 | thing: null, 13 | otherThing: "hello!", 14 | data: [{ user: "barney" }, { user: "fred" }] 15 | }; 16 | 17 | var source = { 18 | fruits: ["banana"], 19 | vegetables: ["carrot"], 20 | thing: "hello!", 21 | otherThing: null, 22 | data: [{ age: 36 }, { age: 40 }] 23 | }; 24 | 25 | var expected = { 26 | data: [ { age: 36, user: "barney" }, { age: 40, user: "fred" } ], 27 | fruits: [ "apple", "banana" ], 28 | vegetables: [ "beet", "carrot" ], 29 | thing: "hello!", 30 | otherThing: null 31 | }; 32 | 33 | it("extends two objects", function() { 34 | var extended = extend(base, source); 35 | expect(extended).to.be.eql(expected); 36 | }); 37 | }); 38 | 39 | describe("isObject", function() { 40 | var fn = _.isObject; 41 | 42 | it("checks if a value is an Object", function() { 43 | var Klass = function() {}, 44 | instance = new Klass(); 45 | 46 | expect(fn({})).to.be.eql(true); 47 | expect(fn(instance)).to.be.eql(true); 48 | 49 | expect(fn([])).to.be.eql(false); 50 | expect(fn(function() {})).to.be.eql(false); 51 | expect(fn(10)).to.be.eql(false); 52 | expect(fn("")).to.be.eql(false); 53 | }); 54 | }); 55 | 56 | describe("isFunction", function() { 57 | var fn = _.isFunction; 58 | 59 | it("checks if a value is a Function", function() { 60 | expect(fn(function() {})).to.be.eql(true); 61 | 62 | expect(fn({})).to.be.eql(false); 63 | expect(fn([])).to.be.eql(false); 64 | expect(fn(10)).to.be.eql(false); 65 | expect(fn("")).to.be.eql(false); 66 | }); 67 | }); 68 | 69 | describe("isArray", function() { 70 | var fn = _.isArray; 71 | 72 | it("checks if a value is an Array", function() { 73 | expect(fn([])).to.be.eql(true); 74 | 75 | expect(fn(function() {})).to.be.eql(false); 76 | expect(fn({})).to.be.eql(false); 77 | expect(fn(10)).to.be.eql(false); 78 | expect(fn("")).to.be.eql(false); 79 | }); 80 | }); 81 | 82 | describe("isNumber", function() { 83 | var fn = _.isNumber; 84 | 85 | it("checks if a value is a Number", function() { 86 | expect(fn(10)).to.be.eql(true); 87 | 88 | expect(fn(function() {})).to.be.eql(false); 89 | expect(fn({})).to.be.eql(false); 90 | expect(fn([])).to.be.eql(false); 91 | expect(fn("")).to.be.eql(false); 92 | }); 93 | }); 94 | 95 | describe("isString", function() { 96 | var fn = _.isString; 97 | 98 | it("checks if a value is a String", function() { 99 | expect(fn("")).to.be.eql(true); 100 | 101 | expect(fn(10)).to.be.eql(false); 102 | expect(fn(function() {})).to.be.eql(false); 103 | expect(fn({})).to.be.eql(false); 104 | expect(fn([])).to.be.eql(false); 105 | }); 106 | }); 107 | 108 | describe("#pluck", function() { 109 | var object = { a: { item: "hello" }, b: { item: "world" } }, 110 | array = [ { item: "hello" }, { item: "world" } ]; 111 | 112 | it("plucks values from a collection", function() { 113 | expect(_.pluck(object, "item")).to.be.eql(["hello", "world"]); 114 | expect(_.pluck(array, "item")).to.be.eql(["hello", "world"]); 115 | }); 116 | }); 117 | 118 | describe("#map", function() { 119 | var object = { a: { item: "hello" }, b: { item: "world" } }, 120 | array = [ { item: "hello" }, { item: "world" } ]; 121 | 122 | var fn = function(value, key) { 123 | return [value, key]; 124 | }; 125 | 126 | it("runs a function over items in a collection", function() { 127 | expect(_.map(object, fn)).to.be.eql([ 128 | [{ item: "hello" }, "a"], 129 | [{ item: "world" }, "b"] 130 | ]); 131 | 132 | expect(_.map(array, fn)).to.be.eql([ 133 | [{ item: "hello" }, 0], 134 | [{ item: "world" }, 1] 135 | ]); 136 | }); 137 | 138 | it("defaults to the identity function", function() { 139 | expect(_.map(array)).to.be.eql(array); 140 | expect(_.map(object)).to.be.eql(array); 141 | }); 142 | }); 143 | 144 | describe("#invoke", function() { 145 | var array = [ 146 | { 147 | name: "bob", 148 | toString: function() { 149 | return "Hi from " + this.name; 150 | } 151 | }, 152 | { 153 | name: "dave", 154 | toString: function() { 155 | return "hello from " + this.name; 156 | } 157 | } 158 | ]; 159 | 160 | var object = { 161 | bob: { 162 | name: "bob", 163 | toString: function() { 164 | return "Hi from " + this.name; 165 | } 166 | }, 167 | dave: { 168 | name: "dave", 169 | toString: function() { 170 | return "hello from " + this.name; 171 | } 172 | } 173 | }; 174 | 175 | it("runs a instance function over items in a collection", function() { 176 | expect(_.invoke(object, "toString")).to.be.eql([ 177 | "Hi from bob", 178 | "hello from dave" 179 | ]); 180 | 181 | expect(_.invoke(array, "toString")).to.be.eql([ 182 | "Hi from bob", 183 | "hello from dave" 184 | ]); 185 | 186 | expect(_.invoke([1, 2, 3, 4, 5], Number.prototype.toString)).to.be.eql([ 187 | "1", "2", "3", "4", "5" 188 | ]); 189 | }); 190 | }); 191 | 192 | describe("#each", function() { 193 | var object = { a: { item: "hello" }, b: { item: "world" } }, 194 | array = [ { item: "hello" }, { item: "world" } ]; 195 | 196 | var fn = function(value, key) { 197 | return [value, key]; 198 | }; 199 | 200 | it("runs a function over items in a collection", function() { 201 | fn = spy(); 202 | _.map(object, fn); 203 | 204 | expect(fn).to.be.calledWith(object.a, "a"); 205 | expect(fn).to.be.calledWith(object.b, "b"); 206 | 207 | fn = spy(); 208 | _.map(array, fn); 209 | 210 | expect(fn).to.be.calledWith(array[0], 0); 211 | expect(fn).to.be.calledWith(array[1], 1); 212 | }); 213 | }); 214 | 215 | describe("#reduce", function() { 216 | var arr = [1, 2, 3, 4, 5, 6], 217 | obj = { a: 1, b: 2 }; 218 | 219 | function add(sum, n) { return sum + n; } 220 | 221 | it("reduces over a collection with the provided iteratee", function() { 222 | expect(_.reduce(arr, add, 0)).to.be.eql(21); 223 | expect(_.reduce(obj, add, 0)).to.be.eql(3); 224 | }); 225 | 226 | it("defaults to the first value for the accumulator", function() { 227 | var object = { 228 | a: { name: "hello" }, 229 | b: { name: "world" } 230 | }; 231 | 232 | expect(_.reduce(arr, add)).to.be.eql(21); 233 | expect( 234 | _.reduce(object, function(acc, val) { 235 | acc.name += " " + val.name; 236 | return acc; 237 | }) 238 | ).to.be.eql({ name: "hello world"}); 239 | }); 240 | 241 | it("supports providing a `this` value", function() { 242 | var self = { 243 | toString: function(y) { return y.toString(); } 244 | }; 245 | 246 | var fn = function(acc, val) { 247 | return acc + this.toString(val); 248 | }; 249 | 250 | expect(_.reduce(arr, fn, 1, self)).to.be.eql("123456"); 251 | }); 252 | }); 253 | 254 | describe("#arity", function() { 255 | it("creates a function that only takes a certain # of args", function() { 256 | var fn = spy(); 257 | var one = _.arity(fn, 1); 258 | one("one", "two", "three"); 259 | expect(fn).to.be.calledWith("one"); 260 | }); 261 | }); 262 | 263 | describe("#partial", function() { 264 | it("partially applies a function's arguments", function() { 265 | var fn = spy(); 266 | var one = _.partial(fn, "one", "two"); 267 | one("three"); 268 | expect(fn).to.be.calledWith("one", "two", "three"); 269 | }); 270 | }); 271 | 272 | describe("#partialRight", function() { 273 | it("partially applies arguments to the end of a fn call", function() { 274 | var fn = spy(); 275 | var one = _.partialRight(fn, "two", "three"); 276 | one("one"); 277 | expect(fn).to.be.calledWith("one", "two", "three"); 278 | }); 279 | }); 280 | 281 | describe("#includes", function() { 282 | it("checks if an array includes a value", function() { 283 | var fn = _.includes; 284 | 285 | var arr = [1, "2", 3]; 286 | 287 | expect(fn(arr, 1)).to.be.eql(true); 288 | expect(fn(arr, "2")).to.be.eql(true); 289 | expect(fn(arr, {})).to.be.eql(false); 290 | }); 291 | }); 292 | 293 | describe("#parallel", function() { 294 | var fn1, fn2, fn3, callback; 295 | 296 | beforeEach(function() { 297 | fn1 = stub(); 298 | fn2 = stub(); 299 | fn3 = stub(); 300 | callback = stub(); 301 | }); 302 | 303 | it("executes a set of functions in parallel", function() { 304 | _.parallel([fn1, fn2, fn3], callback); 305 | 306 | expect(fn1).to.be.called; 307 | expect(fn2).to.be.called; 308 | expect(fn3).to.be.called; 309 | expect(callback).to.not.be.called; 310 | 311 | fn1.yield(null, true); 312 | expect(callback).to.not.be.called; 313 | fn2.yield(null, true); 314 | expect(callback).to.not.be.called; 315 | fn3.yield(null, true); 316 | 317 | expect(callback).to.be.calledWith(null, [true, true, true]); 318 | }); 319 | 320 | it("stops immediately if there's an error", function() { 321 | _.parallel([fn1, fn2, fn3], callback); 322 | 323 | fn1.yield(true, null); 324 | 325 | expect(callback).to.be.calledWith(true); 326 | 327 | fn2.yields(null, true); 328 | fn3.yields(null, true); 329 | 330 | expect(callback).to.be.calledOnce; 331 | }); 332 | }); 333 | 334 | describe("#series", function() { 335 | var fn1, fn2, fn3, callback; 336 | 337 | beforeEach(function() { 338 | fn1 = stub(); 339 | fn2 = stub(); 340 | fn3 = stub(); 341 | callback = stub(); 342 | }); 343 | 344 | it("executes a set of functions in series", function() { 345 | _.series([fn1, fn2, fn3], callback); 346 | 347 | expect(fn1).to.be.called; 348 | expect(fn2).to.not.be.called; 349 | expect(fn3).to.not.be.called; 350 | expect(callback).to.not.be.called; 351 | 352 | fn1.yield(null, true); 353 | 354 | expect(fn2).to.be.called; 355 | expect(fn3).to.not.be.called; 356 | expect(callback).to.not.be.called; 357 | 358 | fn2.yield(null, true); 359 | 360 | expect(fn3).to.be.called; 361 | expect(callback).to.not.be.called; 362 | 363 | fn3.yield(null, true); 364 | 365 | expect(callback).to.be.calledWith(null, [true, true, true]); 366 | }); 367 | 368 | it("stops immediately if there's an error", function() { 369 | _.series([fn1, fn2, fn3], callback); 370 | 371 | fn1.yield(true, null); 372 | 373 | expect(fn2).to.not.be.called; 374 | expect(fn3).to.not.be.called; 375 | expect(callback).to.be.calledWith(true); 376 | }); 377 | }); 378 | }); 379 | -------------------------------------------------------------------------------- /spec/lib/utils/monkey-patches.spec.js: -------------------------------------------------------------------------------- 1 | // jshint expr:true 2 | "use strict"; 3 | 4 | var patches = lib("utils/monkey-patches"); 5 | 6 | describe("monkey-patches", function() { 7 | beforeEach(function() { 8 | patches.uninstall(); 9 | }); 10 | 11 | afterEach(function() { 12 | patches.install(); 13 | }); 14 | 15 | describe("#install", function() { 16 | it("monkey-patches methods onto global classes", function() { 17 | var proto = Number.prototype; 18 | 19 | expect(proto.seconds).to.be.undefined; 20 | expect(proto.second).to.be.undefined; 21 | 22 | patches.install(); 23 | 24 | expect(proto.seconds).to.be.a("function"); 25 | expect(proto.second).to.be.a("function"); 26 | }); 27 | }); 28 | 29 | describe("#uninstall", function() { 30 | it("removes existing monkey-patching", function() { 31 | var proto = Number.prototype; 32 | 33 | patches.install(); 34 | 35 | expect(proto.seconds).to.be.a("function"); 36 | expect(proto.second).to.be.a("function"); 37 | 38 | patches.uninstall(); 39 | 40 | expect(proto.seconds).to.be.undefined; 41 | expect(proto.second).to.be.undefined; 42 | }); 43 | }); 44 | 45 | describe("Number", function() { 46 | beforeEach(function() { 47 | patches.install(); 48 | }); 49 | 50 | describe("#seconds", function() { 51 | it("allows for expressing time in seconds", function() { 52 | expect((5).seconds()).to.be.eql(5000); 53 | }); 54 | }); 55 | 56 | describe("#second", function() { 57 | it("allows for expressing time in seconds", function() { 58 | expect((1).second()).to.be.eql(1000); 59 | }); 60 | }); 61 | 62 | describe("#milliseconds", function() { 63 | it("allows for expressing time in milliseconds", function() { 64 | expect((5).milliseconds()).to.be.eql(5); 65 | }); 66 | }); 67 | 68 | describe("#ms", function() { 69 | it("allows for expressing time in milliseconds", function() { 70 | expect((5).ms()).to.be.eql(5); 71 | }); 72 | }); 73 | 74 | describe("#microseconds", function() { 75 | it("allows for expressing time in microseconds", function() { 76 | expect((5000).microseconds()).to.be.eql(5); 77 | }); 78 | }); 79 | 80 | describe("#minutes", function() { 81 | it("allows for expressing time in minutes", function() { 82 | expect((5).minutes()).to.be.eql(300000); 83 | }); 84 | }); 85 | 86 | describe("#minute", function() { 87 | it("allows for expressing time per minute", function() { 88 | expect((1).minute()).to.be.eql(60000); 89 | }); 90 | }); 91 | 92 | describe("#fromScale", function() { 93 | it("converts a value from one scale to 0-1 scale", function() { 94 | expect((5).fromScale(0, 10)).to.be.eql(0.5); 95 | }); 96 | 97 | it("converts floats", function() { 98 | expect((2.5).fromScale(0, 10)).to.be.eql(0.25); 99 | }); 100 | 101 | context("if the number goes above the top of the scale", function() { 102 | it("should return 1", function() { 103 | expect((15).fromScale(0, 10)).to.be.eql(1); 104 | }); 105 | }); 106 | 107 | context("if the number goes below the bottom of the scale", function() { 108 | it("should return 0", function() { 109 | expect((15).fromScale(0, 10)).to.be.eql(1); 110 | expect((5).fromScale(10, 20)).to.be.eql(0); 111 | }); 112 | }); 113 | }); 114 | 115 | describe("#toScale", function() { 116 | it("converts a value from 0-1 scale to another", function() { 117 | expect((0.5).toScale(0, 10)).to.be.eql(5); 118 | }); 119 | 120 | context("when value goes below bottom of scale", function() { 121 | it("returns the bottom of the scale", function() { 122 | expect((-5).toScale(0, 10)).to.be.eql(0); 123 | }); 124 | }); 125 | 126 | context("when value goes above top of scale", function() { 127 | it("returns the top of the scale", function() { 128 | expect((15).toScale(0, 10)).to.be.eql(10); 129 | }); 130 | }); 131 | 132 | it("converts to floats", function() { 133 | expect((0.25).toScale(0, 10)).to.be.eql(2.5); 134 | }); 135 | 136 | it("can be chained with #fromScale", function() { 137 | var num = (5).fromScale(0, 20).toScale(0, 10); 138 | expect(num).to.be.eql(2.5); 139 | }); 140 | }); 141 | }); 142 | }); 143 | -------------------------------------------------------------------------------- /spec/support/mock_module.js: -------------------------------------------------------------------------------- 1 | // A mock Cylon module for use in internal testing. 2 | "use strict"; 3 | 4 | module.exports = { 5 | adaptors: [ "test-adaptor" ], 6 | drivers: [ "test-driver" ] 7 | }; 8 | --------------------------------------------------------------------------------