Compare commits

..

3 Commits

Author SHA1 Message Date
Andrew Stewart 9d6a19507b Add methods to manipulate Repl context 2015-04-25 20:00:11 -07:00
Andrew Stewart 2ff7dc0866 Add Cylon.repl and Robot#repl methods
Each invokes a Repl with some necessary context
2015-04-25 19:37:26 -07:00
Andrew Stewart b2e8f57d24 Add a basic class wrapping Node's repl 2015-04-25 19:19:10 -07:00
54 changed files with 1577 additions and 2125 deletions

View File

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

View File

@ -1,36 +0,0 @@
# 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")

View File

@ -1,60 +0,0 @@
# 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,69 +1,24 @@
# Contributors
Cylon.js exists thanks to the efforts of the following hardworking humans who have worked on the core, adaptors, drivers, or documentation:
Cylon.js exists thanks to the efforts of the hardworking humans on the core team:
- 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))
And to the fine work of these wonderful contributors:
- Andrew Nesbitt ([@andrew](https://github.com/andrew))
- Chris Boette ([@chrisbodhi](https://github.com/chrisbodhi))
- Chris Mattheiu ([@chrismatthieu](https://github.com/chrismatthieu))
- Fábio Franco Uechi ([@fabito](https://github.com/fabito))
- Loren West ([@lorenwest](https://github.com/lorenwest))
- Mario "Kuroir" Ricalde ([@MarioRicalde](https://github.com/MarioRicalde))
- Javier Cervantes ([@solojavier](https://github.com/solojavier))
- Gize Bonilla ([@XixeBombilla](https://github.com/XixeBombilla))
- Nathan Zankich ([@zankavrogin](https://github.com/zankavrogin))
Thank you!
Please join us, we'd love your contribution too.

View File

@ -1,4 +1,4 @@
Copyright (c) 2013-2016 The Hybrid Group
Copyright (c) 2013-2015 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:
@$(BIN)/eslint lib spec examples
@eslint lib spec examples
ci: lint cover

View File

@ -5,8 +5,6 @@ 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,
@ -195,17 +193,16 @@ Cylon.start();
## Hardware Support
Cylon.js has an extensible syntax for connecting to multiple, different hardware
devices. The following 36 platforms are currently supported:
devices. The following 35 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)
@ -220,6 +217,7 @@ Audio
[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)
@ -230,62 +228,50 @@ Audio
[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 14 different devices:
set of drivers supporting a number of 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 Range Sensor
- IR Rangefinder
- LED
- Makey Button (high-resistance button like the [MakeyMakey](http://www.makeymakey.com/))
- MakeyButton
- Maxbotix Ultrasonic Range Finder
- Motor
- Relay
- RGB LED
- Servo
- Temperature Sensor
- TP401 Gas Sensor
We also support 14 different I2C (Inter-Integrated Circuit) devices
Additionally, we also support a number of 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 RGB LED
- BMP180 Barometric Pressure + Temperature sensor
- Direct I2C
- BlinkM
- BMP180
- HMC6352 Digital Compass
- JHD1313M1 LCD with RGB Backlight
- LCD
- LCD Display
- LIDAR-Lite
- LSM9DS0G 9 Degrees of Freedom IMU
- LSM9DS0XM 9 Degrees of Freedom IMU
- MAG3110 3-Axis Digital Magnetometer
- MPL115A2 Digital Barometer & Thermometer
- MPL115A2 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 officially supported platforms, we have the following 8 user contributed platforms:
In addition to our supported platforms, we have the following 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)
[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)
[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)
We'll also have many more platforms and drivers coming soon, [follow us on Twitter][Twitter] for updates.
@ -296,7 +282,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](https://cylonjs.com/documentation/guides/browser-support/).
For more info on browser support, and for help with different configurations, you can find more info [in our docs](/documentation/guides/browser-support).
## API Plugins
@ -342,12 +328,62 @@ If you want to help with documentation, you can find the code for our website at
## Contributing
For our contribution guidelines, please go to [CONTRIBUTING.md](https://github.com/hybridgroup/cylon/blob/master/CONTRIBUTING.md).
* 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
## Release History
For the release history, please go to [RELEASES.md](https://github.com/hybridgroup/cylon/blob/master/RELEASES.md).
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
## License
Copyright (c) 2013-2016 The Hybrid Group. Licensed under the Apache 2.0 license.
Copyright (c) 2013-2015 The Hybrid Group. Licensed under the Apache 2.0 license.

View File

@ -1,46 +0,0 @@
## 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

@ -1,25 +0,0 @@
"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();

View File

@ -1,38 +0,0 @@
"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,3 +1,11 @@
/**
* 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"),
@ -8,20 +16,14 @@ function formatErrorMessage(name, message) {
return ["Error in connection", "'" + name + "'", "- " + message].join(" ");
}
/**
* 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
*/
// 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
var Adaptor = module.exports = function Adaptor(opts) {
Adaptor.__super__.constructor.apply(this, arguments);
opts = opts || {};
this.name = opts.name;
@ -45,12 +47,9 @@ var Adaptor = module.exports = function Adaptor(opts) {
Utils.subclass(Adaptor, Basestar);
/**
* A base connect function. Must be overwritten by a descendent.
*
* @throws Error if not overridden by a child class
* @return {void}
*/
// Public: Basic #connect function. Must be overwritten by a descendent class
//
// Returns nothing, throws an error
Adaptor.prototype.connect = function() {
var message = formatErrorMessage(
this.name,
@ -60,12 +59,9 @@ Adaptor.prototype.connect = function() {
throw new Error(message);
};
/**
* A base disconnect function. Must be overwritten by a descendent.
*
* @throws Error if not overridden by a child class
* @return {void}
*/
// Public: Basic #disconnect function. Must be overwritten by a descendent class
//
// Returns nothing, throws an error
Adaptor.prototype.disconnect = function() {
var message = formatErrorMessage(
this.name,
@ -75,11 +71,9 @@ Adaptor.prototype.disconnect = function() {
throw new Error(message);
};
/**
* Expresses the Adaptor in a JSON-serializable format
*
* @return {Object} a representation of the Adaptor in a serializable format
*/
// Public: Expresses the Connection in JSON format
//
// Returns an Object containing Connection data
Adaptor.prototype.toJSON = function() {
return {
name: this.name,

View File

@ -1,52 +0,0 @@
"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,3 +1,11 @@
/**
* 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;
@ -5,41 +13,33 @@ var EventEmitter = require("events").EventEmitter;
var Utils = require("./utils"),
_ = require("./utils/helpers");
/**
* 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
*/
// 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.
var Basestar = module.exports = function Basestar() {
Utils.classCallCheck(this, Basestar);
};
Utils.subclass(Basestar, EventEmitter);
/**
* 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
*/
// 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
Basestar.prototype.proxyMethods = Utils.proxyFunctionsToObject;
/**
* 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}
*/
// 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
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) {
}
};
/**
* 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
*/
// 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
Basestar.prototype.defineEvent = function(opts) {
opts.sendUpdate = opts.sendUpdate || false;
opts.targetEventName = opts.targetEventName || opts.eventName;
@ -83,26 +83,23 @@ Basestar.prototype.defineEvent = function(opts) {
return opts.source;
};
/**
* 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
*/
// 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
Basestar.prototype.defineAdaptorEvent = function(opts) {
return this._proxyEvents(opts, this.connector, this);
};
/**
* 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
*/
// 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
Basestar.prototype.defineDriverEvent = function(opts) {
return this._proxyEvents(opts, this.connection, this);
};

View File

@ -1,58 +1,16 @@
/*
* Cylon.js - Internal Configuration
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var _ = require("./utils/helpers");
module.exports = {
logging: {},
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); }
// are we in TDR test mode? Used to stub out adaptors/drivers.
testMode: false
};

200
lib/cylon.js Normal file
View File

@ -0,0 +1,200 @@
/*
* 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,3 +1,11 @@
/**
* 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"),
@ -8,20 +16,14 @@ function formatErrorMessage(name, message) {
return ["Error in driver", "'" + name + "'", "- " + message].join(" ");
}
/**
* 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
*/
// 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
var Driver = module.exports = function Driver(opts) {
Driver.__super__.constructor.apply(this, arguments);
opts = opts || {};
this.name = opts.name;
@ -49,12 +51,9 @@ var Driver = module.exports = function Driver(opts) {
Utils.subclass(Driver, Basestar);
/**
* A base start function. Must be overwritten by a descendent.
*
* @throws Error if not overridden by a child class
* @return {void}
*/
// Public: Basic #start function. Must be overwritten by a descendent class
//
// Returns nothing, throws an error
Driver.prototype.start = function() {
var message = formatErrorMessage(
this.name,
@ -64,12 +63,9 @@ Driver.prototype.start = function() {
throw new Error(message);
};
/**
* A base halt function. Must be overwritten by a descendent.
*
* @throws Error if not overridden by a child class
* @return {void}
*/
// Public: Basic #halt function. Must be overwritten by a descendent class
//
// Returns nothing, throws an error
Driver.prototype.halt = function() {
var message = formatErrorMessage(
this.name,
@ -79,16 +75,6 @@ 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;
@ -96,13 +82,9 @@ 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 && !endsWith(command, match)) {
if (match.length > 1) {
match = match.replace(/[A-Z]$/, function(m) {
return "_" + m.toLowerCase();
});
@ -115,11 +97,6 @@ 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,3 +1,11 @@
/**
* 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"),
@ -23,11 +31,6 @@ 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,3 +1,11 @@
/**
* 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";
@ -12,15 +20,8 @@ var GPIO_PATH = "/sys/class/gpio";
var GPIO_READ = "in";
var GPIO_WRITE = "out";
/**
* 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
*/
// DigitalPin class offers an interface with the Linux GPIO system present in
// single-board computers such as a Raspberry Pi, or a BeagleBone
var DigitalPin = module.exports = function DigitalPin(opts) {
this.pinNum = opts.pin.toString();
this.status = "low";

View File

@ -1,15 +1,17 @@
/**
* 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 = {
/**
* 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
*/
// 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
periodAndDuty: function(scaledDuty, freq, pulseWidth, polarity) {
var period, duty, maxDuty;

View File

@ -1,82 +1,53 @@
/**
* Cylon.js - Logger
* 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 levels = ["debug", "info", "warn", "error", "fatal"];
var Config = require("./config"),
var BasicLogger = require("./logger/basic_logger"),
NullLogger = require("./logger/null_logger"),
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: setup,
setup: function(opts) {
if (_.isObject(opts)) {
Config.logging = _.extend(Config.logging, opts);
}
should: {
log: true,
debug: false
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;
},
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);
}
toString: function() {
return this.logger.toString();
}
};
function setup(opts) {
if (_.isObject(opts)) { _.extend(Config, opts); }
Logger.setup();
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);
levels.forEach(function(level) {
Logger[level] = function() {
if (levels.indexOf(level) >= levels.indexOf(Logger.level)) {
return Logger.logger[level].apply(Logger.logger, arguments);
}
};
});

View File

@ -0,0 +1,33 @@
/**
* 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));
};
});

19
lib/logger/null_logger.js Normal file
View File

@ -0,0 +1,19 @@
/**
* 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() {};
});

View File

@ -1,82 +0,0 @@
"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,8 +1,15 @@
/**
* 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"),
path = require("path");
_ = require("./utils/helpers");
// Explicitly these modules here, so Browserify can grab them later
require("./test/loopback");
@ -31,11 +38,7 @@ var Registry = module.exports = {
var pkg;
try {
if (this.isModuleInDevelopment(module)) {
pkg = require(path.resolve(".") + "/index");
} else {
pkg = require(module);
}
pkg = require(module);
} catch (e) {
if (e.code === "MODULE_NOT_FOUND") {
missingModuleError(module);
@ -104,10 +107,6 @@ var Registry = module.exports = {
}
return false;
},
isModuleInDevelopment: function(module) {
return (path.basename(path.resolve(".")) === module);
}
};

82
lib/repl.js Normal file
View File

@ -0,0 +1,82 @@
"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,45 +1,58 @@
/**
* 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 validator = require("./validator");
var Async = require("async"),
EventEmitter = require("events").EventEmitter;
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
*/
// 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 Robot = module.exports = function Robot(opts) {
Utils.classCallCheck(this, Robot);
opts = opts || {};
validator.validate(opts);
var methods = [
"toString",
"halt",
"startDevices",
"startConnections",
"start",
"initRobot",
"initDevices",
"initConnections",
"log"
];
// auto-bind prototype methods
for (var prop in Object.getPrototypeOf(this)) {
if (this[prop] && prop !== "constructor") {
this[prop] = this[prop].bind(this);
}
}
methods.forEach(function(method) {
this[method] = this[method].bind(this);
}, this);
this.initRobot(opts);
this.checkForBadSyntax(opts);
this.initConnections(opts);
this.initDevices(opts);
_.each(opts, function(opt, name) {
if (this[name] !== undefined) {
return;
@ -84,11 +97,16 @@ var Robot = module.exports = function Robot(opts) {
Utils.subclass(Robot, EventEmitter);
/**
* Condenses information on a Robot to a JSON-serializable format
*
* @return {Object} serializable information on the Robot
*/
// 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
Robot.prototype.toJSON = function() {
return {
name: this.name,
@ -99,13 +117,6 @@ 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;
@ -118,35 +129,72 @@ Robot.prototype.connection = function(name, conn) {
str = "Connection names must be unique.";
str += "Renaming '" + original + "' to '" + conn.name + "'";
this.log(str);
}
if ("adapter" in conn) {
conn.adaptor = conn.adapter;
this.log("warn", str);
}
this.connections[conn.name] = initializer("adaptor", conn);
return this;
};
/**
* Initializes all values for a new Robot.
*
* @param {Object} opts object passed to Robot constructor
* @return {void}
*/
// Public: Initializes all vars for robot
//
// opts - options array passed to constructor
//
// Returns null
Robot.prototype.initRobot = function(opts) {
this.name = opts.name || "Robot " + ROBOT_ID++;
this.running = false;
this.name = opts.name || Robot.randomName();
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("No work yet."); };
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;
}
_.each(opts.connections, function(conn, key) {
@ -163,22 +211,12 @@ Robot.prototype.initRobot = function(opts) {
delete conn.devices;
}
this.connection(name, _.extend({}, conn));
this.connection(name, conn);
}, this);
_.each(opts.devices, function(device, key) {
var name = _.isString(key) ? key : device.name;
this.device(name, _.extend({}, device));
}, this);
return this.connections;
};
/**
* 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;
@ -191,13 +229,13 @@ Robot.prototype.device = function(name, device) {
str = "Device names must be unique.";
str += "Renaming '" + original + "' to '" + device.name + "'";
this.log(str);
this.log("warn", str);
}
if (_.isString(device.connection)) {
if (this.connections[device.connection] == null) {
str = "No connection found with the name " + device.connection + ".\n";
this.log(str);
this.log("fatal", str);
process.emit("SIGINT");
}
@ -214,13 +252,36 @@ Robot.prototype.device = function(name, device) {
return this;
};
/**
* 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
*/
// 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
Robot.prototype.start = function(callback) {
if (this.running) {
return this;
@ -234,14 +295,14 @@ Robot.prototype.start = function(callback) {
}
}.bind(this);
_.series([
Async.series([
this.startConnections,
this.startDevices,
start
], function(err, results) {
if (err) {
this.log("An error occured while trying to start the robot:");
this.log(err);
this.log("fatal", "An error occured while trying to start the robot:");
this.log("fatal", err);
this.halt(function() {
if (_.isFunction(this.error)) {
@ -262,158 +323,130 @@ Robot.prototype.start = function(callback) {
return this;
};
/**
* Starts the Robot's work function
*
* @return {void}
*/
// Public: Starts the Robot"s work and triggers a callback
//
// callback - callback function to be triggered
//
// Returns nothing
Robot.prototype.startWork = function() {
this.log("Working.");
this.log("info", "Working.");
this.emit("ready", this);
this.work.call(this, this);
this.running = true;
};
/**
* Starts the Robot's connections
*
* @param {Function} callback function to be triggered after the connections are
* started
* @return {void}
*/
// Public: Starts the Robot"s connections and triggers a callback
//
// callback - callback function to be triggered
//
// Returns nothing
Robot.prototype.startConnections = function(callback) {
this.log("Starting connections.");
this.log("info", "Starting connections.");
var starters = _.map(this.connections, function(conn, name) {
this[name] = conn;
var starters = _.map(this.connections, function(conn) {
return function(cb) {
return this.startConnection(conn, 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);
}.bind(this);
}, this);
return _.parallel(starters, callback);
return Async.parallel(starters, callback);
};
/**
* 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}
*/
// Public: Starts the Robot"s devices and triggers a callback
//
// callback - callback function to be triggered
//
// Returns nothing
Robot.prototype.startDevices = function(callback) {
var log = this.log;
log("Starting devices.");
log("info", "Starting devices.");
var starters = _.map(this.devices, function(device, name) {
this[name] = device;
var starters = _.map(this.devices, function(device) {
return function(cb) {
return this.startDevice(device, cb);
}.bind(this);
var str = "Starting device '" + name + "'";
if (device.pin) {
str += " on pin " + device.pin;
}
log("debug", str + ".");
return device.start.call(device, cb);
};
}, this);
return _.parallel(starters, callback);
return Async.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(str + ".");
this[device.name] = device;
device.start.call(device, callback);
device.started = true;
return device.started;
};
/**
* Halts the Robot, attempting to gracefully stop devices and connections.
*
* @param {Function} callback to be triggered when the Robot has stopped
* @return {void}
*/
// Public: Halts the Robot.
//
// Halts the devices, disconnects the connections.
//
// callback - callback to be triggered when the Robot is stopped
//
// Returns nothing
Robot.prototype.halt = function(callback) {
callback = callback || function() {};
if (!this.running) {
if (!this.isRunning) {
return callback();
}
// 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);
var devices = _.pluck(this.devices, "halt"),
connections = _.pluck(this.connections, "disconnect");
try {
_.parallel(devices, function() {
_.parallel(connections, callback);
Async.parallel(devices, function() {
Async.parallel(connections, callback);
});
} catch (e) {
var msg = "An error occured while attempting to safely halt the robot";
this.log(msg);
this.log(e.message);
this.log("error", msg);
this.log("error", e.message);
}
this.running = false;
};
/**
* Generates a String representation of a Robot
* Starts a new REPL in the context of the Robot.
*
* @return {String} representation of a Robot
* @return {void}
*/
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(str) {
Logger.log("[" + this.name + "] - " + str);
Robot.prototype.log = function(level) {
var args = Array.prototype.slice.call(arguments, 1);
args.unshift("[" + this.name + "] -");
Logger[level].apply(null, args);
};

View File

@ -1,9 +1,19 @@
/*
* 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 = function Loopback() {
var Loopback;
module.exports = Loopback = function Loopback() {
Loopback.__super__.constructor.apply(this, arguments);
};

View File

@ -1,3 +1,11 @@
/*
* 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,22 +1,23 @@
/*
* 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 = function TestAdaptor() {
var TestAdaptor;
module.exports = TestAdaptor = 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,21 +1,23 @@
/*
* 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 = function TestDriver() {
var TestDriver;
module.exports = TestDriver = 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,3 +1,11 @@
/*
* Cylon - Utils
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
"use strict";
var _ = require("./utils/helpers");
@ -5,63 +13,61 @@ var _ = require("./utils/helpers");
var monkeyPatches = require("./utils/monkey-patches");
var Utils = module.exports = {
/**
* 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()
*/
// 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
every: function every(interval, action) {
return setInterval(action, 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()
*/
// 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
after: function after(delay, action) {
return setTimeout(action, delay);
},
/**
* 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()
*/
// Public: Alias to the `every` function, but passing 0
// Examples
//
// constantly(function() {
// console.log("hello world (and again and again)!");
// });
//
// Returns an interval
constantly: function constantly(action) {
return every(0, action);
},
/**
* 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}
*/
// Public: Sleep - do nothing for some duration of time.
//
// ms - number of ms to sleep for
//
// Examples
//
// sleep((1).second());
//
// Returns a function
sleep: function sleep(ms) {
var start = Date.now(),
i = 0;
@ -71,17 +77,19 @@ var Utils = module.exports = {
}
},
/**
* 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
*/
// 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
subclass: function subclass(child, parent) {
var Ctor = function() {
this.constructor = child;
@ -107,15 +115,16 @@ var Utils = module.exports = {
});
},
/**
* 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
*/
// 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
proxyFunctionsToObject: function(methods, target, base, force) {
if (base == null) {
base = this;
@ -136,37 +145,39 @@ var Utils = module.exports = {
return base;
},
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
*/
// 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.
fetch: function(obj, property, fallback) {
if (obj.hasOwnProperty(property)) {
return obj[property];
@ -189,15 +200,13 @@ var Utils = module.exports = {
return fallback;
},
/**
* 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
*/
// 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
makeUnique: function(name, arr) {
var newName;
@ -213,12 +222,20 @@ var Utils = module.exports = {
}
},
/**
* Adds every/after/constantly to the global namespace, and installs
* monkey-patches.
*
* @return {Object} utils object
*/
// 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
bootstrap: function bootstrap() {
global.every = this.every;
global.after = this.after;

View File

@ -1,6 +1,12 @@
"use strict";
/**
* Cylon.js - Helper Utilities
* cylonjs.com
*
* Copyright (c) 2013-2015 The Hybrid Group
* Licensed under the Apache 2.0 license.
*/
/* eslint no-use-before-define: 0 */
"use strict";
var __slice = Array.prototype.slice;
@ -204,72 +210,3 @@ 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,53 +1,40 @@
/* eslint no-extend-native: 0 key-spacing: 0 */
/**
* 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 */
"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() {
/**
* 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;
};
// 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
/**
* 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
* Multiplies a number by 1000 to get time in milliseconds
*
* @example
* (2).seconds(); //=> 2000
@ -67,40 +54,6 @@ 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.
*

View File

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

View File

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

View File

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

View File

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

View File

@ -1,41 +0,0 @@
"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 = lib("basestar"),
Utils = lib("utils");
var Basestar = source("basestar"),
Utils = source("utils");
var EventEmitter = require("events").EventEmitter;

View File

@ -1,84 +0,0 @@
"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,78 +1,201 @@
"use strict";
var Cylon = lib("../index");
var Cylon = source("cylon"),
Robot = source("robot");
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")
};
var Logger = source("logger"),
Adaptor = source("adaptor"),
Driver = source("driver"),
Config = source("config");
describe("Cylon", function() {
it("exports the MCP as Cylon.MCP", function() {
expect(Cylon.MCP).to.be.eql(MCP);
});
describe("exports", function() {
it("sets Logger to the Logger module", function() {
expect(Cylon.Logger).to.be.eql(Logger);
});
it("exports the Robot as Cylon.Robot", function() {
expect(Cylon.Robot).to.be.eql(Robot);
});
it("sets Adaptor to the Adaptor module", function() {
expect(Cylon.Adaptor).to.be.eql(Adaptor);
});
it("exports the Driver as Cylon.Driver", function() {
expect(Cylon.Driver).to.be.eql(Driver);
});
it("sets Driver to the Driver module", function() {
expect(Cylon.Driver).to.be.eql(Driver);
});
it("exports the Adaptor as Cylon.Adaptor", function() {
expect(Cylon.Adaptor).to.be.eql(Adaptor);
});
it("sets @apiInstances to an empty array by default", function() {
expect(Cylon.apiInstances).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.robots).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);
it("sets @robots to an empty object by default", function() {
expect(Cylon.commands).to.be.eql({});
});
});
describe("#robot", function() {
it("proxies to MCP.create", function() {
expect(Cylon.robot).to.be.eql(MCP.create);
afterEach(function() {
Cylon.robots = {};
});
});
describe("#start", function() {
it("proxies to MCP.start", function() {
expect(Cylon.start).to.be.eql(MCP.start);
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);
});
});
describe("#halt", function() {
it("proxies to MCP.halt", function() {
expect(Cylon.halt).to.be.eql(MCP.halt);
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() {
it("proxies to API.create", function() {
expect(Cylon.api).to.be.eql(API.create);
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;
});
});
});
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() {
it("proxies to Config.update", function() {
expect(Cylon.config).to.be.eql(Config.update);
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;
});
});
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;
});
});
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();
});
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

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

View File

@ -1,7 +1,7 @@
"use strict";
var Driver = lib("driver"),
Utils = lib("utils");
var Driver = source("driver"),
Utils = source("utils");
describe("Driver", function() {
var connection, driver;
@ -139,23 +139,8 @@ describe("Driver", function() {
});
it("handles edge cases", function() {
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"
];
var commands = ["HelloWorld", "getPNGStream", "getHSetting"],
snake_case = ["hello_world", "get_png_stream", "get_h_setting"];
commands.forEach(function(cmd) {
driver[cmd] = function() {};

View File

@ -1,101 +0,0 @@
"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 = lib("io/utils.js");
var Utils = source("io/utils.js");
describe("IOUtils", function() {
describe("#periodAndDuty", function() {

View File

@ -1,39 +1,47 @@
"use strict";
var Logger = lib("logger"),
Config = lib("config");
var Logger = source("logger"),
Config = source("config");
describe("Logger", function() {
afterEach(function() {
// to be friendly to other specs
Config.logger = false;
Config.silent = false;
Config.logging = { logger: false, level: "debug" };
Logger.setup();
});
describe("#setup", function() {
context("with no arguments", function() {
it("sets up a BasicLogger", function() {
Config.logger = null;
Config.logging = {};
Logger.setup();
expect(Logger.logger.name).to.be.eql("basiclogger");
expect(Logger.toString()).to.be.eql("BasicLogger");
});
});
context("with false", function() {
it("sets up a NullLogger", function() {
Config.logger = false;
Config.logging = { logger: false };
Logger.setup();
expect(Logger.logger.name).to.be.eql("nulllogger");
expect(Logger.toString()).to.be.eql("NullLogger");
});
});
context("with a custom logger", function() {
it("uses the custom logger", function() {
function customlogger() {}
Config.logger = customlogger;
var logger = { toString: function() { return "custom"; } };
Config.logging = { logger: logger };
Logger.setup();
expect(Logger.logger.name).to.be.eql("customlogger");
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);
});
});
});
@ -42,31 +50,100 @@ describe("Logger", function() {
var logger;
beforeEach(function() {
logger = spy();
Logger.logger = logger;
logger = Config.logging.logger = {
debug: spy(),
info: spy(),
warn: spy(),
error: spy(),
fatal: spy()
};
Logger.setup();
});
describe("#debug", function() {
it("proxies to the logger method", function() {
Logger.should.debug = true;
Logger.debug("debug message");
Logger.should.debug = false;
expect(logger).to.be.calledWith("debug message");
it("proxies to the Logger's #debug method", function() {
Logger.debug("Hello", "World");
expect(logger.debug).to.be.calledWith("Hello", "World");
});
});
describe("#log", function() {
it("proxies to the logger method", function() {
Logger.log("log message");
expect(logger).to.be.calledWith("log message");
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");
});
});
});
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);
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() {
Logger.debug("debug message");
Logger.info("info message");
expect(logger.debug).to.not.be.called;
expect(logger.info).to.not.be.called;
});
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");
});
});
});

View File

@ -0,0 +1,70 @@
"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");
});
});
});

View File

@ -1,100 +0,0 @@
"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 = lib("registry");
var Registry = source("registry");
var path = "./../spec/support/mock_module.js";

View File

@ -1,9 +1,9 @@
"use strict";
var Driver = lib("driver"),
Adaptor = lib("adaptor"),
Robot = lib("robot"),
Logger = lib("logger");
var Driver = source("driver"),
Adaptor = source("adaptor"),
Robot = source("robot"),
Logger = source("logger");
describe("Robot", function() {
var work, extraFunction, robot;
@ -32,8 +32,17 @@ describe("Robot", function() {
});
context("if not provided", function() {
it("is set to an incrementing name", function() {
expect(new Robot({}).name).to.match(/Robot \d/);
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");
});
});
});
@ -70,7 +79,7 @@ describe("Robot", function() {
});
};
expect(fn).to.throw(Error);
expect(fn).to.throw(Error, "No connections specified");
});
});
@ -264,20 +273,27 @@ describe("Robot", function() {
});
});
describe("initRobot", function() {
describe("initConnections", function() {
var bot;
beforeEach(function() {
bot = new Robot();
});
context("when connection details are provided", function() {
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() {
it("creates new connections with each of the ones provided", function() {
var connections = {
loopback: { adaptor: "loopback" }
};
bot.initRobot({ connections: connections });
bot.initConnections({ connections: connections });
expect(bot.connections.loopback).to.be.instanceOf(Adaptor);
});
@ -296,7 +312,7 @@ describe("Robot", function() {
}
};
bot.initRobot(opts);
bot.initConnections(opts);
});
it("adds the devices to opts.devices", function() {
@ -310,23 +326,6 @@ 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() {
@ -355,7 +354,34 @@ describe("Robot", function() {
});
});
describe("initRobot", 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("#start", function() {
@ -415,13 +441,6 @@ 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() {
@ -449,32 +468,6 @@ 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() {
@ -491,13 +484,18 @@ describe("Robot", function() {
}
});
bot.running = true;
bot.isRunning = true;
device = bot.devices.ping;
connection = bot.connections.loopback;
stub(device, "halt").yields();
stub(connection, "disconnect").yields();
stub(device, "halt").yields(true);
stub(connection, "disconnect").yields(true);
});
afterEach(function() {
device.halt.restore();
connection.disconnect.restore();
});
it("calls #halt on all devices and connections", function() {
@ -506,38 +504,6 @@ 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() {
@ -548,17 +514,22 @@ describe("Robot", function() {
describe("#log", function() {
beforeEach(function() {
stub(Logger, "log");
robot.log("an informative message");
stub(Logger, "info");
stub(Logger, "fatal");
robot.log("info", "an informative message");
robot.log("fatal", "a fatal error");
});
afterEach(function() {
Logger.log.restore();
Logger.info.restore();
Logger.fatal.restore();
});
it("it passes messages onto Logger, with the Robot's name", function() {
var nameStr = "[" + robot.name + "] - ";
expect(Logger.log).to.be.calledWith(nameStr + "an informative message");
var nameStr = "[" + robot.name + "] -";
expect(Logger.info).to.be.calledWith(nameStr, "an informative message");
expect(Logger.fatal).to.be.calledWith(nameStr, "a fatal error");
});
});
});

View File

@ -1,8 +1,74 @@
"use strict";
var utils = lib("utils");
var utils = source("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"]);
@ -71,25 +137,6 @@ 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;
@ -115,24 +162,32 @@ describe("Utils", function() {
describe("#proxyFunctionsToObject", function() {
var methods = ["asString", "toString", "returnString"];
var ProxyClass = function ProxyClass() {};
var ProxyClass = (function() {
function ProxyClass() {}
ProxyClass.prototype.asString = function() {
return "[object ProxyClass]";
};
ProxyClass.prototype.asString = function() {
return "[object ProxyClass]";
};
ProxyClass.prototype.toString = function() {
return "[object ProxyClass]";
};
ProxyClass.prototype.toString = function() {
return "[object ProxyClass]";
};
ProxyClass.prototype.returnString = function(string) {
return string;
};
ProxyClass.prototype.returnString = function(string) {
return string;
};
var TestClass = function TestClass() {
this.testInstance = new ProxyClass();
utils.proxyFunctionsToObject(methods, this.testInstance, this, true);
};
return ProxyClass;
})();
var TestClass = (function() {
function TestClass() {
this.testInstance = new ProxyClass();
utils.proxyFunctionsToObject(methods, this.testInstance, this, true);
}
return TestClass;
})();
var testclass = new TestClass();
@ -196,22 +251,4 @@ 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 _ = lib("utils/helpers");
var _ = source("utils/helpers");
describe("Helpers", function() {
describe("extend", function() {
@ -289,90 +289,4 @@ 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

@ -1,142 +0,0 @@
// 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);
});
});
});
});