Compare commits

..

85 Commits

Author SHA1 Message Date
Ron Evans 0ba0301318 Merge pull request #349 from ahdncordeiro/master
Correcting the test Adaptor and Driver
2016-08-26 12:16:22 +02:00
André Cordeiro f24df1ba89 test-driver.js - Adding superclass methods
Adding the start and halt methods for the TestDriver can be used for tests otherwise an exception will be thrown.
Exception: Driver#start method must be overwritten by descendant classes.
2016-07-26 14:19:43 -03:00
André Cordeiro d550df34f8 test-adaptor.js - Adding superclass methods
Adding the connect and disconnect methods for the TestAdaptor can be used for tests otherwise an exception will be thrown.
Exception: Adaptor#connect method must be overwritten by descendant classes.
2016-07-26 14:16:57 -03:00
Ron Evans ac1bd1ef3f Merge pull request #342 from mortenf/master
Fix missing log output for pin 0 re issue #338
2016-05-21 17:43:11 -07:00
Morten H. Frederiksen d72bab16d5 Fix missing log output for pin 0 re issue #338
Signed-off-by: Morten H. Frederiksen <morten@mfd-consult.dk>
2016-05-21 23:01:04 +02:00
deadprogram 44497e932c Run Travis builds for major Node.js versions only
Signed-off-by: deadprogram <ron@hybridgroup.com>
2016-05-07 11:47:57 -07:00
deadprogram de88b4edfd Add Node.js v6.x to Travis build
Signed-off-by: deadprogram <ron@hybridgroup.com>
2016-05-07 11:46:22 -07:00
deadprogram 1ff38f2e9f Update list of GPIO drivers
Signed-off-by: deadprogram <ron@hybridgroup.com>
2016-04-27 19:28:41 -07:00
deadprogram 9be1513a24 Update README for most current devices/platforms
Signed-off-by: deadprogram <ron@hybridgroup.com>
2016-04-25 13:39:51 -07:00
deadprogram 8c15825ec5 Update README for most current devices/platforms
Signed-off-by: deadprogram <ron@hybridgroup.com>
2016-04-25 13:36:25 -07:00
Ron Evans c0c8923eb7 Merge pull request #339 from danhklein/master
Fixed broken link to browser support documentation
2016-04-24 21:29:39 -07:00
Daniel Klein 33c2f3390f Fixed broken link to browser documentation 2016-04-24 12:54:43 -06:00
deadprogram e9796ef297 Updates for v1.3.0 release
Signed-off-by: deadprogram <ron@hybridgroup.com>
2016-04-22 12:51:19 -07:00
deadprogram 7dc046cbfe A long overdue update of the sizable list of contributors. Thank you all!
Signed-off-by: deadprogram <ron@hybridgroup.com>
2016-04-22 11:22:04 -07:00
deadprogram 54c84f15f9 Move CoC into separate file and update source
Signed-off-by: deadprogram <ron@hybridgroup.com>
2016-04-22 11:21:34 -07:00
deadprogram aca4ebce70 Use dot notation for tests
Signed-off-by: deadprogram <ron@hybridgroup.com>
2016-03-27 18:38:07 -07:00
deadprogram f6be3b1ae3 Adds back robot-scoped connections by name, and the tests to prove it.
Signed-off-by: deadprogram <ron@hybridgroup.com>
2016-03-27 18:28:14 -07:00
deadprogram 17e9cee6dc Autoload module from current directory when under development 2016-03-14 21:25:37 -07:00
Ron Evans 3787ebda6a Merge pull request #331 from iyogeshjoshi/master
Adds alternate keyword for 'adaptor' as 'adapter'
2016-02-25 08:32:33 -08:00
Yogi bb141ee903 Fixed lint errors 2016-02-25 21:35:09 +05:30
Yogi a5923a9d8e Adding alternate keyword for adaptor as adapter 2016-02-24 16:33:19 +05:30
deadprogram 4f81ca355a Corrected name of Sphero BLE module in README 2016-02-06 11:53:04 -08:00
deadprogram 8df5ccbe5d Update README to include PCA9544A i2c device 2016-02-03 21:56:44 -08:00
deadprogram 4cc9d1ac5d Add cylon-myo to list of user contributed platforms 2015-12-04 15:44:17 -08:00
deadprogram 6dcf7ac26d Build core for Travis using most recent versions of Node 2015-11-17 09:08:08 -08:00
deadprogram 161aac9990 Add user created platforms from @afoninsky to README thank you! 2015-11-04 11:56:56 -08:00
deadprogram 013dc86449 Update RELEASES 2015-09-08 06:57:19 -07:00
deadprogram 9182619baa Update to version 1.2.0 2015-09-08 06:53:25 -07:00
deadprogram ed76890b8a Making connections dynamic as well 2015-09-07 10:51:34 -07:00
deadprogram 16cdb56be6 Simple example of how to use dynamic devices 2015-09-07 08:47:25 -07:00
deadprogram 8921e902eb Add function to dynamically start devices 2015-09-07 08:34:01 -07:00
deadprogram 4ec19961f8 Devices will only be started up once, and can be added dynamically 2015-09-01 18:12:39 -07:00
deadprogram 9cdbdd1d37 Correct JSDocs for 'finish' 2015-09-01 11:42:10 -07:00
deadprogram bba5b1e653 Add 'finish' to time-based helpers 2015-09-01 11:30:55 -07:00
deadprogram 9f83eaa7a2 Fix #311 by setting default for tests to 'silent' 2015-09-01 10:18:17 -07:00
deadprogram 7415daed0c Add minutes time functions, for a complete range 2015-09-01 06:43:14 -07:00
deadprogram 012578f012 Convenience functions for milliseconds and microseconds 2015-08-31 21:34:41 -07:00
Ron Evans bea67b2e5e Merge pull request #305 from hybridgroup/refactor/logger
Simplify logger
2015-08-31 21:19:22 -07:00
Andrew Stewart 67f0386bb5 prevent callback(err) from messing with shutdown 2015-07-21 10:31:52 -07:00
Andrew Stewart 2e0b92f1bc small readme updates 2015-07-15 10:04:51 -07:00
Andrew Stewart c7bb3dde8c streamline index file 2015-07-15 09:54:57 -07:00
Andrew Stewart 3a2766bcd9 Stop using `lib` fn, breaks browserify 2015-07-14 18:30:21 -07:00
Andrew Stewart 3451932bde Remove accidentally-left-behind comments 2015-07-14 11:32:54 -07:00
Andrew Stewart 8a582fcf8d _.arity call no longer necessary 2015-07-13 14:08:21 -07:00
Andrew Stewart 843e0a3217 Simplify logger
This commit adds a simplified logging system, with two primary methods:

- `Logger#log(String) :: void` (for normal logging)
- `Logger#debug(String) :: void` (for debug logging)

This helps dramatically simplify logging infrastructure, as well as
enabling simpler toggling of log modes.

The configuration values for the Logger have also been updated:

- `logger :: Function` - function to log output to (both normal + debug)
- `silent :: Boolean` - whether to log default normal messages or not (`--silent` flag)
- `debug  :: Boolean` - whether or not to log debug output (`--debug` flag)

Additionally, the following CLI flags are supported re: logging:

- `--silent` disables normal logging (will override all other settings)
- `--debug` enables debug logging

Deprecation notices have been added for old log methods, with first-run
messages to this effect.

This change should have negligible impact on end-users, with minor
inconvenience for library developers, as they need to switch their
logging function calls over.

Logging configuration is still possible via both
`Cylon.Config.update(Object)`, and directly calling
`Cylon.Logger.setup(Object)`.
2015-07-13 14:05:12 -07:00
Andrew Stewart f14ac10c95 Add addional edge case handling for snake_casing
Closes https://github.com/hybridgroup/robeaux/issues/19.
2015-07-03 10:37:08 -07:00
Andrew Stewart c1bb466d5b Bump version to "1.1.0" 2015-07-01 15:23:14 -07:00
Andrew Stewart a3c2b0544d Correct CI script for codeclimate binary name change 2015-06-30 11:27:17 -07:00
Andrew Stewart e8f109508b Replace tomdoc with jsdoc 2015-06-29 10:21:25 -07:00
Ron Evans 4b374440d9 Merge pull request #301 from hybridgroup/refactor/mcp
Refactor MCP
2015-06-29 08:36:03 -07:00
Andrew Stewart def91ffe53 Split apart MCP, API manager, exports 2015-06-29 08:25:07 -07:00
Ron Evans 455c06ab91 Merge pull request #303 from hybridgroup/refactor/robot-initialization
Consolidate Robot initialization
2015-06-29 08:24:14 -07:00
Andrew Stewart ceac0df8dd Consolidate Robot initialization to a single fn
Additionally, extracts validation of connections being present out to
the validator logic.
2015-06-26 10:31:23 -07:00
Andrew Stewart 1726cdb5f1 Remove vestigial attributes on Robot 2015-06-26 08:17:30 -07:00
Andrew Stewart 8fbe9b9c4d Automate auto-binding of prototype methods 2015-06-25 12:33:13 -07:00
Edgar O Silva fd564f61f8 Merge pull request #296 from hybridgroup/remove/async
Remove dependency on 'async'
2015-06-24 13:30:43 -05:00
Ron Evans 8403d89e9f Merge pull request #299 from hybridgroup/refactor/config
Refactor Config
2015-06-22 21:18:36 -05:00
Andrew Stewart b1c91b7419 Logger should subscribe to updates, not be told what to do 2015-06-22 19:08:15 -07:00
Andrew Stewart 8ac8d7de94 Update config to act as subscribable data store 2015-06-22 18:57:34 -07:00
Edgar O Silva 93357d5012 Merge pull request #298 from hybridgroup/remove/license-preamble
Remove unnecessary comments at start of files
2015-06-22 15:16:28 -05:00
Andrew Stewart 0b48851885 Remove unnecessary comments at start of files 2015-06-22 09:16:43 -07:00
Andrew Stewart 0719fa203c Remove dependency on 'async'
This commit removes the last current dependency of Cylon, 'async'.

In it's place, simplified versions of the methods used have been added
so lib/utils/helpers.js.
2015-06-18 15:40:33 -07:00
Andrew Stewart f3efa83643 Correct issue with Robot halting 2015-06-18 14:56:39 -07:00
Ron Evans 1be64381a9 Merge pull request #295 from hybridgroup/refactor/validations
Extract out Robot option validation
2015-06-17 13:37:52 -07:00
Andrew Stewart 10982e4003 Add additional validations 2015-06-17 09:03:26 -07:00
Andrew Stewart 4238385be4 Extract out Robot param validation into new file 2015-06-17 09:03:26 -07:00
Andrew Stewart ad2db2d459 Update ESLint, use local for make tasks 2015-06-11 12:16:43 -07:00
Ron Evans 6978826f1b Merge pull request #285 from hybridgroup/add/monkey-patch-removal
Add method for removing monkey-patched methods
2015-06-11 08:55:48 -07:00
Andrew Stewart 9c67d9ba02 Add method for removing monkey-patched methods 2015-06-11 08:40:15 -07:00
Andrew Stewart 681e197ed9 Stop giving robots random names, use inc. counter 2015-06-09 11:50:50 -07:00
Andrew Stewart 952a25ac2c Add specs for initializer 2015-06-09 08:05:37 -07:00
Andrew Stewart 8adacef282 s/source/lib for loading files for specs 2015-06-09 07:21:03 -07:00
Andrew Stewart 68ab6ad441 Remove leftover comment 2015-06-09 07:08:46 -07:00
Andrew Stewart 6cccc41aff Begin JSDoc-ing core 2015-06-08 22:57:49 -07:00
deadprogram 80a09d55da Merge branch 'docs/contributing' 2015-06-05 16:28:38 -07:00
nathan 74b92776b0 add relay back to readme 2015-06-05 16:27:15 -07:00
deadprogram b1d84d042f Correct errors in JSON 2015-06-05 10:42:49 -07:00
deadprogram 0255b7ff0f Based on io.js contribution guidelines 2015-06-05 10:39:01 -07:00
deadprogram 7581dc3c64 WIP on possible new format for README 2015-06-05 10:35:33 -07:00
Ron Evans f3cbbe8ad7 Merge pull request #293 from pdehaan/patch-1
Update license property to valid SPDX license
2015-05-21 09:04:49 -07:00
Peter deHaan 760bb87d12 Update license property to valid SPDX license
specifying the type and URL is deprecated:

https://docs.npmjs.com/files/package.json#license
http://npm1k.org/
2015-05-20 23:06:28 -07:00
Andrew Stewart 3f4140bd08 Add util to verify constructor calls use 'new' 2015-05-12 18:47:27 -07:00
deadprogram 8fe7a500cf Remove user contributed driver 2015-05-09 13:38:47 -07:00
deadprogram 9c6e79ef15 Add Parrot Bebop to README 2015-05-07 07:15:26 -07:00
deadprogram 958239c6f1 Add WeMo to list of user-contributed platforms 2015-04-27 17:32:45 -07:00
54 changed files with 2119 additions and 1571 deletions

View File

@ -3,11 +3,13 @@ sudo: false
node_js:
- '0.10'
- '0.12'
- 'iojs'
- '4'
- '5'
- '6'
before_install:
- "mkdir -p ~/.npm"
before_script:
- npm install -g istanbul codeclimate-test-reporter
script:
- make ci
- CODECLIMATE_REPO_TOKEN=d3aad610220b6eaf4f51e38393c1b62586b1d68b898b42e418d9c2a8e0a7cb0d codeclimate < coverage/lcov.info
- CODECLIMATE_REPO_TOKEN=d3aad610220b6eaf4f51e38393c1b62586b1d68b898b42e418d9c2a8e0a7cb0d codeclimate-test-reporter < coverage/lcov.info

36
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,36 @@
# Code of Conduct
This Code of Conduct is adapted from [Rust's wonderful
CoC](http://www.rust-lang.org/conduct.html).
* We are committed to providing a friendly, safe and welcoming
environment for all, regardless of gender, sexual orientation,
disability, ethnicity, religion, or similar personal characteristic.
* Please avoid using overtly sexual nicknames or other nicknames that
might detract from a friendly, safe and welcoming environment for
all.
* Please be kind and courteous. There's no need to be mean or rude.
* Respect that people have differences of opinion and that every
design or implementation choice carries a trade-off and numerous
costs. There is seldom a right answer.
* Please keep unstructured critique to a minimum. If you have solid
ideas you want to experiment with, make a fork and see how it works.
* We will exclude you from interaction if you insult, demean or harass
anyone. That is not welcome behaviour. We interpret the term
"harassment" as including the definition in the [Citizen Code of
Conduct](http://citizencodeofconduct.org/); if you have any lack of
clarity about what might be included in that concept, please read
their definition. In particular, we don't tolerate behavior that
excludes people in socially marginalized groups.
* Private harassment is also unacceptable. No matter who you are, if
you feel you have been or are being harassed or made uncomfortable
by a community member, please contact one of the channel ops or any
of the TC members immediately with a capture (log, photo, email) of
the harassment if possible. Whether you're a regular contributor or
a newcomer, we care about making this community a safe place for you
and we've got your back.
* Likewise any spamming, trolling, flaming, baiting or other
attention-stealing behaviour is not welcome.
* Avoid the use of personal pronouns in code comments or
documentation. There is no need to address persons when explaining
code (e.g. "When the developer")

60
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,60 @@
# Contributing to Cylon.js
This document is based on the [Node.js contribution guidelines](https://github.com/nodejs/node/blob/master/CONTRIBUTING.md) thank you!
## Code of Conduct
The Code of Conduct explains the *bare minimum* behavior
expectations the Cylon.js project requires of its contributors.
[Please read it before participating.](./CODE_OF_CONDUCT.md)
## Issue Contributions
When opening new issues or commenting on existing issues on this repository
please make sure discussions are related to concrete technical issues with the
Cylon.js software.
## Code Contributions
The Cylon.js project welcomes new contributors.
This document will guide you through the contribution process.
What do you want to contribute?
- I want to otherwise correct or improve the docs or examples
- I want to report a bug
- I want to add some feature or functionality to an existing hardware platform
- I want to add support for a new hardware platform
Descriptions for each of these will be provided below.
## General Guidelines
* All patches must be provided under the Apache 2.0 License
* 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
* Submit a Github Pull Request to the appropriate branch and ideally discuss the changes with us in IRC.
* We will look at the patch, test it out, and give you feedback.
* 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.
* Take care to maintain the existing coding style.
* Add unit tests for any new or changed functionality & lint and test your code using `make test` and `make lint`.
* All pull requests should be "fast forward"
* If there are commits after yours use `git rebase -i <new_head_branch>`
* If you have local changes you may need to use `git stash`
* For git help see [progit](http://git-scm.com/book) which is an awesome (and free) book on git
## Developer's Certificate of Origin 1.0
By making a contribution to this project, I certify that:
* (a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license indicated
in the file; or
* (b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source license
and I have the right under that license to submit that work with
modifications, whether created in whole or in part by me, under the
same open source license (unless I am permitted to submit under a
different license), as indicated in the file; or
* (c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified it.

View File

@ -1,23 +1,68 @@
# Contributors
Cylon.js exists thanks to the efforts of the hardworking humans on the core team:
- Ron Evans ([@deadprogram](https://github.com/deadprogram))
- Edgar Silva ([@edgarSilva](https://github.com/edgarSilva))
- Andrew Stewart ([@stewart](https://github.com/stewart))
- Adrian Zankich ([@zankich](https://github.com/zankich))
And to the fine work of these wonderful contributors:
Cylon.js exists thanks to the efforts of the following hardworking humans who have worked on the core, adaptors, drivers, or documentation:
- Aaron Blondeau ([@aaronblondeau](https://github.com/aaronblondeau))
- Quincy Acklen ([@acklenx](https://github.com/acklenx))
- Adam Hunter ([@adamrhunter](https://github.com/adamrhunter))
- Andrew Nesbitt ([@andrew](https://github.com/andrew))
- Avner Cohen ([@AvnerCohen](https://github.com/AvnerCohen))
- Michał ([@bartmichu](https://github.com/bartmichu))
- Ben Evans ([@bencevans](https://github.com/bencevans))
- Brad Midgley ([@bmidgley](https://github.com/bmidgley))
- Sebastián González ([@brutalchrist](https://github.com/brutalchrist))
- Caleb Oller ([@caleboller](https://github.com/caleboller))
- Chris Boette ([@chrisbodhi](https://github.com/chrisbodhi))
- Christian Porzig ([@chrisfp](https://github.com/chrisfp))
- Chris Mattheiu ([@chrismatthieu](https://github.com/chrismatthieu))
- Chris Taylor ([@ChrisTheBaron](https://github.com/ChrisTheBaron))
- Daniel Portales ([@daetherius](https://github.com/daetherius))
- Daniel Lamb ([@daniellmb](https://github.com/daniellmb))
- Ron Evans ([@deadprogram](https://github.com/deadprogram))
- Derrell Lipman ([@derrell](https://github.com/derrell))
- Daniel Fischer ([@dfischer](https://github.com/dfischer))
- Edgar Silva ([@edgarSilva](https://github.com/edgarSilva))
- Eric Terpstra ([@ericterpstra](https://github.com/ericterpstra))
- Evoliofly ([@Evoliofly](https://github.com/Evoliofly))
- Fábio Franco Uechi ([@fabito](https://github.com/fabito))
- Felix Sanz ([@felixsanz](https://github.com/felixsanz))
- gorhgorh ([@gorhgorh](https://github.com/gorhgorh))
- Guillaume normand ([@gui2laume](https://github.com/gui2laume))
- Theron Boerner ([@hunterboerner](https://github.com/hunterboerner))
- James Brown ([@ibjhb](https://github.com/ibjhb))
- Yogi ([@iyogeshjoshi](https://github.com/iyogeshjoshi))
- Janaka Abeywardhana ([@janaka](https://github.com/janaka))
- Jay Wengrow ([@jaywengrow](https://github.com/jaywengrow))
- Jason Sewell ([@jaywon](https://github.com/jaywon))
- Jarrod Ribble ([@jribble](https://github.com/jribble))
- Jason Solis ([@jsolis](https://github.com/jsolis))
- Julian Cheal ([@juliancheal](https://github.com/juliancheal))
- Justin Zemlyansky ([@JustInDevelopment](https://github.com/JustInDevelopment))
- Justin Smith ([@justinisamaker](https://github.com/justinisamaker))
- Philippe Charrière ([@k33g](https://github.com/k33g))
- Kraig Walker ([@KraigWalker](https://github.com/KraigWalker))
- Loren West ([@lorenwest](https://github.com/lorenwest))
- Luis Godinez ([@luisgodinez](https://github.com/luisgodinez))
- Mario "Kuroir" Ricalde ([@MarioRicalde](https://github.com/MarioRicalde))
- Matheus Mariano ([@matheusmariano](https://github.com/matheusmariano))
- Michael Smith ([@michaelshmitty](https://github.com/michaelshmitty))
- Morten Høybye Frederiksen ([@mortenf](https://github.com/mortenf))
- Guido García ([@palmerabollo](https://github.com/palmerabollo))
- Peter deHaan ([@pdehaan](https://github.com/pdehaan))
- peterainbow ([@peterainbow](https://github.com/peterainbow))
- Rafael Magaña ([@rafmagana](https://github.com/rafmagana))
- Reid Carlberg ([@ReidCarlberg](https://github.com/ReidCarlberg))
- Sarah Hui ([@sehsarah](https://github.com/sehsarah))
- Mike Skalnik ([@skalnik](https://github.com/skalnik))
- Javier Cervantes ([@solojavier](https://github.com/solojavier))
- SonicRadish ([@sonicradish](https://github.com/sonicradish))
- Andrew Stewart ([@stewart](https://github.com/stewart))
- Tomasz Szymanski ([@szimano](https://github.com/szimano))
- Wojciech Erbetowski ([@wojtekerbetowski](https://github.com/wojtekerbetowski))
- Gize Bonilla ([@XixeBombilla](https://github.com/XixeBombilla))
- Jasson Qasqant ([@yeco](https://github.com/yeco))
- Nathan Zankich ([@zankavrogin](https://github.com/zankavrogin))
- Adrian Zankich ([@zankich](https://github.com/zankich))
Thank you!

View File

@ -1,4 +1,4 @@
Copyright (c) 2013-2015 The Hybrid Group
Copyright (c) 2013-2016 The Hybrid Group
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -17,7 +17,7 @@ cover:
@istanbul cover $(BIN)/_mocha $(TEST_FILES) --report lcovonly -- -R spec
lint:
@eslint lib spec examples
@$(BIN)/eslint lib spec examples
ci: lint cover

View File

@ -5,6 +5,8 @@ Cylon.js is a JavaScript framework for robotics, physical computing, and the Int
It provides a simple, but powerful way to create solutions that incorporate
multiple, different hardware devices concurrently.
Want to use Node.js for robots, drones, and IoT devices? You are in the right place.
Want to use Ruby on robots? Check out our sister project, [Artoo][].
Want to use Golang to power your robots? Check out our sister project,
@ -193,16 +195,17 @@ Cylon.start();
## Hardware Support
Cylon.js has an extensible syntax for connecting to multiple, different hardware
devices. The following 35 platforms are currently supported:
devices. The following 36 platforms are currently supported:
Platform | Support
-------- | -------
[Ardrone](http://ardrone2.parrot.com/) | [cylon-ardrone](https://github.com/hybridgroup/cylon-ardrone)
[ARDrone](http://ardrone2.parrot.com/) | [cylon-ardrone](https://github.com/hybridgroup/cylon-ardrone)
[Arduino](http://www.arduino.cc/) | [cylon-firmata](https://github.com/hybridgroup/cylon-firmata)
[Arduino YUN](http://arduino.cc/en/Main/ArduinoBoardYun?from=Products.ArduinoYUN) | [cylon-firmata](https://github.com/hybridgroup/cylon-firmata)
[AT&T M2X](https://m2x.att.com) | [cylon-m2x](https://github.com/hybridgroup/cylon-m2x)
[Audio]() | [cylon-audio](https://github.com/hybridgroup/cylon-audio)
Audio | [cylon-audio](https://github.com/hybridgroup/cylon-audio)
[Beaglebone Black](http://beagleboard.org/Products/BeagleBone+Black/) | [cylon-beaglebone](https://github.com/hybridgroup/cylon-beaglebone)
[Bebop](http://www.parrot.com/products/bebop-drone/) | [cylon-bebop](https://github.com/hybridgroup/cylon-bebop)
[Bluetooth LE](http://en.wikipedia.org/wiki/Bluetooth_low_energy) | [cylon-ble](https://github.com/hybridgroup/cylon-ble)
[Crazyflie](http://www.bitcraze.se/) | [cylon-crazyflie](https://github.com/hybridgroup/cylon-crazyflie)
[Digispark](http://digistump.com/products/1) | [cylon-digispark](https://github.com/hybridgroup/cylon-digispark)
@ -217,7 +220,6 @@ Platform | Support
[MQTT](http://mqtt.org/) | [cylon-mqtt](https://github.com/hybridgroup/cylon-mqtt)
[Nest](http://nest.com/) | [cylon-nest](https://github.com/hybridgroup/cylon-nest)
[Neurosky](http://store.neurosky.com/products/mindwave-mobile) | [cylon-neurosky](https://github.com/hybridgroup/cylon-neurosky)
[Ollie](http://gosphero.com/ollie) | [cylon-ollie](https://github.com/hybridgroup/cylon-ollie)
[OpenCV](http://opencv.org/) | [cylon-opencv](https://github.com/hybridgroup/cylon-opencv)
[Phillips Hue](http://www2.meethue.com/) | [cylon-hue](https://github.com/hybridgroup/cylon-hue)
[Pebble](http://www.getpebble.com/) | [cylon-pebble](https://github.com/hybridgroup/cylon-pebble)
@ -228,50 +230,62 @@ Platform | Support
[Salesforce](http://www.force.com/) | [cylon-force](https://github.com/hybridgroup/cylon-force)
[Skynet](http://skynet.im/) | [cylon-skynet](https://github.com/hybridgroup/cylon-skynet)
[Spark](http://www.spark.io/) | [cylon-spark](https://github.com/hybridgroup/cylon-spark)
[Speech]() | [cylon-speech](https://github.com/hybridgroup/cylon-speech)
Speech | [cylon-speech](https://github.com/hybridgroup/cylon-speech)
[Sphero](http://www.gosphero.com/) | [cylon-sphero](https://github.com/hybridgroup/cylon-sphero)
[Sphero BLE](http://sphero.com/bb8) | [cylon-sphero-ble](https://github.com/hybridgroup/cylon-sphero-ble)
[Tessel](https://tessel.io/) | [cylon-tessel](https://github.com/hybridgroup/cylon-tessel)
[WICED Sense](http://www.broadcom.com/products/wiced/sense/) | [cylon-wiced-sense](https://github.com/hybridgroup/cylon-wiced-sense)
Our implementation of GPIO (General Purpose Input/Output) allows for a shared
set of drivers supporting a number of devices:
set of drivers supporting 14 different devices:
- [GPIO](https://en.wikipedia.org/wiki/General_Purpose_Input/Output) <=> [Drivers](https://github.com/hybridgroup/cylon-gpio)
- Analog Sensor
- Button
- Continuous Servo
- Direct Pin
- IR Rangefinder
- IR Range Sensor
- LED
- MakeyButton
- Makey Button (high-resistance button like the [MakeyMakey](http://www.makeymakey.com/))
- Maxbotix Ultrasonic Range Finder
- Motor
- Relay
- RGB LED
- Servo
- Temperature Sensor
- TP401 Gas Sensor
Additionally, we also support a number of I2C (Inter-Integrated Circuit) devices
We also support 14 different I2C (Inter-Integrated Circuit) devices
through a shared `cylon-i2c` module:
- [I2C](https://en.wikipedia.org/wiki/I%C2%B2C) <=> [Drivers](https://github.com/hybridgroup/cylon-i2c)
- BlinkM
- BMP180
- BlinkM RGB LED
- BMP180 Barometric Pressure + Temperature sensor
- Direct I2C
- HMC6352 Digital Compass
- LCD Display
- JHD1313M1 LCD with RGB Backlight
- LCD
- LIDAR-Lite
- LSM9DS0G 9 Degrees of Freedom IMU
- LSM9DS0XM 9 Degrees of Freedom IMU
- MPL115A2 Barometer/Thermometer
- MAG3110 3-Axis Digital Magnetometer
- MPL115A2 Digital Barometer & Thermometer
- MPU6050 Triple Axis Accelerometer and Gyro
- PCA9544a 4-Channel I2C Mux
- PCA9685 16-Channel 12-bit PWM/Servo Driver
In addition to our supported platforms, we have the following user contributed platforms:
In addition to our officially supported platforms, we have the following 8 user contributed platforms:
Platform | Support
-------- | -------
[APC UPS](http://www.apcupsd.org/) | [cylon-apcupsd](https://github.com/afoninsky/cylon-apcupsd)
[iBeacon](https://developer.apple.com/ibeacon/) | [cylon-beacon](https://github.com/juliancheal/cylon-beacon)
[Myo](https://www.myo.com/) | [cylon-myo](https://github.com/adaemi/cylon-myo)
[One-Wire](https://en.wikipedia.org/wiki/1-Wire) | [cylon-one-wire](https://github.com/rkelly92/cylon-one-wire)
[Parrot Rolling Spider](http://www.parrot.com/usa/products/rolling-spider/) | [cylon-rolling-spider](https://github.com/ChrisTheBaron/cylon-rolling-spider)
[PCDuino](http://www.pcduino.com/) | [cylon-pcduino](https://github.com/alexwang2013/cylon-pcduino)
[iBeacon](https://developer.apple.com/ibeacon/) | [cylon-beacon](https://github.com/juliancheal/cylon-beacon)
[Telegram](https://telegram.org/) | [cylon-telegram](https://github.com/afoninsky/cylon-telegram)
[WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) | [cylon-wemo](https://github.com/ChrisTheBaron/cylon-wemo)
We'll also have many more platforms and drivers coming soon, [follow us on Twitter][Twitter] for updates.
@ -282,7 +296,7 @@ We'll also have many more platforms and drivers coming soon, [follow us on Twitt
Cylon.js can be run directly in-browser, using the `browserify` NPM module.
You can also run it from withing a Chrome connected app, or a PhoneGap mobile app.
For more info on browser support, and for help with different configurations, you can find more info [in our docs](/documentation/guides/browser-support).
For more info on browser support, and for help with different configurations, you can find more info [in our docs](https://cylonjs.com/documentation/guides/browser-support/).
## API Plugins
@ -328,62 +342,12 @@ If you want to help with documentation, you can find the code for our website at
## Contributing
* All patches must be provided under the Apache 2.0 License
* 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
* Submit a Github Pull Request to the appropriate branch and ideally discuss the changes with us in IRC.
* We will look at the patch, test it out, and give you feedback.
* 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.
* Take care to maintain the existing coding style.
* Add unit tests for any new or changed functionality & lint and test your code using `make test` and `make lint`.
* All pull requests should be "fast forward"
* If there are commits after yours use “git rebase -i <new_head_branch>
* If you have local changes you may need to use “git stash”
* For git help see [progit](http://git-scm.com/book) which is an awesome (and free) book on git
For our contribution guidelines, please go to [CONTRIBUTING.md](https://github.com/hybridgroup/cylon/blob/master/CONTRIBUTING.md).
## Release History
Version | Notes
------- | -----
1.0.0 | Remove deprecated Device and Connection syntax, add Basestar#respond method
0.22.2 | Bug-fix for Registry loader
0.22.1 | Remove lodash, misc. bug fixes
0.22.0 | API extraction, new devices syntax.
0.21.2 | Update Robeaux version
0.21.1 | Add back debug logging for starting/connecting devices/connections
0.21.0 | Remove Connection/Device objects, update Robot connection/device syntax, fluent syntax updates
0.20.2 | Correct API issues, possible issue with test setups
0.20.1 | Revert accidental scrict handling of param in driver initializer
0.20.0 | Browser support, new module loading, log level support, misc. development changes
0.19.1 | Correct issue with dynamic method proxying
0.19.0 | Fluent syntax, improved start/halt, various other updates
0.18.0 | Updates Robot and Driver commands structure
0.17.0 | Updates to API to match CPPP-IO spec
0.16.0 | New IO Utils, removal of Utils#bind, add Adaptor#_noop method.
0.15.1 | Fixed issue with the API on Tessel
0.15.0 | Better halting, cleaner startup, removed 'connect' and 'start' events, and misc other cleanups/refactors.
0.14.0 | Removal of node-namespace and misc. cleanup
0.13.3 | Fixes bug with disconnect functions not being called.
0.13.2 | Use pure Express, adds server-sent-events, upd API.
0.13.1 | Add API authentication and HTTPS support
0.13.0 | Set minimum Node version to 0.10.20, add utils to global namespace and improve initialization routines
0.12.0 | Extraction of CLI tooling
0.11.2 | bugfixes
0.11.0 | Refactor into pure JavaScript
0.10.4 | Add JS helper functions
0.10.3 | Fix dependency issue
0.10.2 | Create connections convenience vars, refactor config loading
0.10.1 | Updates required for test driven robotics, update Robeaux version, bugfixes
0.10.0 | Use Robeaux UX, add CLI commands for helping connect to devices, bugfixes
0.9.0 | Add AngularJS web interface to API, extensible commands for CLI
0.8.0 | Refactored Adaptor and Driver into proper base classes for easier authoring of new modules
0.7.0 | cylon command for generating new adaptors, support code for better GPIO support, literate examples
0.6.0 | API exposes robot commands, fixes issues in driver/adaptor init
0.5.0 | Improve API, add GPIO support for reuse in adaptors
0.4.0 | Refactor proxy in Cylon.Basestar, improve API
0.3.0 | Improved Cylon.Basestar, and added API
0.2.0 | Cylon.Basestar to help develop external adaptors/drivers
0.1.0 | Initial release for ongoing development
For the release history, please go to [RELEASES.md](https://github.com/hybridgroup/cylon/blob/master/RELEASES.md).
## License
Copyright (c) 2013-2015 The Hybrid Group. Licensed under the Apache 2.0 license.
Copyright (c) 2013-2016 The Hybrid Group. Licensed under the Apache 2.0 license.

46
RELEASES.md Normal file
View File

@ -0,0 +1,46 @@
## Release History
Version | Notes
------- | -----
1.3.0 | Smarter module loading rules, adaptor alias
1.2.0 | Dynamic connections & devices, more time helpers, simplified logging
1.1.0 | Clean ups, refactorings, misc. bug fixes.
1.0.0 | Remove deprecated Device and Connection syntax, add Basestar#respond method
0.22.2 | Bug-fix for Registry loader
0.22.1 | Remove lodash, misc. bug fixes
0.22.0 | API extraction, new devices syntax.
0.21.2 | Update Robeaux version
0.21.1 | Add back debug logging for starting/connecting devices/connections
0.21.0 | Remove Connection/Device objects, update Robot connection/device syntax, fluent syntax updates
0.20.2 | Correct API issues, possible issue with test setups
0.20.1 | Revert accidental scrict handling of param in driver initializer
0.20.0 | Browser support, new module loading, log level support, misc. development changes
0.19.1 | Correct issue with dynamic method proxying
0.19.0 | Fluent syntax, improved start/halt, various other updates
0.18.0 | Updates Robot and Driver commands structure
0.17.0 | Updates to API to match CPPP-IO spec
0.16.0 | New IO Utils, removal of Utils#bind, add Adaptor#_noop method.
0.15.1 | Fixed issue with the API on Tessel
0.15.0 | Better halting, cleaner startup, removed 'connect' and 'start' events, and misc other cleanups/refactors.
0.14.0 | Removal of node-namespace and misc. cleanup
0.13.3 | Fixes bug with disconnect functions not being called.
0.13.2 | Use pure Express, adds server-sent-events, upd API.
0.13.1 | Add API authentication and HTTPS support
0.13.0 | Set minimum Node version to 0.10.20, add utils to global namespace and improve initialization routines
0.12.0 | Extraction of CLI tooling
0.11.2 | bugfixes
0.11.0 | Refactor into pure JavaScript
0.10.4 | Add JS helper functions
0.10.3 | Fix dependency issue
0.10.2 | Create connections convenience vars, refactor config loading
0.10.1 | Updates required for test driven robotics, update Robeaux version, bugfixes
0.10.0 | Use Robeaux UX, add CLI commands for helping connect to devices, bugfixes
0.9.0 | Add AngularJS web interface to API, extensible commands for CLI
0.8.0 | Refactored Adaptor and Driver into proper base classes for easier authoring of new modules
0.7.0 | cylon command for generating new adaptors, support code for better GPIO support, literate examples
0.6.0 | API exposes robot commands, fixes issues in driver/adaptor init
0.5.0 | Improve API, add GPIO support for reuse in adaptors
0.4.0 | Refactor proxy in Cylon.Basestar, improve API
0.3.0 | Improved Cylon.Basestar, and added API
0.2.0 | Cylon.Basestar to help develop external adaptors/drivers
0.1.0 | Initial release for ongoing development

View File

@ -0,0 +1,25 @@
"use strict";
var Cylon = require("cylon");
Cylon.robot({
connections: {
loopback: { adaptor: "loopback" }
},
connectPinger: function() {
this.device("pinger",
{connection: "loopback", driver: "ping"});
this.startDevice(this.devices.pinger, function() {
console.log("Get ready to ping...");
});
},
work: function(my) {
my.connectPinger();
every((1).second(), function() {
console.log(my.pinger.ping());
});
}
}).start();

38
index.js Normal file
View File

@ -0,0 +1,38 @@
"use strict";
var MCP = require("./lib/mcp");
module.exports = {
MCP: require("./lib/mcp"),
Robot: require("./lib/robot"),
Driver: require("./lib/driver"),
Adaptor: require("./lib/adaptor"),
Utils: require("./lib/utils"),
Logger: require("./lib/logger"),
IO: {
DigitalPin: require("./lib/io/digital-pin"),
Utils: require("./lib/io/utils")
},
robot: MCP.create,
api: require("./lib/api").create,
config: require("./lib/config").update,
start: MCP.start,
halt: MCP.halt
};
process.on("SIGINT", function() {
MCP.halt(process.kill.bind(process, process.pid));
});
if (process.platform === "win32") {
var io = { input: process.stdin, output: process.stdout },
quit = process.emit.bind(process, "SIGINT");
require("readline").createInterface(io).on("SIGINT", quit);
}

View File

@ -1,11 +1,3 @@
/**
* Cylon.js - Adaptor base class
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var Basestar = require("./basestar"),
@ -16,14 +8,20 @@ function formatErrorMessage(name, message) {
return ["Error in connection", "'" + name + "'", "- " + message].join(" ");
}
// Public: Creates a new Adaptor
//
// opts - hash of acceptable params
// name - name of the Adaptor, used when printing to console
// connection - Connection the adaptor will use to proxy commands/events
//
// Returns a new Adaptor
/**
* Adaptor class
*
* @constructor Adaptor
*
* @param {Object} [opts] adaptor options
* @param {String} [opts.name] the adaptor's name
* @param {Object} [opts.robot] the robot the adaptor belongs to
* @param {Object} [opts.host] the host the adaptor will connect to
* @param {Object} [opts.port] the port the adaptor will connect to
*/
var Adaptor = module.exports = function Adaptor(opts) {
Adaptor.__super__.constructor.apply(this, arguments);
opts = opts || {};
this.name = opts.name;
@ -47,9 +45,12 @@ var Adaptor = module.exports = function Adaptor(opts) {
Utils.subclass(Adaptor, Basestar);
// Public: Basic #connect function. Must be overwritten by a descendent class
//
// Returns nothing, throws an error
/**
* A base connect function. Must be overwritten by a descendent.
*
* @throws Error if not overridden by a child class
* @return {void}
*/
Adaptor.prototype.connect = function() {
var message = formatErrorMessage(
this.name,
@ -59,9 +60,12 @@ Adaptor.prototype.connect = function() {
throw new Error(message);
};
// Public: Basic #disconnect function. Must be overwritten by a descendent class
//
// Returns nothing, throws an error
/**
* A base disconnect function. Must be overwritten by a descendent.
*
* @throws Error if not overridden by a child class
* @return {void}
*/
Adaptor.prototype.disconnect = function() {
var message = formatErrorMessage(
this.name,
@ -71,9 +75,11 @@ Adaptor.prototype.disconnect = function() {
throw new Error(message);
};
// Public: Expresses the Connection in JSON format
//
// Returns an Object containing Connection data
/**
* Expresses the Adaptor in a JSON-serializable format
*
* @return {Object} a representation of the Adaptor in a serializable format
*/
Adaptor.prototype.toJSON = function() {
return {
name: this.name,

52
lib/api.js Normal file
View File

@ -0,0 +1,52 @@
"use strict";
var MCP = require("./mcp"),
Logger = require("./logger"),
_ = require("./utils/helpers");
var api = module.exports = {};
api.instances = [];
/**
* Creates a new API instance
*
* @param {String} [Server] which API plugin to use (e.g. "http" loads
* cylon-api-http)
* @param {Object} opts options for the new API instance
* @return {void}
*/
api.create = function create(Server, opts) {
// if only passed options (or nothing), assume HTTP server
if (Server == null || _.isObject(Server) && !_.isFunction(Server)) {
opts = Server;
Server = "http";
}
opts = opts || {};
if (_.isString(Server)) {
var req = "cylon-api-" + Server;
try {
Server = require(req);
} catch (e) {
if (e.code !== "MODULE_NOT_FOUND") {
throw e;
}
[
"Cannot find the " + req + " API module.",
"You may be able to install it: `npm install " + req + "`"
].forEach(Logger.log);
throw new Error("Missing API plugin - cannot proceed");
}
}
opts.mcp = MCP;
var instance = new Server(opts);
api.instances.push(instance);
instance.start();
};

View File

@ -1,11 +1,3 @@
/**
* Cylon.js - Basestar base class
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var EventEmitter = require("events").EventEmitter;
@ -13,33 +5,41 @@ var EventEmitter = require("events").EventEmitter;
var Utils = require("./utils"),
_ = require("./utils/helpers");
// Basestar is a base class to be used when writing external Cylon adaptors and
// drivers. It provides some useful base methods and functionality
//
// It also extends EventEmitter, so child classes are capable of emitting events
// for other parts of the system to handle.
/**
* The Basestar class is a wrapper class around EventEmitter that underpins most
* other Cylon adaptor/driver classes, providing useful external base methods
* and functionality.
*
* @constructor Basestar
*/
var Basestar = module.exports = function Basestar() {
Utils.classCallCheck(this, Basestar);
};
Utils.subclass(Basestar, EventEmitter);
// Public: Proxies calls from all methods in the object to a target object
//
// methods - array of methods to proxy
// target - object to proxy methods to
// source - object to proxy methods from
// force - whether or not to overwrite existing method definitions
//
// Returns the klass where the methods have been proxied
/**
* Proxies calls from all methods in the source to a target object
*
* @param {String[]} methods methods to proxy
* @param {Object} target object to proxy methods to
* @param {Object} source object to proxy methods from
* @param {Boolean} [force=false] whether or not to overwrite existing methods
* @return {Object} the source
*/
Basestar.prototype.proxyMethods = Utils.proxyFunctionsToObject;
// Public: Triggers a callback and emits an event with provided data
//
// event - name of event to be triggered
// callback - callback function to be triggered
// ...data - additional arguments to be passed to both event/callback
//
// Returns nothing
/**
* Triggers the provided callback, and emits an event with the provided data.
*
* If an error is provided, emits the 'error' event.
*
* @param {String} event what event to emit
* @param {Function} callback function to be invoked with error/data
* @param {*} err possible error value
* @param {...*} data data values to be passed to error/callback
* @return {void}
*/
Basestar.prototype.respond = function(event, callback, err) {
var args = Array.prototype.slice.call(arguments, 3);
@ -54,17 +54,17 @@ Basestar.prototype.respond = function(event, callback, err) {
}
};
// Public: Defines an event handler that proxies events from a source object
// to a target object
//
// opts - object containing options:
// - targetEventName or eventName - event that should be emitted from the
// target
// - target - object to proxy event to
// - source - object to proxy event from
// - sendUpdate - whether or not to send an "update" event
//
// Returns the source
/**
* Defines an event handler to proxy events from a source object to a target
*
* @param {Object} opts event options
* @param {EventEmitter} opts.source source of events to listen for
* @param {EventEmitter} opts.target target new events should be emitted from
* @param {String} opts.eventName name of event to listen for, and proxy
* @param {Bool} [opts.sendUpdate=false] whether to emit the 'update' event
* @param {String} [opts.targetEventName] new event name to emit from target
* @return {EventEmitter} the source object
*/
Basestar.prototype.defineEvent = function(opts) {
opts.sendUpdate = opts.sendUpdate || false;
opts.targetEventName = opts.targetEventName || opts.eventName;
@ -83,23 +83,26 @@ Basestar.prototype.defineEvent = function(opts) {
return opts.source;
};
// Public: Creates an event handler that proxies events from an adaptor"s
// "connector" (reference to whatever module is actually talking to the hw)
// to the adaptor
//
// opts - hash of opts to be passed to defineEvent()
//
// Returns this.connector
/**
* A #defineEvent shorthand for adaptors.
*
* Proxies events from an adaptor's connector to the adaptor itself.
*
* @param {Object} opts proxy options
* @return {EventEmitter} the adaptor's connector
*/
Basestar.prototype.defineAdaptorEvent = function(opts) {
return this._proxyEvents(opts, this.connector, this);
};
// Public: Creates an event handler that proxies events from a driver"s
// connection to the driver
//
// opts - hash of opts to be passed to defineEvent()
//
// Returns this.connection
/**
* A #defineEvent shorthand for drivers.
*
* Proxies events from an driver's connection to the driver itself.
*
* @param {Object} opts proxy options
* @return {EventEmitter} the driver's connection
*/
Basestar.prototype.defineDriverEvent = function(opts) {
return this._proxyEvents(opts, this.connection, this);
};

View File

@ -1,16 +1,58 @@
/*
* Cylon.js - Internal Configuration
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
module.exports = {
logging: {},
var _ = require("./utils/helpers");
// are we in TDR test mode? Used to stub out adaptors/drivers.
testMode: false
var config = module.exports = {},
callbacks = [];
// default data
config.haltTimeout = 3000;
config.testMode = false;
config.logger = null;
config.silent = false;
config.debug = false;
/**
* Updates the Config, and triggers handler callbacks
*
* @param {Object} data new configuration information to set
* @return {Object} the updated configuration
*/
config.update = function update(data) {
var forbidden = ["update", "subscribe", "unsubscribe"];
Object.keys(data).forEach(function(key) {
if (~forbidden.indexOf(key)) { delete data[key]; }
});
if (!Object.keys(data).length) {
return config;
}
_.extend(config, data);
callbacks.forEach(function(callback) { callback(data); });
return config;
};
/**
* Subscribes a function to be called whenever the config is updated
*
* @param {Function} callback function to be called with updated data
* @return {void}
*/
config.subscribe = function subscribe(callback) {
callbacks.push(callback);
};
/**
* Unsubscribes a callback from configuration changes
*
* @param {Function} callback function to unsubscribe from changes
* @return {void}
*/
config.unsubscribe = function unsubscribe(callback) {
var idx = callbacks.indexOf(callback);
if (idx >= 0) { callbacks.splice(idx, 1); }
};

View File

@ -1,200 +0,0 @@
/*
* Cylon.js - Master Control Program
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var Async = require("async");
var Logger = require("./logger"),
Robot = require("./robot"),
Config = require("./config"),
Repl = require("./repl"),
Utils = require("./utils"),
_ = require("./utils/helpers");
var EventEmitter = require("events").EventEmitter;
var Cylon = module.exports = new EventEmitter();
Cylon.Logger = Logger;
Cylon.Driver = require("./driver");
Cylon.Adaptor = require("./adaptor");
Cylon.Utils = Utils;
Cylon.IO = {
DigitalPin: require("./io/digital-pin"),
Utils: require("./io/utils")
};
Cylon.apiInstances = [];
Cylon.robots = {};
Cylon.commands = {};
Cylon.events = [ "robot_added", "robot_removed" ];
// Public: Creates a new Robot
//
// opts - hash of Robot attributes
//
// Returns a shiny new Robot
// Examples:
// Cylon.robot
// connection: { name: "arduino", adaptor: "firmata" }
// device: { name: "led", driver: "led", pin: 13 }
//
// work: (me) ->
// me.led.toggle()
Cylon.robot = function robot(opts) {
opts = opts || {};
// check if a robot with the same name exists already
if (opts.name && this.robots[opts.name]) {
var original = opts.name;
opts.name = Utils.makeUnique(original, Object.keys(this.robots));
var str = "Robot names must be unique. Renaming '";
str += original + "' to '" + opts.name + "'";
Logger.warn(str);
}
var bot = new Robot(opts);
this.robots[bot.name] = bot;
this.emit("robot_added", bot.name);
return bot;
};
// Public: Initializes an API instance based on provided options.
//
// Returns nothing
Cylon.api = function api(Server, opts) {
// if only passed options (or nothing), assume HTTP server
if (Server == null || _.isObject(Server) && !_.isFunction(Server)) {
opts = Server;
Server = "http";
}
opts = opts || {};
if (_.isString(Server)) {
var req = "cylon-api-" + Server;
try {
Server = require(req);
} catch (e) {
if (e.code !== "MODULE_NOT_FOUND") {
throw e;
}
[
"Cannot find the " + req + " API module.",
"You may be able to install it: `npm install " + req + "`"
].forEach(_.arity(Logger.fatal, 1));
throw new Error("Missing API plugin - cannot proceed");
}
}
opts.mcp = this;
var instance = new Server(opts);
this.apiInstances.push(instance);
instance.start();
};
// Public: Starts up the API and the robots
//
// Returns nothing
Cylon.start = function start() {
var starters = _.pluck(this.robots, "start");
Async.parallel(starters, function() {
var mode = Utils.fetch(Config, "workMode", "async");
if (mode === "sync") {
_.invoke(this.robots, "startWork");
}
}.bind(this));
};
// Public: Sets the internal configuration, based on passed options
//
// opts - object containing configuration key/value pairs
//
// Returns the current config
Cylon.config = function(opts) {
var loggingChanged = (
opts.logging && Config.logging !== _.extend(Config.logging, opts.logging)
);
if (_.isObject(opts)) {
Config = _.extend(Config, opts);
}
if (loggingChanged) {
Logger.setup();
}
return Config;
};
/**
* Starts a new REPL in the context of the MCP.
*
* @return {void}
*/
Cylon.repl = function repl() {
var instance = new Repl(
{ prompt: "mcp > " },
{ robots: this.robots }
);
instance.start();
};
// Public: Halts the API and the robots
//
// callback - callback to be triggered when Cylon is ready to shutdown
//
// Returns nothing
Cylon.halt = function halt(callback) {
callback = callback || function() {};
var fns = _.pluck(this.robots, "halt");
// if robots can"t shut down quickly enough, forcefully self-terminate
var timeout = Config.haltTimeout || 3000;
Utils.after(timeout, callback);
Async.parallel(fns, callback);
};
Cylon.toJSON = function() {
return {
robots: _.invoke(this.robots, "toJSON"),
commands: Object.keys(this.commands),
events: this.events
};
};
if (process.platform === "win32") {
var readline = require("readline");
var io = { input: process.stdin, output: process.stdout };
readline.createInterface(io).on("SIGINT", function() {
process.emit("SIGINT");
});
}
process.on("SIGINT", function() {
Cylon.halt(function() {
process.kill(process.pid);
});
});

View File

@ -1,11 +1,3 @@
/**
* Cylon.js - Driver base class
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var Basestar = require("./basestar"),
@ -16,14 +8,20 @@ function formatErrorMessage(name, message) {
return ["Error in driver", "'" + name + "'", "- " + message].join(" ");
}
// Public: Creates a new Driver
//
// opts - hash of acceptable params
// name - name of the Driver, used when printing to console
// device - Device the driver will use to proxy commands/events
//
// Returns a new Driver
/**
* Driver class
*
* @constructor Driver
* @param {Object} [opts] driver options
* @param {String} [opts.name] the driver's name
* @param {Object} [opts.robot] the robot the driver belongs to
* @param {Object} [opts.connection] the adaptor the driver works through
* @param {Number} [opts.pin] the pin number the driver should have
* @param {Number} [opts.interval=10] read interval in milliseconds
*/
var Driver = module.exports = function Driver(opts) {
Driver.__super__.constructor.apply(this, arguments);
opts = opts || {};
this.name = opts.name;
@ -51,9 +49,12 @@ var Driver = module.exports = function Driver(opts) {
Utils.subclass(Driver, Basestar);
// Public: Basic #start function. Must be overwritten by a descendent class
//
// Returns nothing, throws an error
/**
* A base start function. Must be overwritten by a descendent.
*
* @throws Error if not overridden by a child class
* @return {void}
*/
Driver.prototype.start = function() {
var message = formatErrorMessage(
this.name,
@ -63,9 +64,12 @@ Driver.prototype.start = function() {
throw new Error(message);
};
// Public: Basic #halt function. Must be overwritten by a descendent class
//
// Returns nothing, throws an error
/**
* A base halt function. Must be overwritten by a descendent.
*
* @throws Error if not overridden by a child class
* @return {void}
*/
Driver.prototype.halt = function() {
var message = formatErrorMessage(
this.name,
@ -75,6 +79,16 @@ Driver.prototype.halt = function() {
throw new Error(message);
};
/**
* Sets up an array of commands for the Driver.
*
* Proxies commands from the Driver to its connection (or a manually specified
* proxy), and adds a snake_cased version to the driver's commands object.
*
* @param {String[]} commands an array of driver commands
* @param {Object} [proxy=this.connection] proxy target
* @return {void}
*/
Driver.prototype.setupCommands = function(commands, proxy) {
if (proxy == null) {
proxy = this.connection;
@ -82,9 +96,13 @@ Driver.prototype.setupCommands = function(commands, proxy) {
Utils.proxyFunctionsToObject(commands, proxy, this);
function endsWith(string, substr) {
return string.indexOf(substr, string.length - substr.length) !== -1;
}
commands.forEach(function(command) {
var snakeCase = command.replace(/[A-Z]+/g, function(match) {
if (match.length > 1) {
if (match.length > 1 && !endsWith(command, match)) {
match = match.replace(/[A-Z]$/, function(m) {
return "_" + m.toLowerCase();
});
@ -97,6 +115,11 @@ Driver.prototype.setupCommands = function(commands, proxy) {
}, this);
};
/**
* Expresses the Driver in a JSON-serializable format
*
* @return {Object} a representation of the Driver in a serializable format
*/
Driver.prototype.toJSON = function() {
return {
name: this.name,

View File

@ -1,11 +1,3 @@
/**
* Cylon.js - Adaptor/Driver initializer
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var Registry = require("./registry"),
@ -31,6 +23,11 @@ module.exports = function Initializer(type, opts) {
mod = Registry.findBy(type, opts[type]);
}
if (!mod) {
var err = [ "Unable to find", type, "for", opts[type] ].join(" ");
throw new Error(err);
}
var obj = mod[type](opts);
_.each(obj, function(prop, name) {

View File

@ -1,11 +1,3 @@
/**
* Cylon.js - GPIO Digital Pin class
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
/* eslint no-sync: 0 */
"use strict";
@ -20,8 +12,15 @@ var GPIO_PATH = "/sys/class/gpio";
var GPIO_READ = "in";
var GPIO_WRITE = "out";
// DigitalPin class offers an interface with the Linux GPIO system present in
// single-board computers such as a Raspberry Pi, or a BeagleBone
/**
* The DigitalPin class provides an interface with the Linux GPIO system present
* in single-board computers such as Raspberry Pi, or Beaglebone Black.
*
* @constructor DigitalPin
* @param {Object} opts digital pin options
* @param {String} pin which pin number to use
* @param {String} mode which pin mode to use
*/
var DigitalPin = module.exports = function DigitalPin(opts) {
this.pinNum = opts.pin.toString();
this.status = "low";

View File

@ -1,17 +1,15 @@
/**
* Cylon.js - I/O Utils
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
module.exports = {
// Returns { period: int, duty: int }
// Calculated based on params value, freq, pulseWidth = { min: int, max: int }
// pulseWidth min and max need to be specified in microseconds
/**
* Calculates PWM Period and Duty based on provided params.
*
* @param {Number} scaledDuty the scaled duty value
* @param {Number} freq frequency to use
* @param {Number} pulseWidth pulse width
* @param {String} [polarity=high] polarity value (high or low)
* @return {Object} calculated period and duty encapsulated in an object
*/
periodAndDuty: function(scaledDuty, freq, pulseWidth, polarity) {
var period, duty, maxDuty;

View File

@ -1,53 +1,82 @@
/**
* Cylon.js - Logger
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var levels = ["debug", "info", "warn", "error", "fatal"];
/* eslint no-use-before-define: 0 */
var BasicLogger = require("./logger/basic_logger"),
NullLogger = require("./logger/null_logger"),
Config = require("./config"),
var Config = require("./config"),
_ = require("./utils/helpers");
var BasicLogger = function basiclogger(str) {
var prefix = new Date().toISOString() + " : ";
console.log(prefix + str);
};
var NullLogger = function nulllogger() {};
var Logger = module.exports = {
setup: function(opts) {
if (_.isObject(opts)) {
Config.logging = _.extend(Config.logging, opts);
}
setup: setup,
var logger = Config.logging.logger,
level = Config.logging.level || "info";
// --debug CLI flag overrides any other option
if (_.includes(process.argv, "--debug")) {
level = "debug";
}
logger = (logger == null) ? BasicLogger : logger;
this.logger = logger || NullLogger;
this.level = level;
return this;
should: {
log: true,
debug: false
},
toString: function() {
return this.logger.toString();
log: function log(str) {
if (Logger.should.log) {
Logger.logger.call(Logger.logger, str);
}
},
debug: function debug(str) {
if (Logger.should.log && Logger.should.debug) {
Logger.logger.call(Logger.logger, str);
}
}
};
Logger.setup();
function setup(opts) {
if (_.isObject(opts)) { _.extend(Config, opts); }
levels.forEach(function(level) {
Logger[level] = function() {
if (levels.indexOf(level) >= levels.indexOf(Logger.level)) {
return Logger.logger[level].apply(Logger.logger, arguments);
var logger = Config.logger;
// if no logger supplied, use basic logger
if (logger == null) { logger = BasicLogger; }
// if logger is still falsy, use NullLogger
Logger.logger = logger || NullLogger;
Logger.should.log = !Config.silent;
Logger.should.debug = Config.debug;
// --silent CLI flag overrides
if (_.includes(process.argv, "--silent")) {
Logger.should.log = false;
}
// --debug CLI flag overrides
if (_.includes(process.argv, "--debug")) {
Logger.should.debug = false;
}
return Logger;
}
setup();
Config.subscribe(setup);
// deprecated holdovers
["info", "warn", "error", "fatal"].forEach(function(method) {
var called = false;
function showDeprecationNotice() {
console.log("The method Logger#" + method + " has been deprecated.");
console.log("It will be removed in Cylon 2.0.0.");
console.log("Please switch to using the #log or #debug Logger methods");
called = true;
}
Logger[method] = function() {
if (!called) { showDeprecationNotice(); }
Logger.log.apply(null, arguments);
};
});

View File

@ -1,33 +0,0 @@
/**
* Cylon.js - Basic Logger
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var getArgs = function(args) {
return args.length >= 1 ? [].slice.call(args, 0) : [];
};
var logString = function(type) {
var time = new Date().toISOString(),
upcase = String(type).toUpperCase(),
padded = String(" " + upcase).slice(-5);
return upcase[0] + ", [" + time + "] " + padded + " -- :";
};
// The BasicLogger logs to console.log
var BasicLogger = module.exports = {
toString: function() { return "BasicLogger"; },
};
["debug", "info", "warn", "error", "fatal"].forEach(function(type) {
BasicLogger[type] = function() {
var args = getArgs(arguments);
return console.log.apply(console, [].concat(logString(type), args));
};
});

View File

@ -1,19 +0,0 @@
/**
* Cylon.js - Null Logger
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
// The NullLogger is designed for cases where you want absolutely nothing to
// print to anywhere. Every proxied method from the Logger returns a noop.
var NullLogger = module.exports = {
toString: function() { return "NullLogger"; }
};
["debug", "info", "warn", "error", "fatal"].forEach(function(type) {
NullLogger[type] = function() {};
});

82
lib/mcp.js Normal file
View File

@ -0,0 +1,82 @@
"use strict";
var EventEmitter = require("events").EventEmitter;
var Config = require("./config"),
Logger = require("./logger"),
Utils = require("./utils"),
Robot = require("./robot"),
_ = require("./utils/helpers");
var mcp = module.exports = new EventEmitter();
mcp.robots = {};
mcp.commands = {};
mcp.events = [ "robot_added", "robot_removed" ];
/**
* Creates a new Robot with the provided options.
*
* @param {Object} opts robot options
* @return {Robot} the new robot
*/
mcp.create = function create(opts) {
opts = opts || {};
// check if a robot with the same name exists already
if (opts.name && mcp.robots[opts.name]) {
var original = opts.name;
opts.name = Utils.makeUnique(original, Object.keys(mcp.robots));
var str = "Robot names must be unique. Renaming '";
str += original + "' to '" + opts.name + "'";
Logger.log(str);
}
var bot = new Robot(opts);
mcp.robots[bot.name] = bot;
mcp.emit("robot_added", bot.name);
return bot;
};
mcp.start = function start(callback) {
var fns = _.pluck(mcp.robots, "start");
_.parallel(fns, function() {
var mode = Utils.fetch(Config, "workMode", "async");
if (mode === "sync") { _.invoke(mcp.robots, "startWork"); }
callback();
});
};
/**
* Halts all MCP robots.
*
* @param {Function} callback function to call when done halting robots
* @return {void}
*/
mcp.halt = function halt(callback) {
callback = callback || function() {};
var timeout = setTimeout(callback, Config.haltTimeout || 3000);
_.parallel(_.pluck(mcp.robots, "halt"), function() {
clearTimeout(timeout);
callback();
});
};
/**
* Serializes MCP robots, commands, and events into a JSON-serializable Object.
*
* @return {Object} a serializable representation of the MCP
*/
mcp.toJSON = function() {
return {
robots: _.invoke(mcp.robots, "toJSON"),
commands: Object.keys(mcp.commands),
events: mcp.events
};
};

View File

@ -1,15 +1,8 @@
/**
* Cylon.js - Module Registry
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var Logger = require("./logger"),
_ = require("./utils/helpers");
_ = require("./utils/helpers"),
path = require("path");
// Explicitly these modules here, so Browserify can grab them later
require("./test/loopback");
@ -38,7 +31,11 @@ var Registry = module.exports = {
var pkg;
try {
if (this.isModuleInDevelopment(module)) {
pkg = require(path.resolve(".") + "/index");
} else {
pkg = require(module);
}
} catch (e) {
if (e.code === "MODULE_NOT_FOUND") {
missingModuleError(module);
@ -107,6 +104,10 @@ var Registry = module.exports = {
}
return false;
},
isModuleInDevelopment: function(module) {
return (path.basename(path.resolve(".")) === module);
}
};

View File

@ -1,82 +0,0 @@
"use strict";
var util = require("util"),
createRepl = require("repl").start,
EventEmitter = require("events").EventEmitter;
var _ = require("./utils/helpers");
// asserts that a constructor was called with 'new'
function classCallCheck(instance, constructor) {
if (!instance instanceof constructor) {
throw new TypeError("Cannot call a class as a function");
}
}
var Repl = module.exports = function Repl(opts, context) {
classCallCheck(this, Repl);
opts = opts || {};
context = context || {};
opts.prompt = opts.prompt || "repl > ";
opts.stdin = opts.stdin || process.stdin;
opts.stdout = opts.stdout || process.stdout;
this.repl = null;
this.options = opts;
this.context = context;
};
Repl.active = false;
util.inherits(Repl, EventEmitter);
Repl.prototype.start = function() {
// don't try to start two repls at once
if (Repl.active) {
return false;
}
Repl.active = true;
this.repl = createRepl(this.options);
_.extend(this.repl.context, this.context);
this.repl.on("exit", function() {
Repl.active = false;
this.emit("exit");
}.bind(this));
this.repl.on("reset", function(context) {
_.extend(context, this.context);
this.emit("reset");
}.bind(this));
};
// add a value to the context
Repl.prototype.addContext = function(key, value) {
this.context[key] = value;
this.repl.context[key] = value;
};
// remove a value from the context
Repl.prototype.removeContext = function(key) {
delete this.context[key];
delete this.repl.context[key];
};
// set the context to the provided object
Repl.prototype.setContext = function(object) {
_.each(this.context, function(value, key) {
if (this.context.hasOwnProperty(key)) {
this.removeContext(key);
}
}.bind(this));
_.each(object, function(value, key) {
if (object.hasOwnProperty(key)) {
this.addContext(key, value);
}
}.bind(this));
};

View File

@ -1,58 +1,45 @@
/**
* Cylon.js - Robot class
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var initializer = require("./initializer"),
Logger = require("./logger"),
Utils = require("./utils"),
Repl = require("./repl"),
Config = require("./config"),
_ = require("./utils/helpers");
var Async = require("async"),
EventEmitter = require("events").EventEmitter;
var validator = require("./validator");
// Public: Creates a new Robot
//
// opts - object containing Robot options
// name - optional, string name of the robot
// connection/connections - object connections to connect to
// device/devices - object devices to connect to
// work - work to be performed when the Robot is started
//
// Returns a new Robot
var EventEmitter = require("events").EventEmitter;
// used when creating default robot names
var ROBOT_ID = 1;
/**
* Creates a new Robot instance based on provided options
*
* @constructor
* @param {Object} opts object with Robot options
* @param {String} [name] the name the robot should have
* @param {Object} [connections] object containing connection info for the Robot
* @param {Object} [devices] object containing device information for the Robot
* @param {Function} [work] a function the Robot will run when started
* @returns {Robot} new Robot instance
*/
var Robot = module.exports = function Robot(opts) {
Utils.classCallCheck(this, Robot);
opts = opts || {};
var methods = [
"toString",
"halt",
"startDevices",
"startConnections",
"start",
"initRobot",
"initDevices",
"initConnections",
"log"
];
validator.validate(opts);
methods.forEach(function(method) {
this[method] = this[method].bind(this);
}, this);
// auto-bind prototype methods
for (var prop in Object.getPrototypeOf(this)) {
if (this[prop] && prop !== "constructor") {
this[prop] = this[prop].bind(this);
}
}
this.initRobot(opts);
this.checkForBadSyntax(opts);
this.initConnections(opts);
this.initDevices(opts);
_.each(opts, function(opt, name) {
if (this[name] !== undefined) {
return;
@ -97,16 +84,11 @@ var Robot = module.exports = function Robot(opts) {
Utils.subclass(Robot, EventEmitter);
// Public: Generates a random name for a Robot.
//
// Returns a string name
Robot.randomName = function() {
return "Robot " + (Math.floor(Math.random() * 100000));
};
// Public: Expresses the Robot in a JSON-serializable format
//
// Returns an Object containing Robot data
/**
* Condenses information on a Robot to a JSON-serializable format
*
* @return {Object} serializable information on the Robot
*/
Robot.prototype.toJSON = function() {
return {
name: this.name,
@ -117,6 +99,13 @@ Robot.prototype.toJSON = function() {
};
};
/**
* Adds a new Connection to the Robot with the provided name and details.
*
* @param {String} name string name for the Connection to use
* @param {Object} conn options for the Connection initializer
* @return {Object} the robot
*/
Robot.prototype.connection = function(name, conn) {
conn.robot = this;
conn.name = name;
@ -129,72 +118,35 @@ Robot.prototype.connection = function(name, conn) {
str = "Connection names must be unique.";
str += "Renaming '" + original + "' to '" + conn.name + "'";
this.log("warn", str);
this.log(str);
}
if ("adapter" in conn) {
conn.adaptor = conn.adapter;
}
this.connections[conn.name] = initializer("adaptor", conn);
return this;
};
// Public: Initializes all vars for robot
//
// opts - options array passed to constructor
//
// Returns null
/**
* Initializes all values for a new Robot.
*
* @param {Object} opts object passed to Robot constructor
* @return {void}
*/
Robot.prototype.initRobot = function(opts) {
this.name = opts.name || Robot.randomName();
this.name = opts.name || "Robot " + ROBOT_ID++;
this.running = false;
this.connections = {};
this.devices = {};
this.adaptors = {};
this.drivers = {};
this.commands = {};
this.running = false;
this.work = opts.work || opts.play;
this.commands = {};
if (!this.work) {
this.work = function() { this.log("debug", "No work yet."); };
}
};
// Public: Checks options for bad Cylon syntax
//
// Returns nothing
Robot.prototype.checkForBadSyntax = function(opts) {
var self = this;
var RobotDSLError = new Error("Unable to start robot due to a syntax error");
RobotDSLError.name = "RobotDSLError";
function has(prop) { return opts[prop] != null; }
function checkForSingleObjectSyntax(type) {
var plural = type + "s";
if (has(type) && !has(plural)) {
[
"The single-object '" + type + "' syntax for robots is not valid.",
"Instead, use the multiple-value '" + plural + "' key syntax.",
"Details: http://cylonjs.com/documentation/guides/working-with-robots/"
].forEach(function(str) { self.log("fatal", str); });
throw RobotDSLError;
}
}
["connection", "device"].forEach(checkForSingleObjectSyntax);
};
// Public: Initializes all connections for the robot
//
// opts - options array passed to constructor
//
// Returns initialized connections
Robot.prototype.initConnections = function(opts) {
this.log("info", "Initializing connections.");
if (opts.connections == null) {
return this.connections;
this.work = function() { this.log("No work yet."); };
}
_.each(opts.connections, function(conn, key) {
@ -211,12 +163,22 @@ Robot.prototype.initConnections = function(opts) {
delete conn.devices;
}
this.connection(name, conn);
this.connection(name, _.extend({}, conn));
}, this);
return this.connections;
_.each(opts.devices, function(device, key) {
var name = _.isString(key) ? key : device.name;
this.device(name, _.extend({}, device));
}, this);
};
/**
* Adds a new Device to the Robot with the provided name and details.
*
* @param {String} name string name for the Device to use
* @param {Object} device options for the Device initializer
* @return {Object} the robot
*/
Robot.prototype.device = function(name, device) {
var str;
@ -229,13 +191,13 @@ Robot.prototype.device = function(name, device) {
str = "Device names must be unique.";
str += "Renaming '" + original + "' to '" + device.name + "'";
this.log("warn", str);
this.log(str);
}
if (_.isString(device.connection)) {
if (this.connections[device.connection] == null) {
str = "No connection found with the name " + device.connection + ".\n";
this.log("fatal", str);
this.log(str);
process.emit("SIGINT");
}
@ -252,36 +214,13 @@ Robot.prototype.device = function(name, device) {
return this;
};
// Public: Initializes all devices for the robot
//
// opts - options array passed to constructor
//
// Returns initialized devices
Robot.prototype.initDevices = function(opts) {
this.log("info", "Initializing devices.");
if (opts.devices == null) {
return this.devices;
}
// check that there are connections to use
if (!Object.keys(this.connections).length) {
throw new Error("No connections specified");
}
_.each(opts.devices, function(device, key) {
var name = _.isString(key) ? key : device.name;
this.device(name, device);
}, this);
return this.devices;
};
// Public: Starts the Robot working.
//
// Starts the connections, devices, and work.
//
// Returns the result of the work
/**
* Starts the Robot's connections, then devices, then work.
*
* @param {Function} callback function to be triggered when the Robot has
* started working
* @return {Object} the Robot
*/
Robot.prototype.start = function(callback) {
if (this.running) {
return this;
@ -295,14 +234,14 @@ Robot.prototype.start = function(callback) {
}
}.bind(this);
Async.series([
_.series([
this.startConnections,
this.startDevices,
start
], function(err, results) {
if (err) {
this.log("fatal", "An error occured while trying to start the robot:");
this.log("fatal", err);
this.log("An error occured while trying to start the robot:");
this.log(err);
this.halt(function() {
if (_.isFunction(this.error)) {
@ -323,130 +262,158 @@ Robot.prototype.start = function(callback) {
return this;
};
// Public: Starts the Robot"s work and triggers a callback
//
// callback - callback function to be triggered
//
// Returns nothing
/**
* Starts the Robot's work function
*
* @return {void}
*/
Robot.prototype.startWork = function() {
this.log("info", "Working.");
this.log("Working.");
this.emit("ready", this);
this.work.call(this, this);
this.running = true;
};
// Public: Starts the Robot"s connections and triggers a callback
//
// callback - callback function to be triggered
//
// Returns nothing
/**
* Starts the Robot's connections
*
* @param {Function} callback function to be triggered after the connections are
* started
* @return {void}
*/
Robot.prototype.startConnections = function(callback) {
this.log("info", "Starting connections.");
var starters = _.map(this.connections, function(conn, name) {
this[name] = conn;
this.log("Starting connections.");
var starters = _.map(this.connections, function(conn) {
return function(cb) {
var str = "Starting connection '" + name + "'";
if (conn.host) {
str += " on host " + conn.host;
} else if (conn.port) {
str += " on port " + conn.port;
}
this.log("debug", str + ".");
return conn.connect.call(conn, cb);
return this.startConnection(conn, cb);
}.bind(this);
}, this);
return Async.parallel(starters, callback);
return _.parallel(starters, callback);
};
// Public: Starts the Robot"s devices and triggers a callback
//
// callback - callback function to be triggered
//
// Returns nothing
/**
* Starts a single connection on Robot
*
* @param {Object} connection to start
* @param {Function} callback function to be triggered after the connection is
* started
* @return {void}
*/
Robot.prototype.startConnection = function(connection, callback) {
if (connection.connected === true) {
return callback.call(connection);
}
var str = "Starting connection '" + connection.name + "'";
if (connection.host) {
str += " on host " + connection.host;
} else if (connection.port) {
str += " on port " + connection.port;
}
this.log(str + ".");
this[connection.name] = connection;
connection.connect.call(connection, callback);
connection.connected = true;
return true;
};
/**
* Starts the Robot's devices
*
* @param {Function} callback function to be triggered after the devices are
* started
* @return {void}
*/
Robot.prototype.startDevices = function(callback) {
var log = this.log;
log("info", "Starting devices.");
var starters = _.map(this.devices, function(device, name) {
this[name] = device;
log("Starting devices.");
var starters = _.map(this.devices, function(device) {
return function(cb) {
var str = "Starting device '" + name + "'";
return this.startDevice(device, cb);
}.bind(this);
}, this);
if (device.pin) {
return _.parallel(starters, callback);
};
/**
* Starts a single device on Robot
*
* @param {Object} device to start
* @param {Function} callback function to be triggered after the device is
* started
* @return {void}
*/
Robot.prototype.startDevice = function(device, callback) {
if (device.started === true) {
return callback.call(device);
}
var log = this.log;
var str = "Starting device '" + device.name + "'";
if (device.pin || device.pin === 0) {
str += " on pin " + device.pin;
}
log("debug", str + ".");
return device.start.call(device, cb);
};
}, this);
log(str + ".");
this[device.name] = device;
device.start.call(device, callback);
device.started = true;
return Async.parallel(starters, callback);
return device.started;
};
// Public: Halts the Robot.
//
// Halts the devices, disconnects the connections.
//
// callback - callback to be triggered when the Robot is stopped
//
// Returns nothing
/**
* Halts the Robot, attempting to gracefully stop devices and connections.
*
* @param {Function} callback to be triggered when the Robot has stopped
* @return {void}
*/
Robot.prototype.halt = function(callback) {
callback = callback || function() {};
if (!this.isRunning) {
if (!this.running) {
return callback();
}
var devices = _.pluck(this.devices, "halt"),
connections = _.pluck(this.connections, "disconnect");
// ensures callback(err) won't prevent others from halting
function wrap(fn) {
return function(cb) { fn.call(null, cb.bind(null, null)); };
}
var devices = _.pluck(this.devices, "halt").map(wrap),
connections = _.pluck(this.connections, "disconnect").map(wrap);
try {
Async.parallel(devices, function() {
Async.parallel(connections, callback);
_.parallel(devices, function() {
_.parallel(connections, callback);
});
} catch (e) {
var msg = "An error occured while attempting to safely halt the robot";
this.log("error", msg);
this.log("error", e.message);
this.log(msg);
this.log(e.message);
}
this.running = false;
};
/**
* Starts a new REPL in the context of the Robot.
* Generates a String representation of a Robot
*
* @return {void}
* @return {String} representation of a Robot
*/
Robot.prototype.repl = function() {
var context = {};
_.extend(context, this.connections);
_.extend(context, this.devices);
var repl = new Repl({ prompt: this.name + " > " }, context);
repl.start();
};
// Public: Returns basic info about the robot as a String
//
// Returns a String
Robot.prototype.toString = function() {
return "[Robot name='" + this.name + "']";
};
Robot.prototype.log = function(level) {
var args = Array.prototype.slice.call(arguments, 1);
args.unshift("[" + this.name + "] -");
Logger[level].apply(null, args);
Robot.prototype.log = function(str) {
Logger.log("[" + this.name + "] - " + str);
};

View File

@ -1,19 +1,9 @@
/*
* Cylon.js - Loopback adaptor
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var Adaptor = require("../adaptor"),
Utils = require("../utils");
var Loopback;
module.exports = Loopback = function Loopback() {
var Loopback = module.exports = function Loopback() {
Loopback.__super__.constructor.apply(this, arguments);
};

View File

@ -1,11 +1,3 @@
/*
* Cylon.js - Ping driver
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var Driver = require("../driver"),

View File

@ -1,23 +1,22 @@
/*
* Cylon.js - Test Adaptor
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var Adaptor = require("../adaptor"),
Utils = require("../utils");
var TestAdaptor;
module.exports = TestAdaptor = function TestAdaptor() {
var TestAdaptor = module.exports = function TestAdaptor() {
TestAdaptor.__super__.constructor.apply(this, arguments);
};
Utils.subclass(TestAdaptor, Adaptor);
TestAdaptor.prototype.connect = function(callback) {
callback();
};
TestAdaptor.prototype.disconnect = function(callback) {
callback();
};
TestAdaptor.adaptors = ["test"];
TestAdaptor.adaptor = function(opts) { return new TestAdaptor(opts); };

View File

@ -1,23 +1,21 @@
/*
* Cylon.js - Test Driver
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var Driver = require("../driver"),
Utils = require("../utils");
var TestDriver;
module.exports = TestDriver = function TestDriver() {
var TestDriver = module.exports = function TestDriver() {
TestDriver.__super__.constructor.apply(this, arguments);
};
Utils.subclass(TestDriver, Driver);
TestDriver.prototype.start = function(callback) {
callback();
};
TestDriver.prototype.halt = function(callback) {
callback();
};
TestDriver.drivers = ["test"];
TestDriver.driver = function(opts) { return new TestDriver(opts); };

View File

@ -1,11 +1,3 @@
/*
* Cylon - Utils
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var _ = require("./utils/helpers");
@ -13,61 +5,63 @@ var _ = require("./utils/helpers");
var monkeyPatches = require("./utils/monkey-patches");
var Utils = module.exports = {
// Public: Alias to setInterval, combined with Number monkeypatches below to
// create an artoo-like syntax.
//
// interval - interval to run action on
// action - action to perform at interval
//
// Examples
//
// every((5).seconds(), function() {
// console.log("Hello world (and again in 5 seconds)!");
// });
//
// Returns an interval
/**
* A wrapper around setInterval to provide a more english-like syntax
* (e.g. "every 5 seconds, do this thing")
*
* @param {Number} interval delay between action invocations
* @param {Function} action function to trigger every time interval elapses
* @example every((5).seconds(), function() {});
* @return {intervalID} setInterval ID to pass to clearInterval()
*/
every: function every(interval, action) {
return setInterval(action, interval);
},
// Public: Alias to setTimeout, combined with Number monkeypatches below to
// create an artoo-like syntax.
//
// interval - interval to run action on
// action - action to perform at interval
//
// Examples
//
// after((10).seconds(), function() {
// console.log("Hello world from ten seconds ago!");
// });
//
// Returns an interval
/**
* A wrapper around setInterval to provide a more english-like syntax
* (e.g. "after 5 seconds, do this thing")
*
* @param {Number} delay how long to wait
* @param {Function} action action to perform after delay
* @example after((5).seconds(), function() {});
* @return {timeoutId} setTimeout ID to pass to clearTimeout()
*/
after: function after(delay, action) {
return setTimeout(action, delay);
},
// Public: Alias to the `every` function, but passing 0
// Examples
//
// constantly(function() {
// console.log("hello world (and again and again)!");
// });
//
// Returns an interval
/**
* A wrapper around setInterval, with a delay of 0ms
*
* @param {Function} action function to invoke constantly
* @example constantly(function() {});
* @return {intervalID} setInterval ID to pass to clearInterval()
*/
constantly: function constantly(action) {
return every(0, action);
},
// Public: Sleep - do nothing for some duration of time.
//
// ms - number of ms to sleep for
//
// Examples
//
// sleep((1).second());
//
// Returns a function
/**
* A wrapper around clearInterval
*
* @param {intervalID} intervalID ID of every/after/constantly
* @example finish(blinking);
* @return {void}
*/
finish: function finish(intervalID) {
clearInterval(intervalID);
},
/**
* Sleeps the program for a period of time.
*
* Use of this is not advised, as your program can't do anything else while
* it's running.
*
* @param {Number} ms number of milliseconds to sleep
* @return {void}
*/
sleep: function sleep(ms) {
var start = Date.now(),
i = 0;
@ -77,19 +71,17 @@ var Utils = module.exports = {
}
},
// Public: Function to use for class inheritance.
// Based on CoffeeScript's implementation.
//
// Example
//
// var Sphero = function Sphero() {};
//
// subclass(Sphero, ParentClass);
//
// // Sphero is now a subclass of Parent, and can access parent methods
// // through Sphero.__super__
//
// Returns subclass
/**
* Utility for providing class inheritance.
*
* Based on CoffeeScript's implementation of inheritance.
*
* Parent class methods/properites are available on Child.__super__.
*
* @param {Function} child the descendent class
* @param {Function} parent the parent class
* @return {Function} the child class
*/
subclass: function subclass(child, parent) {
var Ctor = function() {
this.constructor = child;
@ -115,16 +107,15 @@ var Utils = module.exports = {
});
},
// Public: Proxies a list of methods from one object to another. It will not
// overwrite existing methods unless told to.
//
// methods - array of functions to proxy
// target - object to proxy the functions to
// base - (optional) object that proxied functions will be declared on.
// Defaults to 'this'.
// force - (optional) boolean - whether or not to force method assignment
//
// Returns base
/**
* Proxies calls from all methods in the source to a target object
*
* @param {String[]} methods methods to proxy
* @param {Object} target object to proxy methods to
* @param {Object} [base=this] object to proxy methods from
* @param {Boolean} [force=false] whether to overwrite existing methods
* @return {Object} the base
*/
proxyFunctionsToObject: function(methods, target, base, force) {
if (base == null) {
base = this;
@ -145,39 +136,37 @@ var Utils = module.exports = {
return base;
},
// Public: Analogue to Ruby"s Hash#fetch method for looking up object
// properties.
//
// obj - object to do property lookup on
// property - string property name to attempt to look up
// fallback - either:
// - a fallback value to return if `property` can"t be found
// - a function to be executed if `property` can"t be found. The function
// will be passed `property` as an argument.
//
// Examples
//
// var object = { property: "hello world" };
// fetch(object, "property");
// //=> "hello world"
//
// fetch(object, "notaproperty", "default value");
// //=> "default value"
//
// var notFound = function(prop) { return prop + " not found!" };
// fetch(object, "notaproperty", notFound)
// // "notaproperty not found!"
//
// var badFallback = function(prop) { prop + " not found!" };
// fetch(object, "notaproperty", badFallback)
// // Error: no return value from provided callback function
//
// fetch(object, "notaproperty");
// // Error: key not found: "notaproperty"
//
// Returns the value of obj[property], a fallback value, or the results of
// running "fallback". If the property isn"t found, and "fallback" hasn"t been
// provided, will throw an error.
classCallCheck: function(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
},
/**
* Approximation of Ruby's Hash#fetch method for object property lookup
*
* @param {Object} obj object to do lookup on
* @param {String} property property name to attempt to access
* @param {*} fallback a fallback value if property can't be found. if
* a function, will be invoked with the string property name.
* @throws Error if fallback needed but not provided, or fallback fn doesn't
* return anything
* @example
* fetch({ property: "hello world" }, "property"); //=> "hello world"
* @example
* fetch({}, "notaproperty", "default value"); //=> "default value"
* @example
* var notFound = function(prop) { return prop + " not found!" };
* fetch({}, "notaproperty", notFound); //=> "notaproperty not found!"
* @example
* var badFallback = function(prop) { prop + " not found!" };
* fetch({}, "notaproperty", badFallback);
* // Error: no return value from provided callback function
* @example
* fetch(object, "notaproperty");
* // Error: key not found: "notaproperty"
* @return {*} fetched property, fallback, or fallback function return value
*/
fetch: function(obj, property, fallback) {
if (obj.hasOwnProperty(property)) {
return obj[property];
@ -200,13 +189,15 @@ var Utils = module.exports = {
return fallback;
},
// Public: Given a name, and an array of existing names, returns a unique
// name.
//
// name - name that"s colliding with existing names
// arr - array of existing names
//
// Returns the new name as a string
/**
* Given a name, and an array of existing names, returns a unique new name
*
* @param {String} name the name that's colliding with existing names
* @param {String[]} arr array of existing names
* @example
* makeUnique("hello", ["hello", "hello-1", "hello-2"]); //=> "hello3"
* @return {String} the new name
*/
makeUnique: function(name, arr) {
var newName;
@ -222,20 +213,12 @@ var Utils = module.exports = {
}
},
// Public: Adds necessary utils to global namespace, along with base class
// extensions.
//
// Examples
//
// Number.prototype.seconds // undefined
// after // undefined
//
// Utils.bootstrap();
//
// Number.prototype.seconds // [function]
// (after === Utils.after) // true
//
// Returns Cylon.Utils
/**
* Adds every/after/constantly to the global namespace, and installs
* monkey-patches.
*
* @return {Object} utils object
*/
bootstrap: function bootstrap() {
global.every = this.every;
global.after = this.after;

View File

@ -1,13 +1,7 @@
/**
* Cylon.js - Helper Utilities
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
/* eslint no-use-before-define: 0 */
var __slice = Array.prototype.slice;
var H = module.exports = {};
@ -210,3 +204,72 @@ function includes(arr, value) {
extend(H, {
includes: includes
});
function parallel(functions, done) {
var total = functions.length,
completed = 0,
results = [],
error;
if (typeof done !== "function") { done = function() {}; }
function callback(err, result) {
if (error) {
return;
}
if (err || error) {
error = err;
done(err);
return;
}
completed++;
results.push(result);
if (completed === total) {
done(null, results);
}
}
if (!functions.length) { done(); }
functions.forEach(function(fn) { fn(callback); });
}
extend(H, {
parallel: parallel
});
function series(functions, done) {
var results = [],
error;
if (typeof done !== "function") { done = function() {}; }
function callback(err, result) {
if (err || error) {
error = err;
return done(err);
}
results.push(result);
if (!functions.length) {
return done(null, results);
}
next();
}
function next() {
functions.shift()(callback);
}
if (!functions.length) { done(null, results); }
next();
}
extend(H, {
series: series
});

View File

@ -1,40 +1,53 @@
/**
* Cylon.js - Monkey Patches
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
/* eslint no-extend-native: 0 */
/* eslint no-extend-native: 0 key-spacing: 0 */
"use strict";
var max = Math.max,
min = Math.min;
var originals = {
seconds: Number.prototype.seconds,
second: Number.prototype.second,
fromScale: Number.prototype.fromScale,
toScale: Number.prototype.toScale
};
module.exports.uninstall = function() {
for (var opt in originals) {
if (originals[opt] == null) {
Number.prototype[opt] = originals[opt];
} else {
delete Number.prototype[opt];
}
}
};
module.exports.install = function() {
// Public: Monkey-patches Number to have Rails-like //seconds() function.
// Warning, due to the way the Javascript parser works, applying functions on
// numbers is kind of weird. See examples for details.
//
// Examples
//
// 2.seconds()
// //=> SyntaxError: Unexpected token ILLEGAL
//
// 10..seconds()
// //=> 10000
//
// (5).seconds()
// //=> 5000
// // This is the preferred way to represent numbers when calling these
// // methods on them
//
// Returns an integer representing time in milliseconds
/**
* Multiplies a number by 60000 to convert minutes
* to milliseconds
*
* @example
* (2).minutes(); //=> 120000
* @return {Number} time in milliseconds
*/
Number.prototype.minutes = function() {
return this * 60000;
};
/**
* Multiplies a number by 1000 to get time in milliseconds
* Alias for Number.prototype.minutes
*
* @see Number.prototype.minute
* @example
* (1).minute(); //=>60000
* @return {Number} time in milliseconds
*/
Number.prototype.minute = Number.prototype.minutes;
/**
* Multiplies a number by 1000 to convert seconds
* to milliseconds
*
* @example
* (2).seconds(); //=> 2000
@ -54,6 +67,40 @@ module.exports.install = function() {
*/
Number.prototype.second = Number.prototype.seconds;
/**
* Passthru to get time in milliseconds
*
* @example
* (200).milliseconds(); //=> 200
* @return {Number} time in milliseconds
*/
Number.prototype.milliseconds = function() {
return this;
};
/**
* Alias for Number.prototype.milliseconds
*
* @see Number.prototype.milliseconds
* @example
* (100).ms(); //=> 100
* @return {Number} time in milliseconds
*/
Number.prototype.ms = Number.prototype.milliseconds;
/**
* Converts microseconds to milliseconds.
* Note that timing of events in terms of microseconds
* is not very accurate in JS.
*
* @example
* (2000).microseconds(); //=> 2
* @return {Number} time in milliseconds
*/
Number.prototype.microseconds = function() {
return this / 1000;
};
/**
* Converts a number from a current scale to a 0 - 1 scale.
*

102
lib/validator.js Normal file
View File

@ -0,0 +1,102 @@
"use strict";
// validates an Object containing Robot parameters
var Logger = require("./logger"),
_ = require("./utils/helpers");
function hasProp(object, prop) {
return object.hasOwnProperty(prop);
}
function die() {
var RobotDSLError = new Error("Unable to start robot due to a syntax error");
RobotDSLError.name = "RobotDSLError";
throw RobotDSLError;
}
function warn(messages) {
messages = [].concat(messages);
messages.map(function(msg) { Logger.log(msg); });
}
function fatal(messages) {
messages = [].concat(messages);
messages.map(function(msg) { Logger.log(msg); });
die();
}
var checks = {};
checks.singleObjectSyntax = function(opts, key) {
var single = hasProp(opts, key),
plural = hasProp(opts, key + "s");
if (single && !plural) {
fatal([
"The single-object '" + key + "' syntax for robots is not valid.",
"Instead, use the multiple-value '" + key + "s' key syntax.",
"Details: http://cylonjs.com/documentation/guides/working-with-robots/"
]);
}
};
checks.singleObjectSyntax = function(opts) {
["connection", "device"].map(function(key) {
var single = hasProp(opts, key),
plural = hasProp(opts, key + "s");
if (single && !plural) {
fatal([
"The single-object '" + key + "' syntax for robots is not valid.",
"Instead, use the multiple-value '" + key + "s' key syntax.",
"Details: http://cylonjs.com/documentation/guides/working-with-robots/"
]);
}
});
};
checks.deviceWithoutDriver = function(opts) {
if (opts.devices) {
_.each(opts.devices, function(device, name) {
if (!device.driver || device.driver === "") {
fatal("No driver supplied for device " + name);
}
});
}
};
checks.devicesWithoutConnection = function(opts) {
var connections = opts.connections,
devices = opts.devices;
if (devices && connections && Object.keys(connections).length > 1) {
var first = Object.keys(connections)[0];
_.each(devices, function(device, name) {
if (!device.connection || device.connection === "") {
warn([
"No explicit connection provided for device " + name,
"Will default to using connection " + first
]);
}
});
}
};
checks.noConnections = function(opts) {
var connections = Object.keys(opts.connections || {}).length,
devices = Object.keys(opts.devices || {}).length;
if (devices && !connections) {
fatal(["No connections provided for devices"]);
}
};
module.exports.validate = function validate(opts) {
opts = opts || {};
_.each(checks, function(check) {
check(opts);
});
};

View File

@ -1,19 +1,14 @@
{
"name": "cylon",
"version": "1.0.0",
"main": "lib/cylon.js",
"description": "A JavaScript robotics framework for Node.js",
"version": "1.3.0",
"description": "JavaScript framework for robotics, drones, and the Internet of Things (IoT) using Node.js",
"homepage": "http://cylonjs.com",
"bugs": "https://github.com/hybridgroup/cylon/issues",
"author": "The Hybrid Group <cylonjs@hybridgroup.com>",
"contributors": [
"Ron Evans <ron@hybridgroup.com>",
"Andrew Stewart <andrew@hybridgroup.com>",
"Edgar Silva <edgar@hybridgroup.com>",
"Mario 'Kuroir' Ricalde <mario@hybridgroup.com>",
"Adrian Zankich <adrian@hybridgroup.com>"
"Contributors List (https://github.com/hybridgroup/cylon/blob/master/CONTRIBUTORS.markdown)"
],
"repository": {
@ -21,13 +16,26 @@
"url": "https://github.com/hybridgroup/cylon"
},
"license": "Apache 2.0",
"license": "Apache-2.0",
"keywords": [
"cylon",
"cylonjs",
"cylons",
"robot",
"robots",
"robotics",
"iot",
"hardware",
"drones",
"internet of things"
],
"hardware": {
"*": false,
"./": false,
"async": true,
"./lib": true
"./lib": true,
"index.js": true
},
"engines" : {
@ -39,10 +47,6 @@
"chai": "2.2.0",
"mocha": "2.2.4",
"sinon": "1.14.1",
"eslint": "0.19.0"
},
"dependencies": {
"async": "0.9.0"
"eslint": "0.22.1"
}
}

View File

@ -3,7 +3,7 @@ env:
globals:
expect: true
source: true
lib: true
stub: true
spy: true
chai: true

View File

@ -22,17 +22,15 @@ global.spy = sinon.spy;
global.stub = sinon.stub;
// convenience function to require modules in lib directory
global.source = function(module) {
global.lib = function(module) {
return require(path.normalize("./../lib/" + module));
};
var Cylon = source("cylon");
var Cylon = require("./../");
Cylon.config({
mode: "manual",
logging: {
logger: false
}
silent: true
});
Cylon.Logger.setup();

View File

@ -1,7 +1,7 @@
"use strict";
var Adaptor = source("adaptor"),
Utils = source("utils");
var Adaptor = lib("adaptor"),
Utils = lib("utils");
describe("Adaptor", function() {
var adaptor;

41
spec/lib/api.spec.js Normal file
View File

@ -0,0 +1,41 @@
"use strict";
var API = lib("api"),
MCP = lib("mcp");
describe("API", function() {
describe("#create", function() {
afterEach(function() {
API.instances = [];
});
context("with a provided API server and opts", function() {
var Server, opts, instance;
beforeEach(function() {
instance = { start: spy() };
opts = { https: false };
Server = stub().returns(instance);
API.create(Server, opts);
});
it("creates an API instance", function() {
expect(Server).to.be.calledWithNew;
expect(Server).to.be.calledWith(opts);
});
it("passes MCP through to the instance as opts.mcp", function() {
expect(opts.mcp).to.be.eql(MCP);
});
it("stores the API instance in @instances", function() {
expect(API.instances).to.be.eql([instance]);
});
it("tells the API instance to start", function() {
expect(instance.start).to.be.called;
});
});
});
});

View File

@ -1,7 +1,7 @@
"use strict";
var Basestar = source("basestar"),
Utils = source("utils");
var Basestar = lib("basestar"),
Utils = lib("utils");
var EventEmitter = require("events").EventEmitter;

84
spec/lib/config.spec.js Normal file
View File

@ -0,0 +1,84 @@
"use strict";
var config = lib("config");
describe("config", function() {
it("contains configuration options", function() {
expect(config.testMode).to.be.eql(false);
});
describe("#update", function() {
var callback;
beforeEach(function() {
callback = spy();
config.subscribe(callback);
});
afterEach(function() {
config.unsubscribe(callback);
delete config.newValue;
});
it("updates the configuration", function() {
expect(config.newValue).to.be.eql(undefined);
config.update({ newValue: "value" });
expect(config.newValue).to.be.eql("value");
});
it("notifies subscribers of changes", function() {
var update = { newValue: "value" };
expect(callback).to.not.be.called;
config.update(update);
expect(callback).to.be.calledWith(update);
});
it("rejects changes that conflict with config functions", function() {
config.update({ update: null });
expect(config.update).to.be.a("function");
});
it("does nothing with empty changesets", function() {
config.update({});
expect(callback).to.not.be.called;
});
});
describe("#subscribe", function() {
var callback = spy();
afterEach(function() {
delete config.test;
config.unsubscribe(callback);
});
it("subscribes a callback to change updates", function() {
config.subscribe(callback);
config.update({ test: true });
expect(callback).to.be.calledWith({ test: true });
});
});
describe("#unsubscribe", function() {
var callback;
beforeEach(function() {
callback = spy();
config.subscribe(callback);
});
afterEach(function() {
delete config.test;
});
it("unsubscribes a callback from change updates", function() {
config.update({ test: true });
expect(callback).to.be.called;
config.unsubscribe(callback);
config.update({ test: false });
expect(callback).to.be.calledOnce;
});
});
});

View File

@ -1,201 +1,78 @@
"use strict";
var Cylon = source("cylon"),
Robot = source("robot");
var Cylon = lib("../index");
var Logger = source("logger"),
Adaptor = source("adaptor"),
Driver = source("driver"),
Config = source("config");
var MCP = lib("mcp"),
API = lib("api"),
Robot = lib("robot"),
Driver = lib("driver"),
Adaptor = lib("adaptor"),
Utils = lib("utils"),
Config = lib("config"),
Logger = lib("logger");
var IO = {
DigitalPin: lib("io/digital-pin"),
Utils: lib("io/utils")
};
describe("Cylon", function() {
describe("exports", function() {
it("sets Logger to the Logger module", function() {
expect(Cylon.Logger).to.be.eql(Logger);
it("exports the MCP as Cylon.MCP", function() {
expect(Cylon.MCP).to.be.eql(MCP);
});
it("sets Adaptor to the Adaptor module", function() {
expect(Cylon.Adaptor).to.be.eql(Adaptor);
it("exports the Robot as Cylon.Robot", function() {
expect(Cylon.Robot).to.be.eql(Robot);
});
it("sets Driver to the Driver module", function() {
it("exports the Driver as Cylon.Driver", function() {
expect(Cylon.Driver).to.be.eql(Driver);
});
it("sets @apiInstances to an empty array by default", function() {
expect(Cylon.apiInstances).to.be.eql([]);
it("exports the Adaptor as Cylon.Adaptor", function() {
expect(Cylon.Adaptor).to.be.eql(Adaptor);
});
it("sets @robots to an empty object by default", function() {
expect(Cylon.robots).to.be.eql({});
it("exports the Utils as Cylon.Utils", function() {
expect(Cylon.Utils).to.be.eql(Utils);
});
it("sets @robots to an empty object by default", function() {
expect(Cylon.commands).to.be.eql({});
it("exports the Logger as Cylon.Logger", function() {
expect(Cylon.Logger).to.be.eql(Logger);
});
it("exports the IO DigitalPin and Utils as Cylon.IO", function() {
expect(Cylon.IO).to.be.eql(IO);
});
describe("#robot", function() {
afterEach(function() {
Cylon.robots = {};
});
it("uses passed options to create a new Robot", function() {
var opts = { name: "Ultron" };
var robot = Cylon.robot(opts);
expect(robot.toString()).to.be.eql("[Robot name='Ultron']");
expect(Cylon.robots.Ultron).to.be.eql(robot);
});
it("avoids duplicating names", function() {
Cylon.robot({ name: "Ultron" });
Cylon.robot({ name: "Ultron" });
var bots = Object.keys(Cylon.robots);
expect(bots).to.be.eql(["Ultron", "Ultron-1"]);
});
});
describe("#api", function() {
afterEach(function() {
Cylon.apiInstances = [];
});
context("with a provided API server and opts", function() {
var API, opts, instance;
beforeEach(function() {
instance = { start: spy() };
opts = { https: false };
API = stub().returns(instance);
Cylon.api(API, opts);
});
it("creates an API instance", function() {
expect(API).to.be.calledWithNew;
expect(API).to.be.calledWith(opts);
});
it("passes Cylon through to the instance as opts.mcp", function() {
expect(opts.mcp).to.be.eql(Cylon);
});
it("stores the API instance in @apiInstances", function() {
expect(Cylon.apiInstances).to.be.eql([instance]);
});
it("tells the API instance to start", function() {
expect(instance.start).to.be.called;
});
it("proxies to MCP.create", function() {
expect(Cylon.robot).to.be.eql(MCP.create);
});
});
describe("#start", function() {
it("calls #start() on all robots", function() {
var bot1 = { start: spy() },
bot2 = { start: spy() };
Cylon.robots = {
bot1: bot1,
bot2: bot2
};
Cylon.start();
expect(bot1.start).to.be.called;
expect(bot2.start).to.be.called;
});
});
describe("#config", function() {
beforeEach(function() {
for (var c in Config) {
delete Config[c];
}
stub(Logger, "setup");
});
afterEach(function() {
Logger.setup.restore();
});
it("sets config variables", function() {
Cylon.config({ a: 1, b: 2 });
expect(Config.a).to.be.eql(1);
expect(Config.b).to.be.eql(2);
});
it("updates existing config", function() {
Cylon.config({ a: 1, b: 2 });
Cylon.config({ a: 3 });
expect(Config.a).to.be.eql(3);
expect(Config.b).to.be.eql(2);
});
it("returns updated config", function() {
var config = Cylon.config({ a: 1, b: 2 });
expect(Config).to.be.eql(config);
});
it("doesn't ignores non-object arguments", function() {
var config = Cylon.config({ a: 1, b: 2 });
Cylon.config(["a", 1, "b", 2]);
Cylon.config("hello world");
expect(Config).to.be.eql(config);
});
it("updates the Logger setup if that changed", function() {
Cylon.config({ a: 1 });
expect(Logger.setup).to.not.be.called;
Cylon.config({ a: 1, logging: { logger: false } });
expect(Logger.setup).to.be.called;
it("proxies to MCP.start", function() {
expect(Cylon.start).to.be.eql(MCP.start);
});
});
describe("#halt", function() {
it("calls #halt() on all robots", function() {
var bot1 = { halt: spy() },
bot2 = { halt: spy() };
Cylon.robots = {
bot1: bot1,
bot2: bot2
};
Cylon.halt();
expect(bot1.halt).to.be.called;
expect(bot2.halt).to.be.called;
it("proxies to MCP.halt", function() {
expect(Cylon.halt).to.be.eql(MCP.halt);
});
});
describe("#toJSON", function() {
var json, bot1, bot2;
beforeEach(function() {
bot1 = new Robot();
bot2 = new Robot();
Cylon.robots = { bot1: bot1, bot2: bot2 };
Cylon.commands.echo = function(arg) { return arg; };
json = Cylon.toJSON();
describe("#api", function() {
it("proxies to API.create", function() {
expect(Cylon.api).to.be.eql(API.create);
});
});
it("contains all robots the MCP knows about", function() {
expect(json.robots).to.be.eql([bot1.toJSON(), bot2.toJSON()]);
});
it("contains an array of MCP commands", function() {
expect(json.commands).to.be.eql(["echo"]);
});
it("contains an array of MCP events", function() {
expect(json.events).to.be.eql(["robot_added", "robot_removed"]);
describe("#config", function() {
it("proxies to Config.update", function() {
expect(Cylon.config).to.be.eql(Config.update);
});
});
});

View File

@ -3,8 +3,8 @@
var fs = require("fs");
var DigitalPin = source("io/digital-pin"),
Utils = source("utils");
var DigitalPin = lib("io/digital-pin"),
Utils = lib("utils");
describe("Cylon.IO.DigitalPin", function() {
var pin = new DigitalPin({ pin: "4", mode: "w" });

View File

@ -1,7 +1,7 @@
"use strict";
var Driver = source("driver"),
Utils = source("utils");
var Driver = lib("driver"),
Utils = lib("utils");
describe("Driver", function() {
var connection, driver;
@ -139,8 +139,23 @@ describe("Driver", function() {
});
it("handles edge cases", function() {
var commands = ["HelloWorld", "getPNGStream", "getHSetting"],
snake_case = ["hello_world", "get_png_stream", "get_h_setting"];
var commands = [
"HelloWorld",
"getPNGStream",
"getHSetting",
"getRGB",
"convertRGBToHSL",
"getTemperatureInF"
];
var snake_case = [
"hello_world",
"get_png_stream",
"get_h_setting",
"get_rgb",
"convert_rgb_to_hsl",
"get_temperature_in_f"
];
commands.forEach(function(cmd) {
driver[cmd] = function() {};

View File

@ -0,0 +1,101 @@
"use strict";
var initializer = lib("initializer"),
Registry = lib("registry"),
Config = lib("config");
var Loopback = lib("test/loopback"),
Ping = lib("test/ping"),
TestAdaptor = lib("test/test-adaptor"),
TestDriver = lib("test/test-driver");
describe("Initializer", function() {
beforeEach(function() {
spy(Registry, "findBy");
stub(Registry, "register");
});
afterEach(function() {
Registry.findBy.restore();
Registry.register.restore();
});
it("creates an instance of the requested adaptor/driver", function() {
var adaptor = initializer("adaptor", { adaptor: "loopback" });
expect(adaptor).to.be.an.instanceOf(Loopback);
var driver = initializer("driver", { driver: "ping" });
expect(driver).to.be.an.instanceOf(Ping);
});
context("if the module isn't registered", function() {
var module;
beforeEach(function() {
Registry.findBy.restore();
module = {
adaptor: stub(),
driver: stub()
};
stub(Registry, "findBy")
.onFirstCall().returns(false)
.onSecondCall().returns(module);
});
context("if a module key was provided", function() {
it("attempts to register it", function() {
initializer("adaptor", { adaptor: "adaptor", module: "test" });
expect(Registry.register).to.be.calledWith("test");
expect(module.adaptor).to.be.called;
});
});
context("if no module key was provided", function() {
it("attempts to find it automatically", function() {
initializer("driver", { driver: "driver" });
expect(Registry.register).to.be.calledWith("cylon-driver");
expect(module.driver).to.be.called;
});
});
context("if the module still can't be found", function() {
beforeEach(function() {
Registry.findBy.onSecondCall().returns(false);
});
it("throws an error", function() {
function fn() {
return initializer("adaptor", { adaptor: "badadaptor" });
}
expect(fn).to.throw("Unable to find adaptor for badadaptor");
});
});
});
context("if in test mode", function() {
var tm = Config.testMode, adaptor, driver;
beforeEach(function() {
Config.testMode = true;
driver = initializer("driver", { driver: "ping" });
adaptor = initializer("adaptor", { adaptor: "loopback" });
});
afterEach(function() {
Config.testMode = tm;
});
it("creates a test adaptor/driver", function() {
expect(driver).to.be.an.instanceOf(TestDriver);
expect(adaptor).to.be.an.instanceOf(TestAdaptor);
});
it("stubs out the driver/adaptor behaviour", function() {
expect(driver.ping).to.be.a("function");
});
});
});

View File

@ -1,6 +1,6 @@
"use strict";
var Utils = source("io/utils.js");
var Utils = lib("io/utils.js");
describe("IOUtils", function() {
describe("#periodAndDuty", function() {

View File

@ -1,47 +1,39 @@
"use strict";
var Logger = source("logger"),
Config = source("config");
var Logger = lib("logger"),
Config = lib("config");
describe("Logger", function() {
afterEach(function() {
// to be friendly to other specs
Config.logging = { logger: false, level: "debug" };
Config.logger = false;
Config.silent = false;
Logger.setup();
});
describe("#setup", function() {
context("with no arguments", function() {
it("sets up a BasicLogger", function() {
Config.logging = {};
Config.logger = null;
Logger.setup();
expect(Logger.toString()).to.be.eql("BasicLogger");
expect(Logger.logger.name).to.be.eql("basiclogger");
});
});
context("with false", function() {
it("sets up a NullLogger", function() {
Config.logging = { logger: false };
Config.logger = false;
Logger.setup();
expect(Logger.toString()).to.be.eql("NullLogger");
expect(Logger.logger.name).to.be.eql("nulllogger");
});
});
context("with a custom logger", function() {
it("uses the custom logger", function() {
var logger = { toString: function() { return "custom"; } };
Config.logging = { logger: logger };
function customlogger() {}
Config.logger = customlogger;
Logger.setup();
expect(Logger.toString()).to.be.eql("custom");
});
});
context("with a custom logger, provided directly", function() {
it("uses the custom logger", function() {
var logger = { toString: function() { return "custom"; } };
Logger.setup({ logger: logger });
expect(Logger.toString()).to.be.eql("custom");
expect(Config.logging.logger).to.be.eql(logger);
expect(Logger.logger.name).to.be.eql("customlogger");
});
});
});
@ -50,100 +42,31 @@ describe("Logger", function() {
var logger;
beforeEach(function() {
logger = Config.logging.logger = {
debug: spy(),
info: spy(),
warn: spy(),
error: spy(),
fatal: spy()
};
Logger.setup();
logger = spy();
Logger.logger = logger;
});
describe("#debug", function() {
it("proxies to the Logger's #debug method", function() {
Logger.debug("Hello", "World");
expect(logger.debug).to.be.calledWith("Hello", "World");
});
});
describe("#info", function() {
it("proxies to the Logger's #info method", function() {
Logger.info("Hello", "World");
expect(logger.info).to.be.calledWith("Hello", "World");
});
});
describe("#warn", function() {
it("proxies to the Logger's #warn method", function() {
Logger.warn("Hello", "World");
expect(logger.warn).to.be.calledWith("Hello", "World");
});
});
describe("#error", function() {
it("proxies to the Logger's #error method", function() {
Logger.error("Hello", "World");
expect(logger.error).to.be.calledWith("Hello", "World");
});
});
describe("#fatal", function() {
it("proxies to the Logger's #fatal method", function() {
Logger.fatal("Hello", "World");
expect(logger.fatal).to.be.calledWith("Hello", "World");
});
});
});
describe("log levels", function() {
var logger;
beforeEach(function() {
logger = {
debug: spy(),
info: spy(),
warn: spy(),
error: spy(),
fatal: spy()
};
Config.logging = {
logger: logger,
level: "warn"
};
Logger.setup();
});
it("prevents logging below the specified level", function() {
it("proxies to the logger method", function() {
Logger.should.debug = true;
Logger.debug("debug message");
Logger.info("info message");
expect(logger.debug).to.not.be.called;
expect(logger.info).to.not.be.called;
Logger.should.debug = false;
expect(logger).to.be.calledWith("debug message");
});
});
it("still logs levels equal/greater than the specified level", function() {
Logger.warn("warn message");
Logger.error("error message");
Logger.fatal("fatal message");
expect(logger.warn).to.be.calledWith("warn message");
expect(logger.error).to.be.calledWith("error message");
expect(logger.fatal).to.be.calledWith("fatal message");
});
it("defaults to 'info' level", function() {
delete Config.logging.level;
Logger.setup();
Logger.debug("debug message");
Logger.info("info message");
expect(logger.debug).to.not.be.called;
expect(logger.info).to.be.calledWith("info message");
describe("#log", function() {
it("proxies to the logger method", function() {
Logger.log("log message");
expect(logger).to.be.calledWith("log message");
});
});
});
it("automatically updates if configuration changed", function() {
var custom = spy();
expect(Logger.logger.name).to.be.eql("basiclogger");
Config.update({ logger: custom });
expect(Logger.logger).to.be.eql(custom);
});
});

View File

@ -1,70 +0,0 @@
"use strict";
var logger = source("logger/basic_logger");
var date = new Date(0).toISOString();
describe("BasicLogger", function() {
var clock;
beforeEach(function() {
stub(console, "log");
clock = sinon.useFakeTimers(0);
});
afterEach(function() {
console.log.restore();
clock.restore();
});
describe("#toString", function() {
it("returns 'BasicLogger'", function() {
expect(logger.toString()).to.be.eql("BasicLogger");
});
});
describe("#debug", function() {
it("logs to the console with a debug string", function() {
var logstring = "D, [" + date + "] DEBUG -- :";
logger.debug("Hello, World");
expect(console.log).to.be.calledWith(logstring, "Hello, World");
});
});
describe("#info", function() {
it("logs to the console with a info string", function() {
var logstring = "I, [" + date + "] INFO -- :";
logger.info("Hello, World");
expect(console.log).to.be.calledWith(logstring, "Hello, World");
});
});
describe("#warn", function() {
it("logs to the console with a warn string", function() {
var logstring = "W, [" + date + "] WARN -- :";
logger.warn("Hello, World");
expect(console.log).to.be.calledWith(logstring, "Hello, World");
});
});
describe("#error", function() {
it("logs to the console with a error string", function() {
var logstring = "E, [" + date + "] ERROR -- :";
logger.error("Hello, World");
expect(console.log).to.be.calledWith(logstring, "Hello, World");
});
});
describe("#fatal", function() {
it("logs to the console with a fatal string", function() {
var logstring = "F, [" + date + "] FATAL -- :";
logger.fatal("Hello, World");
expect(console.log).to.be.calledWith(logstring, "Hello, World");
});
});
});

100
spec/lib/mcp.spec.js Normal file
View File

@ -0,0 +1,100 @@
"use strict";
var MCP = lib("mcp"),
Robot = lib("robot");
describe("MCP", function() {
it("contains a collection of robots", function() {
expect(MCP.robots).to.be.eql({});
});
it("contains a collection of commands", function() {
expect(MCP.commands).to.be.eql({});
});
it("contains a collection of events", function() {
expect(MCP.events).to.be.eql(["robot_added", "robot_removed"]);
});
describe("#create", function() {
afterEach(function() {
MCP.robots = {};
});
it("uses passed options to create a new Robot", function() {
var opts = { name: "Ultron" };
var robot = MCP.create(opts);
expect(robot.toString()).to.be.eql("[Robot name='Ultron']");
expect(MCP.robots.Ultron).to.be.eql(robot);
});
it("avoids duplicating names", function() {
MCP.create({ name: "Ultron" });
MCP.create({ name: "Ultron" });
var bots = Object.keys(MCP.robots);
expect(bots).to.be.eql(["Ultron", "Ultron-1"]);
});
});
describe("#start", function() {
it("calls #start() on all robots", function() {
var bot1 = { start: spy() },
bot2 = { start: spy() };
MCP.robots = {
bot1: bot1,
bot2: bot2
};
MCP.start();
expect(bot1.start).to.be.called;
expect(bot2.start).to.be.called;
});
});
describe("#halt", function() {
it("calls #halt() on all robots", function() {
var bot1 = { halt: spy() },
bot2 = { halt: spy() };
MCP.robots = {
bot1: bot1,
bot2: bot2
};
MCP.halt();
expect(bot1.halt).to.be.called;
expect(bot2.halt).to.be.called;
});
});
describe("#toJSON", function() {
var json, bot1, bot2;
beforeEach(function() {
bot1 = new Robot();
bot2 = new Robot();
MCP.robots = { bot1: bot1, bot2: bot2 };
MCP.commands.echo = function(arg) { return arg; };
json = MCP.toJSON();
});
it("contains all robots the MCP knows about", function() {
expect(json.robots).to.be.eql([bot1.toJSON(), bot2.toJSON()]);
});
it("contains an array of MCP commands", function() {
expect(json.commands).to.be.eql(["echo"]);
});
it("contains an array of MCP events", function() {
expect(json.events).to.be.eql(["robot_added", "robot_removed"]);
});
});
});

View File

@ -1,6 +1,6 @@
"use strict";
var Registry = source("registry");
var Registry = lib("registry");
var path = "./../spec/support/mock_module.js";

View File

@ -1,9 +1,9 @@
"use strict";
var Driver = source("driver"),
Adaptor = source("adaptor"),
Robot = source("robot"),
Logger = source("logger");
var Driver = lib("driver"),
Adaptor = lib("adaptor"),
Robot = lib("robot"),
Logger = lib("logger");
describe("Robot", function() {
var work, extraFunction, robot;
@ -32,17 +32,8 @@ describe("Robot", function() {
});
context("if not provided", function() {
beforeEach(function() {
stub(Robot, "randomName").returns("New Robot");
});
afterEach(function() {
Robot.randomName.restore();
});
it("is set to a random name", function() {
var bot = new Robot({});
expect(bot.name).to.be.eql("New Robot");
it("is set to an incrementing name", function() {
expect(new Robot({}).name).to.match(/Robot \d/);
});
});
});
@ -79,7 +70,7 @@ describe("Robot", function() {
});
};
expect(fn).to.throw(Error, "No connections specified");
expect(fn).to.throw(Error);
});
});
@ -273,27 +264,20 @@ describe("Robot", function() {
});
});
describe("initConnections", function() {
describe("initRobot", function() {
var bot;
beforeEach(function() {
bot = new Robot();
});
context("when not passed anything", function() {
it("does not modify the bot's connections", function() {
bot.initConnections({});
expect(bot.connections).to.be.eql({});
});
});
context("when passed an object containing connection details", function() {
context("when connection details are provided", function() {
it("creates new connections with each of the ones provided", function() {
var connections = {
loopback: { adaptor: "loopback" }
};
bot.initConnections({ connections: connections });
bot.initRobot({ connections: connections });
expect(bot.connections.loopback).to.be.instanceOf(Adaptor);
});
@ -312,7 +296,7 @@ describe("Robot", function() {
}
};
bot.initConnections(opts);
bot.initRobot(opts);
});
it("adds the devices to opts.devices", function() {
@ -326,6 +310,23 @@ describe("Robot", function() {
});
});
});
context("when device details are provided", function() {
it("creates new devices with each of the ones provided", function() {
var opts = {
connections: {
loopback: { adaptor: "loopback" }
},
devices: {
ping: { driver: "ping" }
}
};
bot.initRobot(opts);
expect(bot.devices.ping).to.be.instanceOf(Driver);
});
});
});
describe("#device", function() {
@ -354,34 +355,7 @@ describe("Robot", function() {
});
});
describe("initDevices", function() {
var bot;
beforeEach(function() {
bot = new Robot({
connections: {
loopback: { adaptor: "loopback" }
}
});
});
context("when not passed anything", function() {
it("does not modify the bot's devices", function() {
bot.initDevices({});
expect(bot.devices).to.be.eql({});
});
});
context("when passed an object containing device details", function() {
it("creates new devices with each of the ones provided", function() {
var devices = {
ping: { driver: "ping" }
};
bot.initDevices({ devices: devices });
expect(bot.devices.ping).to.be.instanceOf(Driver);
});
});
describe("initRobot", function() {
});
describe("#start", function() {
@ -441,6 +415,13 @@ describe("Robot", function() {
expect(bot.connections.alpha.connect).to.be.called;
expect(bot.connections.bravo.connect).to.be.called;
});
it("defines a named connection on robot for each connection", function() {
bot.startConnections();
expect(bot.alpha).to.be.an.instanceOf(Adaptor);
expect(bot.bravo).to.be.an.instanceOf(Adaptor);
});
});
describe("#startDevices", function() {
@ -468,6 +449,32 @@ describe("Robot", function() {
expect(bot.devices.alpha.start).to.be.called;
expect(bot.devices.bravo.start).to.be.called;
});
it("runs #start on each device only once", function() {
bot.startDevices();
bot.startDevices();
expect(bot.devices.alpha.start).to.be.called.once;
expect(bot.devices.bravo.start).to.be.called.once;
});
it("runs #start on a newly added device", function() {
bot.startDevices();
bot.device("charlie", { driver: "ping" });
stub(bot.devices.charlie, "start").returns(true);
bot.startDevices();
expect(bot.devices.alpha.start).to.be.called.once;
expect(bot.devices.bravo.start).to.be.called.once;
expect(bot.devices.charlie.start).to.be.called.once;
});
it("defines a named device on robot for each device", function() {
bot.startDevices();
expect(bot.alpha).to.be.an.instanceOf(Driver);
expect(bot.bravo).to.be.an.instanceOf(Driver);
});
});
describe("#halt", function() {
@ -484,18 +491,13 @@ describe("Robot", function() {
}
});
bot.isRunning = true;
bot.running = true;
device = bot.devices.ping;
connection = bot.connections.loopback;
stub(device, "halt").yields(true);
stub(connection, "disconnect").yields(true);
});
afterEach(function() {
device.halt.restore();
connection.disconnect.restore();
stub(device, "halt").yields();
stub(connection, "disconnect").yields();
});
it("calls #halt on all devices and connections", function() {
@ -504,6 +506,38 @@ describe("Robot", function() {
expect(device.halt).to.be.called;
expect(connection.disconnect).to.be.called;
});
context("if a subcall triggers it's callback with an error", function() {
beforeEach(function() {
bot = new Robot({
devices: {
ping: { driver: "ping" },
ping2: { driver: "ping" }
},
connections: {
loopback: { adaptor: "loopback" },
loopback2: { adaptor: "loopback" }
}
});
bot.running = true;
stub(bot.devices.ping, "halt").yields("error!");
stub(bot.devices.ping2, "halt").yields();
stub(bot.connections.loopback, "disconnect").yields("another err!");
stub(bot.connections.loopback2, "disconnect").yields();
});
it("doesn't effect the rest of the shutdown", function() {
bot.halt();
expect(bot.devices.ping.halt).to.be.called;
expect(bot.devices.ping2.halt).to.be.called;
expect(bot.connections.loopback.disconnect).to.be.called;
expect(bot.connections.loopback2.disconnect).to.be.called;
});
});
});
describe("#toString", function() {
@ -514,22 +548,17 @@ describe("Robot", function() {
describe("#log", function() {
beforeEach(function() {
stub(Logger, "info");
stub(Logger, "fatal");
robot.log("info", "an informative message");
robot.log("fatal", "a fatal error");
stub(Logger, "log");
robot.log("an informative message");
});
afterEach(function() {
Logger.info.restore();
Logger.fatal.restore();
Logger.log.restore();
});
it("it passes messages onto Logger, with the Robot's name", function() {
var nameStr = "[" + robot.name + "] - ";
expect(Logger.info).to.be.calledWith(nameStr, "an informative message");
expect(Logger.fatal).to.be.calledWith(nameStr, "a fatal error");
expect(Logger.log).to.be.calledWith(nameStr + "an informative message");
});
});
});

View File

@ -1,74 +1,8 @@
"use strict";
var utils = source("utils");
var utils = lib("utils");
describe("Utils", function() {
describe("Monkey-patches", function() {
describe("Number", function() {
describe("#seconds", function() {
it("allows for expressing time in seconds", function() {
expect((5).seconds()).to.be.eql(5000);
});
});
describe("#second", function() {
it("allows for expressing time in seconds", function() {
expect((1).second()).to.be.eql(1000);
});
});
describe("#fromScale", function() {
it("converts a value from one scale to 0-1 scale", function() {
expect((5).fromScale(0, 10)).to.be.eql(0.5);
});
it("converts floats", function() {
expect((2.5).fromScale(0, 10)).to.be.eql(0.25);
});
context("if the number goes above the top of the scale", function() {
it("should return 1", function() {
expect((15).fromScale(0, 10)).to.be.eql(1);
});
});
context("if the number goes below the bottom of the scale", function() {
it("should return 0", function() {
expect((15).fromScale(0, 10)).to.be.eql(1);
expect((5).fromScale(10, 20)).to.be.eql(0);
});
});
});
describe("#toScale", function() {
it("converts a value from 0-1 scale to another", function() {
expect((0.5).toScale(0, 10)).to.be.eql(5);
});
context("when value goes below bottom of scale", function() {
it("returns the bottom of the scale", function() {
expect((-5).toScale(0, 10)).to.be.eql(0);
});
});
context("when value goes above top of scale", function() {
it("returns the top of the scale", function() {
expect((15).toScale(0, 10)).to.be.eql(10);
});
});
it("converts to floats", function() {
expect((0.25).toScale(0, 10)).to.be.eql(2.5);
});
it("can be chained with #fromScale", function() {
var num = (5).fromScale(0, 20).toScale(0, 10);
expect(num).to.be.eql(2.5);
});
});
});
});
describe("#makeUnique", function() {
it("returns the original name if it's not a conflict", function() {
var res = utils.makeUnique("hello", ["world"]);
@ -137,6 +71,25 @@ describe("Utils", function() {
});
});
describe("#finish", function() {
beforeEach(function() {
this.clock = sinon.useFakeTimers();
});
afterEach(function() {
this.clock.restore();
});
it("stops calling an interval function", function() {
var func = spy();
var interval = utils.every(10, func);
this.clock.tick(15);
utils.finish(interval);
this.clock.tick(15);
expect(func).to.be.calledOnce;
});
});
describe("#subclass", function() {
var BaseClass = function BaseClass(opts) {
this.greeting = opts.greeting;
@ -162,8 +115,7 @@ describe("Utils", function() {
describe("#proxyFunctionsToObject", function() {
var methods = ["asString", "toString", "returnString"];
var ProxyClass = (function() {
function ProxyClass() {}
var ProxyClass = function ProxyClass() {};
ProxyClass.prototype.asString = function() {
return "[object ProxyClass]";
@ -177,17 +129,10 @@ describe("Utils", function() {
return string;
};
return ProxyClass;
})();
var TestClass = (function() {
function TestClass() {
var TestClass = function TestClass() {
this.testInstance = new ProxyClass();
utils.proxyFunctionsToObject(methods, this.testInstance, this, true);
}
return TestClass;
})();
};
var testclass = new TestClass();
@ -251,4 +196,22 @@ describe("Utils", function() {
});
});
});
describe("#classCallCheck", function() {
it("checks that an object is an instance of a constructor", function() {
var fn = function(instance, Constructor) {
return utils.classCallCheck.bind(null, instance, Constructor);
};
function Class() {
utils.classCallCheck(this, Class);
}
expect(fn([], Array)).to.not.throw;
expect(fn([], Number)).to.throw(TypeError);
expect(Class).to.throw(TypeError);
expect(function() { return new Class(); }).not.to.throw(TypeError);
});
});
});

View File

@ -1,6 +1,6 @@
"use strict";
var _ = source("utils/helpers");
var _ = lib("utils/helpers");
describe("Helpers", function() {
describe("extend", function() {
@ -289,4 +289,90 @@ describe("Helpers", function() {
expect(fn(arr, {})).to.be.eql(false);
});
});
describe("#parallel", function() {
var fn1, fn2, fn3, callback;
beforeEach(function() {
fn1 = stub();
fn2 = stub();
fn3 = stub();
callback = stub();
});
it("executes a set of functions in parallel", function() {
_.parallel([fn1, fn2, fn3], callback);
expect(fn1).to.be.called;
expect(fn2).to.be.called;
expect(fn3).to.be.called;
expect(callback).to.not.be.called;
fn1.yield(null, true);
expect(callback).to.not.be.called;
fn2.yield(null, true);
expect(callback).to.not.be.called;
fn3.yield(null, true);
expect(callback).to.be.calledWith(null, [true, true, true]);
});
it("stops immediately if there's an error", function() {
_.parallel([fn1, fn2, fn3], callback);
fn1.yield(true, null);
expect(callback).to.be.calledWith(true);
fn2.yields(null, true);
fn3.yields(null, true);
expect(callback).to.be.calledOnce;
});
});
describe("#series", function() {
var fn1, fn2, fn3, callback;
beforeEach(function() {
fn1 = stub();
fn2 = stub();
fn3 = stub();
callback = stub();
});
it("executes a set of functions in series", function() {
_.series([fn1, fn2, fn3], callback);
expect(fn1).to.be.called;
expect(fn2).to.not.be.called;
expect(fn3).to.not.be.called;
expect(callback).to.not.be.called;
fn1.yield(null, true);
expect(fn2).to.be.called;
expect(fn3).to.not.be.called;
expect(callback).to.not.be.called;
fn2.yield(null, true);
expect(fn3).to.be.called;
expect(callback).to.not.be.called;
fn3.yield(null, true);
expect(callback).to.be.calledWith(null, [true, true, true]);
});
it("stops immediately if there's an error", function() {
_.series([fn1, fn2, fn3], callback);
fn1.yield(true, null);
expect(fn2).to.not.be.called;
expect(fn3).to.not.be.called;
expect(callback).to.be.calledWith(true);
});
});
});

View File

@ -0,0 +1,142 @@
// jshint expr:true
"use strict";
var patches = lib("utils/monkey-patches");
describe("monkey-patches", function() {
beforeEach(function() {
patches.uninstall();
});
afterEach(function() {
patches.install();
});
describe("#install", function() {
it("monkey-patches methods onto global classes", function() {
var proto = Number.prototype;
expect(proto.seconds).to.be.undefined;
expect(proto.second).to.be.undefined;
patches.install();
expect(proto.seconds).to.be.a("function");
expect(proto.second).to.be.a("function");
});
});
describe("#uninstall", function() {
it("removes existing monkey-patching", function() {
var proto = Number.prototype;
patches.install();
expect(proto.seconds).to.be.a("function");
expect(proto.second).to.be.a("function");
patches.uninstall();
expect(proto.seconds).to.be.undefined;
expect(proto.second).to.be.undefined;
});
});
describe("Number", function() {
beforeEach(function() {
patches.install();
});
describe("#seconds", function() {
it("allows for expressing time in seconds", function() {
expect((5).seconds()).to.be.eql(5000);
});
});
describe("#second", function() {
it("allows for expressing time in seconds", function() {
expect((1).second()).to.be.eql(1000);
});
});
describe("#milliseconds", function() {
it("allows for expressing time in milliseconds", function() {
expect((5).milliseconds()).to.be.eql(5);
});
});
describe("#ms", function() {
it("allows for expressing time in milliseconds", function() {
expect((5).ms()).to.be.eql(5);
});
});
describe("#microseconds", function() {
it("allows for expressing time in microseconds", function() {
expect((5000).microseconds()).to.be.eql(5);
});
});
describe("#minutes", function() {
it("allows for expressing time in minutes", function() {
expect((5).minutes()).to.be.eql(300000);
});
});
describe("#minute", function() {
it("allows for expressing time per minute", function() {
expect((1).minute()).to.be.eql(60000);
});
});
describe("#fromScale", function() {
it("converts a value from one scale to 0-1 scale", function() {
expect((5).fromScale(0, 10)).to.be.eql(0.5);
});
it("converts floats", function() {
expect((2.5).fromScale(0, 10)).to.be.eql(0.25);
});
context("if the number goes above the top of the scale", function() {
it("should return 1", function() {
expect((15).fromScale(0, 10)).to.be.eql(1);
});
});
context("if the number goes below the bottom of the scale", function() {
it("should return 0", function() {
expect((15).fromScale(0, 10)).to.be.eql(1);
expect((5).fromScale(10, 20)).to.be.eql(0);
});
});
});
describe("#toScale", function() {
it("converts a value from 0-1 scale to another", function() {
expect((0.5).toScale(0, 10)).to.be.eql(5);
});
context("when value goes below bottom of scale", function() {
it("returns the bottom of the scale", function() {
expect((-5).toScale(0, 10)).to.be.eql(0);
});
});
context("when value goes above top of scale", function() {
it("returns the top of the scale", function() {
expect((15).toScale(0, 10)).to.be.eql(10);
});
});
it("converts to floats", function() {
expect((0.25).toScale(0, 10)).to.be.eql(2.5);
});
it("can be chained with #fromScale", function() {
var num = (5).fromScale(0, 20).toScale(0, 10);
expect(num).to.be.eql(2.5);
});
});
});
});