Compare commits
85 Commits
Author | SHA1 | Date |
---|---|---|
Ron Evans | 0ba0301318 | |
André Cordeiro | f24df1ba89 | |
André Cordeiro | d550df34f8 | |
Ron Evans | ac1bd1ef3f | |
Morten H. Frederiksen | d72bab16d5 | |
deadprogram | 44497e932c | |
deadprogram | de88b4edfd | |
deadprogram | 1ff38f2e9f | |
deadprogram | 9be1513a24 | |
deadprogram | 8c15825ec5 | |
Ron Evans | c0c8923eb7 | |
Daniel Klein | 33c2f3390f | |
deadprogram | e9796ef297 | |
deadprogram | 7dc046cbfe | |
deadprogram | 54c84f15f9 | |
deadprogram | aca4ebce70 | |
deadprogram | f6be3b1ae3 | |
deadprogram | 17e9cee6dc | |
Ron Evans | 3787ebda6a | |
Yogi | bb141ee903 | |
Yogi | a5923a9d8e | |
deadprogram | 4f81ca355a | |
deadprogram | 8df5ccbe5d | |
deadprogram | 4cc9d1ac5d | |
deadprogram | 6dcf7ac26d | |
deadprogram | 161aac9990 | |
deadprogram | 013dc86449 | |
deadprogram | 9182619baa | |
deadprogram | ed76890b8a | |
deadprogram | 16cdb56be6 | |
deadprogram | 8921e902eb | |
deadprogram | 4ec19961f8 | |
deadprogram | 9cdbdd1d37 | |
deadprogram | bba5b1e653 | |
deadprogram | 9f83eaa7a2 | |
deadprogram | 7415daed0c | |
deadprogram | 012578f012 | |
Ron Evans | bea67b2e5e | |
Andrew Stewart | 67f0386bb5 | |
Andrew Stewart | 2e0b92f1bc | |
Andrew Stewart | c7bb3dde8c | |
Andrew Stewart | 3a2766bcd9 | |
Andrew Stewart | 3451932bde | |
Andrew Stewart | 8a582fcf8d | |
Andrew Stewart | 843e0a3217 | |
Andrew Stewart | f14ac10c95 | |
Andrew Stewart | c1bb466d5b | |
Andrew Stewart | a3c2b0544d | |
Andrew Stewart | e8f109508b | |
Ron Evans | 4b374440d9 | |
Andrew Stewart | def91ffe53 | |
Ron Evans | 455c06ab91 | |
Andrew Stewart | ceac0df8dd | |
Andrew Stewart | 1726cdb5f1 | |
Andrew Stewart | 8fbe9b9c4d | |
Edgar O Silva | fd564f61f8 | |
Ron Evans | 8403d89e9f | |
Andrew Stewart | b1c91b7419 | |
Andrew Stewart | 8ac8d7de94 | |
Edgar O Silva | 93357d5012 | |
Andrew Stewart | 0b48851885 | |
Andrew Stewart | 0719fa203c | |
Andrew Stewart | f3efa83643 | |
Ron Evans | 1be64381a9 | |
Andrew Stewart | 10982e4003 | |
Andrew Stewart | 4238385be4 | |
Andrew Stewart | ad2db2d459 | |
Ron Evans | 6978826f1b | |
Andrew Stewart | 9c67d9ba02 | |
Andrew Stewart | 681e197ed9 | |
Andrew Stewart | 952a25ac2c | |
Andrew Stewart | 8adacef282 | |
Andrew Stewart | 68ab6ad441 | |
Andrew Stewart | 6cccc41aff | |
deadprogram | 80a09d55da | |
nathan | 74b92776b0 | |
deadprogram | b1d84d042f | |
deadprogram | 0255b7ff0f | |
deadprogram | 7581dc3c64 | |
Ron Evans | f3cbbe8ad7 | |
Peter deHaan | 760bb87d12 | |
Andrew Stewart | 3f4140bd08 | |
deadprogram | 8fe7a500cf | |
deadprogram | 9c6e79ef15 | |
deadprogram | 958239c6f1 |
|
@ -3,11 +3,13 @@ sudo: false
|
|||
node_js:
|
||||
- '0.10'
|
||||
- '0.12'
|
||||
- 'iojs'
|
||||
- '4'
|
||||
- '5'
|
||||
- '6'
|
||||
before_install:
|
||||
- "mkdir -p ~/.npm"
|
||||
before_script:
|
||||
- npm install -g istanbul codeclimate-test-reporter
|
||||
script:
|
||||
- make ci
|
||||
- CODECLIMATE_REPO_TOKEN=d3aad610220b6eaf4f51e38393c1b62586b1d68b898b42e418d9c2a8e0a7cb0d codeclimate < coverage/lcov.info
|
||||
- CODECLIMATE_REPO_TOKEN=d3aad610220b6eaf4f51e38393c1b62586b1d68b898b42e418d9c2a8e0a7cb0d codeclimate-test-reporter < coverage/lcov.info
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# Code of Conduct
|
||||
|
||||
This Code of Conduct is adapted from [Rust's wonderful
|
||||
CoC](http://www.rust-lang.org/conduct.html).
|
||||
|
||||
* We are committed to providing a friendly, safe and welcoming
|
||||
environment for all, regardless of gender, sexual orientation,
|
||||
disability, ethnicity, religion, or similar personal characteristic.
|
||||
* Please avoid using overtly sexual nicknames or other nicknames that
|
||||
might detract from a friendly, safe and welcoming environment for
|
||||
all.
|
||||
* Please be kind and courteous. There's no need to be mean or rude.
|
||||
* Respect that people have differences of opinion and that every
|
||||
design or implementation choice carries a trade-off and numerous
|
||||
costs. There is seldom a right answer.
|
||||
* Please keep unstructured critique to a minimum. If you have solid
|
||||
ideas you want to experiment with, make a fork and see how it works.
|
||||
* We will exclude you from interaction if you insult, demean or harass
|
||||
anyone. That is not welcome behaviour. We interpret the term
|
||||
"harassment" as including the definition in the [Citizen Code of
|
||||
Conduct](http://citizencodeofconduct.org/); if you have any lack of
|
||||
clarity about what might be included in that concept, please read
|
||||
their definition. In particular, we don't tolerate behavior that
|
||||
excludes people in socially marginalized groups.
|
||||
* Private harassment is also unacceptable. No matter who you are, if
|
||||
you feel you have been or are being harassed or made uncomfortable
|
||||
by a community member, please contact one of the channel ops or any
|
||||
of the TC members immediately with a capture (log, photo, email) of
|
||||
the harassment if possible. Whether you're a regular contributor or
|
||||
a newcomer, we care about making this community a safe place for you
|
||||
and we've got your back.
|
||||
* Likewise any spamming, trolling, flaming, baiting or other
|
||||
attention-stealing behaviour is not welcome.
|
||||
* Avoid the use of personal pronouns in code comments or
|
||||
documentation. There is no need to address persons when explaining
|
||||
code (e.g. "When the developer")
|
|
@ -0,0 +1,60 @@
|
|||
# Contributing to Cylon.js
|
||||
|
||||
This document is based on the [Node.js contribution guidelines](https://github.com/nodejs/node/blob/master/CONTRIBUTING.md) thank you!
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
The Code of Conduct explains the *bare minimum* behavior
|
||||
expectations the Cylon.js project requires of its contributors.
|
||||
[Please read it before participating.](./CODE_OF_CONDUCT.md)
|
||||
|
||||
## Issue Contributions
|
||||
|
||||
When opening new issues or commenting on existing issues on this repository
|
||||
please make sure discussions are related to concrete technical issues with the
|
||||
Cylon.js software.
|
||||
|
||||
## Code Contributions
|
||||
|
||||
The Cylon.js project welcomes new contributors.
|
||||
|
||||
This document will guide you through the contribution process.
|
||||
|
||||
What do you want to contribute?
|
||||
|
||||
- I want to otherwise correct or improve the docs or examples
|
||||
- I want to report a bug
|
||||
- I want to add some feature or functionality to an existing hardware platform
|
||||
- I want to add support for a new hardware platform
|
||||
|
||||
Descriptions for each of these will be provided below.
|
||||
|
||||
## General Guidelines
|
||||
|
||||
* All patches must be provided under the Apache 2.0 License
|
||||
* Please use the -s option in git to "sign off" that the commit is your work and you are providing it under the Apache 2.0 License
|
||||
* Submit a Github Pull Request to the appropriate branch and ideally discuss the changes with us in IRC.
|
||||
* We will look at the patch, test it out, and give you feedback.
|
||||
* Avoid doing minor whitespace changes, renamings, etc. along with merged content. These will be done by the maintainers from time to time but they can complicate merges and should be done seperately.
|
||||
* Take care to maintain the existing coding style.
|
||||
* Add unit tests for any new or changed functionality & lint and test your code using `make test` and `make lint`.
|
||||
* All pull requests should be "fast forward"
|
||||
* If there are commits after yours use `git rebase -i <new_head_branch>`
|
||||
* If you have local changes you may need to use `git stash`
|
||||
* For git help see [progit](http://git-scm.com/book) which is an awesome (and free) book on git
|
||||
|
||||
## Developer's Certificate of Origin 1.0
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
* (a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license indicated
|
||||
in the file; or
|
||||
* (b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source license
|
||||
and I have the right under that license to submit that work with
|
||||
modifications, whether created in whole or in part by me, under the
|
||||
same open source license (unless I am permitted to submit under a
|
||||
different license), as indicated in the file; or
|
||||
* (c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified it.
|
|
@ -1,23 +1,68 @@
|
|||
# Contributors
|
||||
|
||||
Cylon.js exists thanks to the efforts of the hardworking humans on the core team:
|
||||
|
||||
- Ron Evans ([@deadprogram](https://github.com/deadprogram))
|
||||
- Edgar Silva ([@edgarSilva](https://github.com/edgarSilva))
|
||||
- Andrew Stewart ([@stewart](https://github.com/stewart))
|
||||
- Adrian Zankich ([@zankich](https://github.com/zankich))
|
||||
|
||||
And to the fine work of these wonderful contributors:
|
||||
Cylon.js exists thanks to the efforts of the following hardworking humans who have worked on the core, adaptors, drivers, or documentation:
|
||||
|
||||
- Aaron Blondeau ([@aaronblondeau](https://github.com/aaronblondeau))
|
||||
- Quincy Acklen ([@acklenx](https://github.com/acklenx))
|
||||
- Adam Hunter ([@adamrhunter](https://github.com/adamrhunter))
|
||||
- Andrew Nesbitt ([@andrew](https://github.com/andrew))
|
||||
- Avner Cohen ([@AvnerCohen](https://github.com/AvnerCohen))
|
||||
- Michał ([@bartmichu](https://github.com/bartmichu))
|
||||
- Ben Evans ([@bencevans](https://github.com/bencevans))
|
||||
- Brad Midgley ([@bmidgley](https://github.com/bmidgley))
|
||||
- Sebastián González ([@brutalchrist](https://github.com/brutalchrist))
|
||||
- Caleb Oller ([@caleboller](https://github.com/caleboller))
|
||||
- Chris Boette ([@chrisbodhi](https://github.com/chrisbodhi))
|
||||
- Christian Porzig ([@chrisfp](https://github.com/chrisfp))
|
||||
- Chris Mattheiu ([@chrismatthieu](https://github.com/chrismatthieu))
|
||||
- Chris Taylor ([@ChrisTheBaron](https://github.com/ChrisTheBaron))
|
||||
- Daniel Portales ([@daetherius](https://github.com/daetherius))
|
||||
- Daniel Lamb ([@daniellmb](https://github.com/daniellmb))
|
||||
- Ron Evans ([@deadprogram](https://github.com/deadprogram))
|
||||
- Derrell Lipman ([@derrell](https://github.com/derrell))
|
||||
- Daniel Fischer ([@dfischer](https://github.com/dfischer))
|
||||
- Edgar Silva ([@edgarSilva](https://github.com/edgarSilva))
|
||||
- Eric Terpstra ([@ericterpstra](https://github.com/ericterpstra))
|
||||
- Evoliofly ([@Evoliofly](https://github.com/Evoliofly))
|
||||
- Fábio Franco Uechi ([@fabito](https://github.com/fabito))
|
||||
- Felix Sanz ([@felixsanz](https://github.com/felixsanz))
|
||||
- gorhgorh ([@gorhgorh](https://github.com/gorhgorh))
|
||||
- Guillaume normand ([@gui2laume](https://github.com/gui2laume))
|
||||
- Theron Boerner ([@hunterboerner](https://github.com/hunterboerner))
|
||||
- James Brown ([@ibjhb](https://github.com/ibjhb))
|
||||
- Yogi ([@iyogeshjoshi](https://github.com/iyogeshjoshi))
|
||||
- Janaka Abeywardhana ([@janaka](https://github.com/janaka))
|
||||
- Jay Wengrow ([@jaywengrow](https://github.com/jaywengrow))
|
||||
- Jason Sewell ([@jaywon](https://github.com/jaywon))
|
||||
- Jarrod Ribble ([@jribble](https://github.com/jribble))
|
||||
- Jason Solis ([@jsolis](https://github.com/jsolis))
|
||||
- Julian Cheal ([@juliancheal](https://github.com/juliancheal))
|
||||
- Justin Zemlyansky ([@JustInDevelopment](https://github.com/JustInDevelopment))
|
||||
- Justin Smith ([@justinisamaker](https://github.com/justinisamaker))
|
||||
- Philippe Charrière ([@k33g](https://github.com/k33g))
|
||||
- Kraig Walker ([@KraigWalker](https://github.com/KraigWalker))
|
||||
- Loren West ([@lorenwest](https://github.com/lorenwest))
|
||||
- Luis Godinez ([@luisgodinez](https://github.com/luisgodinez))
|
||||
- Mario "Kuroir" Ricalde ([@MarioRicalde](https://github.com/MarioRicalde))
|
||||
- Matheus Mariano ([@matheusmariano](https://github.com/matheusmariano))
|
||||
- Michael Smith ([@michaelshmitty](https://github.com/michaelshmitty))
|
||||
- Morten Høybye Frederiksen ([@mortenf](https://github.com/mortenf))
|
||||
- Guido García ([@palmerabollo](https://github.com/palmerabollo))
|
||||
- Peter deHaan ([@pdehaan](https://github.com/pdehaan))
|
||||
- peterainbow ([@peterainbow](https://github.com/peterainbow))
|
||||
- Rafael Magaña ([@rafmagana](https://github.com/rafmagana))
|
||||
- Reid Carlberg ([@ReidCarlberg](https://github.com/ReidCarlberg))
|
||||
- Sarah Hui ([@sehsarah](https://github.com/sehsarah))
|
||||
- Mike Skalnik ([@skalnik](https://github.com/skalnik))
|
||||
- Javier Cervantes ([@solojavier](https://github.com/solojavier))
|
||||
- SonicRadish ([@sonicradish](https://github.com/sonicradish))
|
||||
- Andrew Stewart ([@stewart](https://github.com/stewart))
|
||||
- Tomasz Szymanski ([@szimano](https://github.com/szimano))
|
||||
- Wojciech Erbetowski ([@wojtekerbetowski](https://github.com/wojtekerbetowski))
|
||||
- Gize Bonilla ([@XixeBombilla](https://github.com/XixeBombilla))
|
||||
- Jasson Qasqant ([@yeco](https://github.com/yeco))
|
||||
- Nathan Zankich ([@zankavrogin](https://github.com/zankavrogin))
|
||||
- Adrian Zankich ([@zankich](https://github.com/zankich))
|
||||
|
||||
Thank you!
|
||||
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2013-2015 The Hybrid Group
|
||||
Copyright (c) 2013-2016 The Hybrid Group
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
2
Makefile
2
Makefile
|
@ -17,7 +17,7 @@ cover:
|
|||
@istanbul cover $(BIN)/_mocha $(TEST_FILES) --report lcovonly -- -R spec
|
||||
|
||||
lint:
|
||||
@eslint lib spec examples
|
||||
@$(BIN)/eslint lib spec examples
|
||||
|
||||
ci: lint cover
|
||||
|
||||
|
|
102
README.markdown
102
README.markdown
|
@ -5,6 +5,8 @@ Cylon.js is a JavaScript framework for robotics, physical computing, and the Int
|
|||
It provides a simple, but powerful way to create solutions that incorporate
|
||||
multiple, different hardware devices concurrently.
|
||||
|
||||
Want to use Node.js for robots, drones, and IoT devices? You are in the right place.
|
||||
|
||||
Want to use Ruby on robots? Check out our sister project, [Artoo][].
|
||||
|
||||
Want to use Golang to power your robots? Check out our sister project,
|
||||
|
@ -193,16 +195,17 @@ Cylon.start();
|
|||
## Hardware Support
|
||||
|
||||
Cylon.js has an extensible syntax for connecting to multiple, different hardware
|
||||
devices. The following 35 platforms are currently supported:
|
||||
devices. The following 36 platforms are currently supported:
|
||||
|
||||
Platform | Support
|
||||
-------- | -------
|
||||
[Ardrone](http://ardrone2.parrot.com/) | [cylon-ardrone](https://github.com/hybridgroup/cylon-ardrone)
|
||||
[ARDrone](http://ardrone2.parrot.com/) | [cylon-ardrone](https://github.com/hybridgroup/cylon-ardrone)
|
||||
[Arduino](http://www.arduino.cc/) | [cylon-firmata](https://github.com/hybridgroup/cylon-firmata)
|
||||
[Arduino YUN](http://arduino.cc/en/Main/ArduinoBoardYun?from=Products.ArduinoYUN) | [cylon-firmata](https://github.com/hybridgroup/cylon-firmata)
|
||||
[AT&T M2X](https://m2x.att.com) | [cylon-m2x](https://github.com/hybridgroup/cylon-m2x)
|
||||
[Audio]() | [cylon-audio](https://github.com/hybridgroup/cylon-audio)
|
||||
Audio | [cylon-audio](https://github.com/hybridgroup/cylon-audio)
|
||||
[Beaglebone Black](http://beagleboard.org/Products/BeagleBone+Black/) | [cylon-beaglebone](https://github.com/hybridgroup/cylon-beaglebone)
|
||||
[Bebop](http://www.parrot.com/products/bebop-drone/) | [cylon-bebop](https://github.com/hybridgroup/cylon-bebop)
|
||||
[Bluetooth LE](http://en.wikipedia.org/wiki/Bluetooth_low_energy) | [cylon-ble](https://github.com/hybridgroup/cylon-ble)
|
||||
[Crazyflie](http://www.bitcraze.se/) | [cylon-crazyflie](https://github.com/hybridgroup/cylon-crazyflie)
|
||||
[Digispark](http://digistump.com/products/1) | [cylon-digispark](https://github.com/hybridgroup/cylon-digispark)
|
||||
|
@ -217,7 +220,6 @@ Platform | Support
|
|||
[MQTT](http://mqtt.org/) | [cylon-mqtt](https://github.com/hybridgroup/cylon-mqtt)
|
||||
[Nest](http://nest.com/) | [cylon-nest](https://github.com/hybridgroup/cylon-nest)
|
||||
[Neurosky](http://store.neurosky.com/products/mindwave-mobile) | [cylon-neurosky](https://github.com/hybridgroup/cylon-neurosky)
|
||||
[Ollie](http://gosphero.com/ollie) | [cylon-ollie](https://github.com/hybridgroup/cylon-ollie)
|
||||
[OpenCV](http://opencv.org/) | [cylon-opencv](https://github.com/hybridgroup/cylon-opencv)
|
||||
[Phillips Hue](http://www2.meethue.com/) | [cylon-hue](https://github.com/hybridgroup/cylon-hue)
|
||||
[Pebble](http://www.getpebble.com/) | [cylon-pebble](https://github.com/hybridgroup/cylon-pebble)
|
||||
|
@ -228,50 +230,62 @@ Platform | Support
|
|||
[Salesforce](http://www.force.com/) | [cylon-force](https://github.com/hybridgroup/cylon-force)
|
||||
[Skynet](http://skynet.im/) | [cylon-skynet](https://github.com/hybridgroup/cylon-skynet)
|
||||
[Spark](http://www.spark.io/) | [cylon-spark](https://github.com/hybridgroup/cylon-spark)
|
||||
[Speech]() | [cylon-speech](https://github.com/hybridgroup/cylon-speech)
|
||||
Speech | [cylon-speech](https://github.com/hybridgroup/cylon-speech)
|
||||
[Sphero](http://www.gosphero.com/) | [cylon-sphero](https://github.com/hybridgroup/cylon-sphero)
|
||||
[Sphero BLE](http://sphero.com/bb8) | [cylon-sphero-ble](https://github.com/hybridgroup/cylon-sphero-ble)
|
||||
[Tessel](https://tessel.io/) | [cylon-tessel](https://github.com/hybridgroup/cylon-tessel)
|
||||
[WICED Sense](http://www.broadcom.com/products/wiced/sense/) | [cylon-wiced-sense](https://github.com/hybridgroup/cylon-wiced-sense)
|
||||
|
||||
Our implementation of GPIO (General Purpose Input/Output) allows for a shared
|
||||
set of drivers supporting a number of devices:
|
||||
set of drivers supporting 14 different devices:
|
||||
|
||||
- [GPIO](https://en.wikipedia.org/wiki/General_Purpose_Input/Output) <=> [Drivers](https://github.com/hybridgroup/cylon-gpio)
|
||||
- Analog Sensor
|
||||
- Button
|
||||
- Continuous Servo
|
||||
- Direct Pin
|
||||
- IR Rangefinder
|
||||
- IR Range Sensor
|
||||
- LED
|
||||
- MakeyButton
|
||||
- Makey Button (high-resistance button like the [MakeyMakey](http://www.makeymakey.com/))
|
||||
- Maxbotix Ultrasonic Range Finder
|
||||
- Motor
|
||||
- Relay
|
||||
- RGB LED
|
||||
- Servo
|
||||
- Temperature Sensor
|
||||
- TP401 Gas Sensor
|
||||
|
||||
Additionally, we also support a number of I2C (Inter-Integrated Circuit) devices
|
||||
We also support 14 different I2C (Inter-Integrated Circuit) devices
|
||||
through a shared `cylon-i2c` module:
|
||||
|
||||
- [I2C](https://en.wikipedia.org/wiki/I%C2%B2C) <=> [Drivers](https://github.com/hybridgroup/cylon-i2c)
|
||||
- BlinkM
|
||||
- BMP180
|
||||
- BlinkM RGB LED
|
||||
- BMP180 Barometric Pressure + Temperature sensor
|
||||
- Direct I2C
|
||||
- HMC6352 Digital Compass
|
||||
- LCD Display
|
||||
- JHD1313M1 LCD with RGB Backlight
|
||||
- LCD
|
||||
- LIDAR-Lite
|
||||
- LSM9DS0G 9 Degrees of Freedom IMU
|
||||
- LSM9DS0XM 9 Degrees of Freedom IMU
|
||||
- MPL115A2 Barometer/Thermometer
|
||||
- MAG3110 3-Axis Digital Magnetometer
|
||||
- MPL115A2 Digital Barometer & Thermometer
|
||||
- MPU6050 Triple Axis Accelerometer and Gyro
|
||||
- PCA9544a 4-Channel I2C Mux
|
||||
- PCA9685 16-Channel 12-bit PWM/Servo Driver
|
||||
|
||||
In addition to our supported platforms, we have the following user contributed platforms:
|
||||
In addition to our officially supported platforms, we have the following 8 user contributed platforms:
|
||||
|
||||
Platform | Support
|
||||
-------- | -------
|
||||
[APC UPS](http://www.apcupsd.org/) | [cylon-apcupsd](https://github.com/afoninsky/cylon-apcupsd)
|
||||
[iBeacon](https://developer.apple.com/ibeacon/) | [cylon-beacon](https://github.com/juliancheal/cylon-beacon)
|
||||
[Myo](https://www.myo.com/) | [cylon-myo](https://github.com/adaemi/cylon-myo)
|
||||
[One-Wire](https://en.wikipedia.org/wiki/1-Wire) | [cylon-one-wire](https://github.com/rkelly92/cylon-one-wire)
|
||||
[Parrot Rolling Spider](http://www.parrot.com/usa/products/rolling-spider/) | [cylon-rolling-spider](https://github.com/ChrisTheBaron/cylon-rolling-spider)
|
||||
[PCDuino](http://www.pcduino.com/) | [cylon-pcduino](https://github.com/alexwang2013/cylon-pcduino)
|
||||
[iBeacon](https://developer.apple.com/ibeacon/) | [cylon-beacon](https://github.com/juliancheal/cylon-beacon)
|
||||
[Telegram](https://telegram.org/) | [cylon-telegram](https://github.com/afoninsky/cylon-telegram)
|
||||
[WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) | [cylon-wemo](https://github.com/ChrisTheBaron/cylon-wemo)
|
||||
|
||||
We'll also have many more platforms and drivers coming soon, [follow us on Twitter][Twitter] for updates.
|
||||
|
||||
|
@ -282,7 +296,7 @@ We'll also have many more platforms and drivers coming soon, [follow us on Twitt
|
|||
Cylon.js can be run directly in-browser, using the `browserify` NPM module.
|
||||
You can also run it from withing a Chrome connected app, or a PhoneGap mobile app.
|
||||
|
||||
For more info on browser support, and for help with different configurations, you can find more info [in our docs](/documentation/guides/browser-support).
|
||||
For more info on browser support, and for help with different configurations, you can find more info [in our docs](https://cylonjs.com/documentation/guides/browser-support/).
|
||||
|
||||
## API Plugins
|
||||
|
||||
|
@ -328,62 +342,12 @@ If you want to help with documentation, you can find the code for our website at
|
|||
|
||||
## Contributing
|
||||
|
||||
* All patches must be provided under the Apache 2.0 License
|
||||
* Please use the -s option in git to "sign off" that the commit is your work and you are providing it under the Apache 2.0 License
|
||||
* Submit a Github Pull Request to the appropriate branch and ideally discuss the changes with us in IRC.
|
||||
* We will look at the patch, test it out, and give you feedback.
|
||||
* Avoid doing minor whitespace changes, renamings, etc. along with merged content. These will be done by the maintainers from time to time but they can complicate merges and should be done seperately.
|
||||
* Take care to maintain the existing coding style.
|
||||
* Add unit tests for any new or changed functionality & lint and test your code using `make test` and `make lint`.
|
||||
* All pull requests should be "fast forward"
|
||||
* If there are commits after yours use “git rebase -i <new_head_branch>”
|
||||
* If you have local changes you may need to use “git stash”
|
||||
* For git help see [progit](http://git-scm.com/book) which is an awesome (and free) book on git
|
||||
For our contribution guidelines, please go to [CONTRIBUTING.md](https://github.com/hybridgroup/cylon/blob/master/CONTRIBUTING.md).
|
||||
|
||||
## Release History
|
||||
|
||||
Version | Notes
|
||||
------- | -----
|
||||
1.0.0 | Remove deprecated Device and Connection syntax, add Basestar#respond method
|
||||
0.22.2 | Bug-fix for Registry loader
|
||||
0.22.1 | Remove lodash, misc. bug fixes
|
||||
0.22.0 | API extraction, new devices syntax.
|
||||
0.21.2 | Update Robeaux version
|
||||
0.21.1 | Add back debug logging for starting/connecting devices/connections
|
||||
0.21.0 | Remove Connection/Device objects, update Robot connection/device syntax, fluent syntax updates
|
||||
0.20.2 | Correct API issues, possible issue with test setups
|
||||
0.20.1 | Revert accidental scrict handling of param in driver initializer
|
||||
0.20.0 | Browser support, new module loading, log level support, misc. development changes
|
||||
0.19.1 | Correct issue with dynamic method proxying
|
||||
0.19.0 | Fluent syntax, improved start/halt, various other updates
|
||||
0.18.0 | Updates Robot and Driver commands structure
|
||||
0.17.0 | Updates to API to match CPPP-IO spec
|
||||
0.16.0 | New IO Utils, removal of Utils#bind, add Adaptor#_noop method.
|
||||
0.15.1 | Fixed issue with the API on Tessel
|
||||
0.15.0 | Better halting, cleaner startup, removed 'connect' and 'start' events, and misc other cleanups/refactors.
|
||||
0.14.0 | Removal of node-namespace and misc. cleanup
|
||||
0.13.3 | Fixes bug with disconnect functions not being called.
|
||||
0.13.2 | Use pure Express, adds server-sent-events, upd API.
|
||||
0.13.1 | Add API authentication and HTTPS support
|
||||
0.13.0 | Set minimum Node version to 0.10.20, add utils to global namespace and improve initialization routines
|
||||
0.12.0 | Extraction of CLI tooling
|
||||
0.11.2 | bugfixes
|
||||
0.11.0 | Refactor into pure JavaScript
|
||||
0.10.4 | Add JS helper functions
|
||||
0.10.3 | Fix dependency issue
|
||||
0.10.2 | Create connections convenience vars, refactor config loading
|
||||
0.10.1 | Updates required for test driven robotics, update Robeaux version, bugfixes
|
||||
0.10.0 | Use Robeaux UX, add CLI commands for helping connect to devices, bugfixes
|
||||
0.9.0 | Add AngularJS web interface to API, extensible commands for CLI
|
||||
0.8.0 | Refactored Adaptor and Driver into proper base classes for easier authoring of new modules
|
||||
0.7.0 | cylon command for generating new adaptors, support code for better GPIO support, literate examples
|
||||
0.6.0 | API exposes robot commands, fixes issues in driver/adaptor init
|
||||
0.5.0 | Improve API, add GPIO support for reuse in adaptors
|
||||
0.4.0 | Refactor proxy in Cylon.Basestar, improve API
|
||||
0.3.0 | Improved Cylon.Basestar, and added API
|
||||
0.2.0 | Cylon.Basestar to help develop external adaptors/drivers
|
||||
0.1.0 | Initial release for ongoing development
|
||||
For the release history, please go to [RELEASES.md](https://github.com/hybridgroup/cylon/blob/master/RELEASES.md).
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2013-2015 The Hybrid Group. Licensed under the Apache 2.0 license.
|
||||
Copyright (c) 2013-2016 The Hybrid Group. Licensed under the Apache 2.0 license.
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
## Release History
|
||||
|
||||
Version | Notes
|
||||
------- | -----
|
||||
1.3.0 | Smarter module loading rules, adaptor alias
|
||||
1.2.0 | Dynamic connections & devices, more time helpers, simplified logging
|
||||
1.1.0 | Clean ups, refactorings, misc. bug fixes.
|
||||
1.0.0 | Remove deprecated Device and Connection syntax, add Basestar#respond method
|
||||
0.22.2 | Bug-fix for Registry loader
|
||||
0.22.1 | Remove lodash, misc. bug fixes
|
||||
0.22.0 | API extraction, new devices syntax.
|
||||
0.21.2 | Update Robeaux version
|
||||
0.21.1 | Add back debug logging for starting/connecting devices/connections
|
||||
0.21.0 | Remove Connection/Device objects, update Robot connection/device syntax, fluent syntax updates
|
||||
0.20.2 | Correct API issues, possible issue with test setups
|
||||
0.20.1 | Revert accidental scrict handling of param in driver initializer
|
||||
0.20.0 | Browser support, new module loading, log level support, misc. development changes
|
||||
0.19.1 | Correct issue with dynamic method proxying
|
||||
0.19.0 | Fluent syntax, improved start/halt, various other updates
|
||||
0.18.0 | Updates Robot and Driver commands structure
|
||||
0.17.0 | Updates to API to match CPPP-IO spec
|
||||
0.16.0 | New IO Utils, removal of Utils#bind, add Adaptor#_noop method.
|
||||
0.15.1 | Fixed issue with the API on Tessel
|
||||
0.15.0 | Better halting, cleaner startup, removed 'connect' and 'start' events, and misc other cleanups/refactors.
|
||||
0.14.0 | Removal of node-namespace and misc. cleanup
|
||||
0.13.3 | Fixes bug with disconnect functions not being called.
|
||||
0.13.2 | Use pure Express, adds server-sent-events, upd API.
|
||||
0.13.1 | Add API authentication and HTTPS support
|
||||
0.13.0 | Set minimum Node version to 0.10.20, add utils to global namespace and improve initialization routines
|
||||
0.12.0 | Extraction of CLI tooling
|
||||
0.11.2 | bugfixes
|
||||
0.11.0 | Refactor into pure JavaScript
|
||||
0.10.4 | Add JS helper functions
|
||||
0.10.3 | Fix dependency issue
|
||||
0.10.2 | Create connections convenience vars, refactor config loading
|
||||
0.10.1 | Updates required for test driven robotics, update Robeaux version, bugfixes
|
||||
0.10.0 | Use Robeaux UX, add CLI commands for helping connect to devices, bugfixes
|
||||
0.9.0 | Add AngularJS web interface to API, extensible commands for CLI
|
||||
0.8.0 | Refactored Adaptor and Driver into proper base classes for easier authoring of new modules
|
||||
0.7.0 | cylon command for generating new adaptors, support code for better GPIO support, literate examples
|
||||
0.6.0 | API exposes robot commands, fixes issues in driver/adaptor init
|
||||
0.5.0 | Improve API, add GPIO support for reuse in adaptors
|
||||
0.4.0 | Refactor proxy in Cylon.Basestar, improve API
|
||||
0.3.0 | Improved Cylon.Basestar, and added API
|
||||
0.2.0 | Cylon.Basestar to help develop external adaptors/drivers
|
||||
0.1.0 | Initial release for ongoing development
|
|
@ -0,0 +1,25 @@
|
|||
"use strict";
|
||||
|
||||
var Cylon = require("cylon");
|
||||
|
||||
Cylon.robot({
|
||||
connections: {
|
||||
loopback: { adaptor: "loopback" }
|
||||
},
|
||||
|
||||
connectPinger: function() {
|
||||
this.device("pinger",
|
||||
{connection: "loopback", driver: "ping"});
|
||||
this.startDevice(this.devices.pinger, function() {
|
||||
console.log("Get ready to ping...");
|
||||
});
|
||||
},
|
||||
|
||||
work: function(my) {
|
||||
my.connectPinger();
|
||||
|
||||
every((1).second(), function() {
|
||||
console.log(my.pinger.ping());
|
||||
});
|
||||
}
|
||||
}).start();
|
|
@ -0,0 +1,38 @@
|
|||
"use strict";
|
||||
|
||||
var MCP = require("./lib/mcp");
|
||||
|
||||
module.exports = {
|
||||
MCP: require("./lib/mcp"),
|
||||
|
||||
Robot: require("./lib/robot"),
|
||||
|
||||
Driver: require("./lib/driver"),
|
||||
Adaptor: require("./lib/adaptor"),
|
||||
|
||||
Utils: require("./lib/utils"),
|
||||
Logger: require("./lib/logger"),
|
||||
|
||||
IO: {
|
||||
DigitalPin: require("./lib/io/digital-pin"),
|
||||
Utils: require("./lib/io/utils")
|
||||
},
|
||||
|
||||
robot: MCP.create,
|
||||
api: require("./lib/api").create,
|
||||
config: require("./lib/config").update,
|
||||
|
||||
start: MCP.start,
|
||||
halt: MCP.halt
|
||||
};
|
||||
|
||||
process.on("SIGINT", function() {
|
||||
MCP.halt(process.kill.bind(process, process.pid));
|
||||
});
|
||||
|
||||
if (process.platform === "win32") {
|
||||
var io = { input: process.stdin, output: process.stdout },
|
||||
quit = process.emit.bind(process, "SIGINT");
|
||||
|
||||
require("readline").createInterface(io).on("SIGINT", quit);
|
||||
}
|
|
@ -1,11 +1,3 @@
|
|||
/**
|
||||
* Cylon.js - Adaptor base class
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var Basestar = require("./basestar"),
|
||||
|
@ -16,14 +8,20 @@ function formatErrorMessage(name, message) {
|
|||
return ["Error in connection", "'" + name + "'", "- " + message].join(" ");
|
||||
}
|
||||
|
||||
// Public: Creates a new Adaptor
|
||||
//
|
||||
// opts - hash of acceptable params
|
||||
// name - name of the Adaptor, used when printing to console
|
||||
// connection - Connection the adaptor will use to proxy commands/events
|
||||
//
|
||||
// Returns a new Adaptor
|
||||
/**
|
||||
* Adaptor class
|
||||
*
|
||||
* @constructor Adaptor
|
||||
*
|
||||
* @param {Object} [opts] adaptor options
|
||||
* @param {String} [opts.name] the adaptor's name
|
||||
* @param {Object} [opts.robot] the robot the adaptor belongs to
|
||||
* @param {Object} [opts.host] the host the adaptor will connect to
|
||||
* @param {Object} [opts.port] the port the adaptor will connect to
|
||||
*/
|
||||
var Adaptor = module.exports = function Adaptor(opts) {
|
||||
Adaptor.__super__.constructor.apply(this, arguments);
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
this.name = opts.name;
|
||||
|
@ -47,9 +45,12 @@ var Adaptor = module.exports = function Adaptor(opts) {
|
|||
|
||||
Utils.subclass(Adaptor, Basestar);
|
||||
|
||||
// Public: Basic #connect function. Must be overwritten by a descendent class
|
||||
//
|
||||
// Returns nothing, throws an error
|
||||
/**
|
||||
* A base connect function. Must be overwritten by a descendent.
|
||||
*
|
||||
* @throws Error if not overridden by a child class
|
||||
* @return {void}
|
||||
*/
|
||||
Adaptor.prototype.connect = function() {
|
||||
var message = formatErrorMessage(
|
||||
this.name,
|
||||
|
@ -59,9 +60,12 @@ Adaptor.prototype.connect = function() {
|
|||
throw new Error(message);
|
||||
};
|
||||
|
||||
// Public: Basic #disconnect function. Must be overwritten by a descendent class
|
||||
//
|
||||
// Returns nothing, throws an error
|
||||
/**
|
||||
* A base disconnect function. Must be overwritten by a descendent.
|
||||
*
|
||||
* @throws Error if not overridden by a child class
|
||||
* @return {void}
|
||||
*/
|
||||
Adaptor.prototype.disconnect = function() {
|
||||
var message = formatErrorMessage(
|
||||
this.name,
|
||||
|
@ -71,9 +75,11 @@ Adaptor.prototype.disconnect = function() {
|
|||
throw new Error(message);
|
||||
};
|
||||
|
||||
// Public: Expresses the Connection in JSON format
|
||||
//
|
||||
// Returns an Object containing Connection data
|
||||
/**
|
||||
* Expresses the Adaptor in a JSON-serializable format
|
||||
*
|
||||
* @return {Object} a representation of the Adaptor in a serializable format
|
||||
*/
|
||||
Adaptor.prototype.toJSON = function() {
|
||||
return {
|
||||
name: this.name,
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
"use strict";
|
||||
|
||||
var MCP = require("./mcp"),
|
||||
Logger = require("./logger"),
|
||||
_ = require("./utils/helpers");
|
||||
|
||||
var api = module.exports = {};
|
||||
|
||||
api.instances = [];
|
||||
|
||||
/**
|
||||
* Creates a new API instance
|
||||
*
|
||||
* @param {String} [Server] which API plugin to use (e.g. "http" loads
|
||||
* cylon-api-http)
|
||||
* @param {Object} opts options for the new API instance
|
||||
* @return {void}
|
||||
*/
|
||||
api.create = function create(Server, opts) {
|
||||
// if only passed options (or nothing), assume HTTP server
|
||||
if (Server == null || _.isObject(Server) && !_.isFunction(Server)) {
|
||||
opts = Server;
|
||||
Server = "http";
|
||||
}
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
if (_.isString(Server)) {
|
||||
var req = "cylon-api-" + Server;
|
||||
|
||||
try {
|
||||
Server = require(req);
|
||||
} catch (e) {
|
||||
if (e.code !== "MODULE_NOT_FOUND") {
|
||||
throw e;
|
||||
}
|
||||
|
||||
[
|
||||
"Cannot find the " + req + " API module.",
|
||||
"You may be able to install it: `npm install " + req + "`"
|
||||
].forEach(Logger.log);
|
||||
|
||||
throw new Error("Missing API plugin - cannot proceed");
|
||||
}
|
||||
}
|
||||
|
||||
opts.mcp = MCP;
|
||||
|
||||
var instance = new Server(opts);
|
||||
api.instances.push(instance);
|
||||
instance.start();
|
||||
};
|
107
lib/basestar.js
107
lib/basestar.js
|
@ -1,11 +1,3 @@
|
|||
/**
|
||||
* Cylon.js - Basestar base class
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var EventEmitter = require("events").EventEmitter;
|
||||
|
@ -13,33 +5,41 @@ var EventEmitter = require("events").EventEmitter;
|
|||
var Utils = require("./utils"),
|
||||
_ = require("./utils/helpers");
|
||||
|
||||
// Basestar is a base class to be used when writing external Cylon adaptors and
|
||||
// drivers. It provides some useful base methods and functionality
|
||||
//
|
||||
// It also extends EventEmitter, so child classes are capable of emitting events
|
||||
// for other parts of the system to handle.
|
||||
/**
|
||||
* The Basestar class is a wrapper class around EventEmitter that underpins most
|
||||
* other Cylon adaptor/driver classes, providing useful external base methods
|
||||
* and functionality.
|
||||
*
|
||||
* @constructor Basestar
|
||||
*/
|
||||
var Basestar = module.exports = function Basestar() {
|
||||
Utils.classCallCheck(this, Basestar);
|
||||
};
|
||||
|
||||
Utils.subclass(Basestar, EventEmitter);
|
||||
|
||||
// Public: Proxies calls from all methods in the object to a target object
|
||||
//
|
||||
// methods - array of methods to proxy
|
||||
// target - object to proxy methods to
|
||||
// source - object to proxy methods from
|
||||
// force - whether or not to overwrite existing method definitions
|
||||
//
|
||||
// Returns the klass where the methods have been proxied
|
||||
/**
|
||||
* Proxies calls from all methods in the source to a target object
|
||||
*
|
||||
* @param {String[]} methods methods to proxy
|
||||
* @param {Object} target object to proxy methods to
|
||||
* @param {Object} source object to proxy methods from
|
||||
* @param {Boolean} [force=false] whether or not to overwrite existing methods
|
||||
* @return {Object} the source
|
||||
*/
|
||||
Basestar.prototype.proxyMethods = Utils.proxyFunctionsToObject;
|
||||
|
||||
// Public: Triggers a callback and emits an event with provided data
|
||||
//
|
||||
// event - name of event to be triggered
|
||||
// callback - callback function to be triggered
|
||||
// ...data - additional arguments to be passed to both event/callback
|
||||
//
|
||||
// Returns nothing
|
||||
/**
|
||||
* Triggers the provided callback, and emits an event with the provided data.
|
||||
*
|
||||
* If an error is provided, emits the 'error' event.
|
||||
*
|
||||
* @param {String} event what event to emit
|
||||
* @param {Function} callback function to be invoked with error/data
|
||||
* @param {*} err possible error value
|
||||
* @param {...*} data data values to be passed to error/callback
|
||||
* @return {void}
|
||||
*/
|
||||
Basestar.prototype.respond = function(event, callback, err) {
|
||||
var args = Array.prototype.slice.call(arguments, 3);
|
||||
|
||||
|
@ -54,17 +54,17 @@ Basestar.prototype.respond = function(event, callback, err) {
|
|||
}
|
||||
};
|
||||
|
||||
// Public: Defines an event handler that proxies events from a source object
|
||||
// to a target object
|
||||
//
|
||||
// opts - object containing options:
|
||||
// - targetEventName or eventName - event that should be emitted from the
|
||||
// target
|
||||
// - target - object to proxy event to
|
||||
// - source - object to proxy event from
|
||||
// - sendUpdate - whether or not to send an "update" event
|
||||
//
|
||||
// Returns the source
|
||||
/**
|
||||
* Defines an event handler to proxy events from a source object to a target
|
||||
*
|
||||
* @param {Object} opts event options
|
||||
* @param {EventEmitter} opts.source source of events to listen for
|
||||
* @param {EventEmitter} opts.target target new events should be emitted from
|
||||
* @param {String} opts.eventName name of event to listen for, and proxy
|
||||
* @param {Bool} [opts.sendUpdate=false] whether to emit the 'update' event
|
||||
* @param {String} [opts.targetEventName] new event name to emit from target
|
||||
* @return {EventEmitter} the source object
|
||||
*/
|
||||
Basestar.prototype.defineEvent = function(opts) {
|
||||
opts.sendUpdate = opts.sendUpdate || false;
|
||||
opts.targetEventName = opts.targetEventName || opts.eventName;
|
||||
|
@ -83,23 +83,26 @@ Basestar.prototype.defineEvent = function(opts) {
|
|||
return opts.source;
|
||||
};
|
||||
|
||||
// Public: Creates an event handler that proxies events from an adaptor"s
|
||||
// "connector" (reference to whatever module is actually talking to the hw)
|
||||
// to the adaptor
|
||||
//
|
||||
// opts - hash of opts to be passed to defineEvent()
|
||||
//
|
||||
// Returns this.connector
|
||||
/**
|
||||
* A #defineEvent shorthand for adaptors.
|
||||
*
|
||||
* Proxies events from an adaptor's connector to the adaptor itself.
|
||||
*
|
||||
* @param {Object} opts proxy options
|
||||
* @return {EventEmitter} the adaptor's connector
|
||||
*/
|
||||
Basestar.prototype.defineAdaptorEvent = function(opts) {
|
||||
return this._proxyEvents(opts, this.connector, this);
|
||||
};
|
||||
|
||||
// Public: Creates an event handler that proxies events from a driver"s
|
||||
// connection to the driver
|
||||
//
|
||||
// opts - hash of opts to be passed to defineEvent()
|
||||
//
|
||||
// Returns this.connection
|
||||
/**
|
||||
* A #defineEvent shorthand for drivers.
|
||||
*
|
||||
* Proxies events from an driver's connection to the driver itself.
|
||||
*
|
||||
* @param {Object} opts proxy options
|
||||
* @return {EventEmitter} the driver's connection
|
||||
*/
|
||||
Basestar.prototype.defineDriverEvent = function(opts) {
|
||||
return this._proxyEvents(opts, this.connection, this);
|
||||
};
|
||||
|
|
|
@ -1,16 +1,58 @@
|
|||
/*
|
||||
* Cylon.js - Internal Configuration
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
logging: {},
|
||||
var _ = require("./utils/helpers");
|
||||
|
||||
// are we in TDR test mode? Used to stub out adaptors/drivers.
|
||||
testMode: false
|
||||
var config = module.exports = {},
|
||||
callbacks = [];
|
||||
|
||||
// default data
|
||||
config.haltTimeout = 3000;
|
||||
config.testMode = false;
|
||||
config.logger = null;
|
||||
config.silent = false;
|
||||
config.debug = false;
|
||||
|
||||
/**
|
||||
* Updates the Config, and triggers handler callbacks
|
||||
*
|
||||
* @param {Object} data new configuration information to set
|
||||
* @return {Object} the updated configuration
|
||||
*/
|
||||
config.update = function update(data) {
|
||||
var forbidden = ["update", "subscribe", "unsubscribe"];
|
||||
|
||||
Object.keys(data).forEach(function(key) {
|
||||
if (~forbidden.indexOf(key)) { delete data[key]; }
|
||||
});
|
||||
|
||||
if (!Object.keys(data).length) {
|
||||
return config;
|
||||
}
|
||||
|
||||
_.extend(config, data);
|
||||
|
||||
callbacks.forEach(function(callback) { callback(data); });
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
/**
|
||||
* Subscribes a function to be called whenever the config is updated
|
||||
*
|
||||
* @param {Function} callback function to be called with updated data
|
||||
* @return {void}
|
||||
*/
|
||||
config.subscribe = function subscribe(callback) {
|
||||
callbacks.push(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Unsubscribes a callback from configuration changes
|
||||
*
|
||||
* @param {Function} callback function to unsubscribe from changes
|
||||
* @return {void}
|
||||
*/
|
||||
config.unsubscribe = function unsubscribe(callback) {
|
||||
var idx = callbacks.indexOf(callback);
|
||||
if (idx >= 0) { callbacks.splice(idx, 1); }
|
||||
};
|
||||
|
|
185
lib/cylon.js
185
lib/cylon.js
|
@ -1,185 +0,0 @@
|
|||
/*
|
||||
* Cylon.js - Master Control Program
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var Async = require("async");
|
||||
|
||||
var Logger = require("./logger"),
|
||||
Robot = require("./robot"),
|
||||
Config = require("./config"),
|
||||
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;
|
||||
};
|
||||
|
||||
// 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);
|
||||
});
|
||||
});
|
|
@ -1,11 +1,3 @@
|
|||
/**
|
||||
* Cylon.js - Driver base class
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var Basestar = require("./basestar"),
|
||||
|
@ -16,14 +8,20 @@ function formatErrorMessage(name, message) {
|
|||
return ["Error in driver", "'" + name + "'", "- " + message].join(" ");
|
||||
}
|
||||
|
||||
// Public: Creates a new Driver
|
||||
//
|
||||
// opts - hash of acceptable params
|
||||
// name - name of the Driver, used when printing to console
|
||||
// device - Device the driver will use to proxy commands/events
|
||||
//
|
||||
// Returns a new Driver
|
||||
/**
|
||||
* Driver class
|
||||
*
|
||||
* @constructor Driver
|
||||
* @param {Object} [opts] driver options
|
||||
* @param {String} [opts.name] the driver's name
|
||||
* @param {Object} [opts.robot] the robot the driver belongs to
|
||||
* @param {Object} [opts.connection] the adaptor the driver works through
|
||||
* @param {Number} [opts.pin] the pin number the driver should have
|
||||
* @param {Number} [opts.interval=10] read interval in milliseconds
|
||||
*/
|
||||
var Driver = module.exports = function Driver(opts) {
|
||||
Driver.__super__.constructor.apply(this, arguments);
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
this.name = opts.name;
|
||||
|
@ -51,9 +49,12 @@ var Driver = module.exports = function Driver(opts) {
|
|||
|
||||
Utils.subclass(Driver, Basestar);
|
||||
|
||||
// Public: Basic #start function. Must be overwritten by a descendent class
|
||||
//
|
||||
// Returns nothing, throws an error
|
||||
/**
|
||||
* A base start function. Must be overwritten by a descendent.
|
||||
*
|
||||
* @throws Error if not overridden by a child class
|
||||
* @return {void}
|
||||
*/
|
||||
Driver.prototype.start = function() {
|
||||
var message = formatErrorMessage(
|
||||
this.name,
|
||||
|
@ -63,9 +64,12 @@ Driver.prototype.start = function() {
|
|||
throw new Error(message);
|
||||
};
|
||||
|
||||
// Public: Basic #halt function. Must be overwritten by a descendent class
|
||||
//
|
||||
// Returns nothing, throws an error
|
||||
/**
|
||||
* A base halt function. Must be overwritten by a descendent.
|
||||
*
|
||||
* @throws Error if not overridden by a child class
|
||||
* @return {void}
|
||||
*/
|
||||
Driver.prototype.halt = function() {
|
||||
var message = formatErrorMessage(
|
||||
this.name,
|
||||
|
@ -75,6 +79,16 @@ Driver.prototype.halt = function() {
|
|||
throw new Error(message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets up an array of commands for the Driver.
|
||||
*
|
||||
* Proxies commands from the Driver to its connection (or a manually specified
|
||||
* proxy), and adds a snake_cased version to the driver's commands object.
|
||||
*
|
||||
* @param {String[]} commands an array of driver commands
|
||||
* @param {Object} [proxy=this.connection] proxy target
|
||||
* @return {void}
|
||||
*/
|
||||
Driver.prototype.setupCommands = function(commands, proxy) {
|
||||
if (proxy == null) {
|
||||
proxy = this.connection;
|
||||
|
@ -82,9 +96,13 @@ Driver.prototype.setupCommands = function(commands, proxy) {
|
|||
|
||||
Utils.proxyFunctionsToObject(commands, proxy, this);
|
||||
|
||||
function endsWith(string, substr) {
|
||||
return string.indexOf(substr, string.length - substr.length) !== -1;
|
||||
}
|
||||
|
||||
commands.forEach(function(command) {
|
||||
var snakeCase = command.replace(/[A-Z]+/g, function(match) {
|
||||
if (match.length > 1) {
|
||||
if (match.length > 1 && !endsWith(command, match)) {
|
||||
match = match.replace(/[A-Z]$/, function(m) {
|
||||
return "_" + m.toLowerCase();
|
||||
});
|
||||
|
@ -97,6 +115,11 @@ Driver.prototype.setupCommands = function(commands, proxy) {
|
|||
}, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Expresses the Driver in a JSON-serializable format
|
||||
*
|
||||
* @return {Object} a representation of the Driver in a serializable format
|
||||
*/
|
||||
Driver.prototype.toJSON = function() {
|
||||
return {
|
||||
name: this.name,
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
/**
|
||||
* Cylon.js - Adaptor/Driver initializer
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var Registry = require("./registry"),
|
||||
|
@ -31,6 +23,11 @@ module.exports = function Initializer(type, opts) {
|
|||
mod = Registry.findBy(type, opts[type]);
|
||||
}
|
||||
|
||||
if (!mod) {
|
||||
var err = [ "Unable to find", type, "for", opts[type] ].join(" ");
|
||||
throw new Error(err);
|
||||
}
|
||||
|
||||
var obj = mod[type](opts);
|
||||
|
||||
_.each(obj, function(prop, name) {
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
/**
|
||||
* Cylon.js - GPIO Digital Pin class
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
/* eslint no-sync: 0 */
|
||||
|
||||
"use strict";
|
||||
|
@ -20,8 +12,15 @@ var GPIO_PATH = "/sys/class/gpio";
|
|||
var GPIO_READ = "in";
|
||||
var GPIO_WRITE = "out";
|
||||
|
||||
// DigitalPin class offers an interface with the Linux GPIO system present in
|
||||
// single-board computers such as a Raspberry Pi, or a BeagleBone
|
||||
/**
|
||||
* The DigitalPin class provides an interface with the Linux GPIO system present
|
||||
* in single-board computers such as Raspberry Pi, or Beaglebone Black.
|
||||
*
|
||||
* @constructor DigitalPin
|
||||
* @param {Object} opts digital pin options
|
||||
* @param {String} pin which pin number to use
|
||||
* @param {String} mode which pin mode to use
|
||||
*/
|
||||
var DigitalPin = module.exports = function DigitalPin(opts) {
|
||||
this.pinNum = opts.pin.toString();
|
||||
this.status = "low";
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
/**
|
||||
* Cylon.js - I/O Utils
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Returns { period: int, duty: int }
|
||||
// Calculated based on params value, freq, pulseWidth = { min: int, max: int }
|
||||
// pulseWidth min and max need to be specified in microseconds
|
||||
/**
|
||||
* Calculates PWM Period and Duty based on provided params.
|
||||
*
|
||||
* @param {Number} scaledDuty the scaled duty value
|
||||
* @param {Number} freq frequency to use
|
||||
* @param {Number} pulseWidth pulse width
|
||||
* @param {String} [polarity=high] polarity value (high or low)
|
||||
* @return {Object} calculated period and duty encapsulated in an object
|
||||
*/
|
||||
periodAndDuty: function(scaledDuty, freq, pulseWidth, polarity) {
|
||||
var period, duty, maxDuty;
|
||||
|
||||
|
|
103
lib/logger.js
103
lib/logger.js
|
@ -1,53 +1,82 @@
|
|||
/**
|
||||
* Cylon.js - Logger
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var levels = ["debug", "info", "warn", "error", "fatal"];
|
||||
/* eslint no-use-before-define: 0 */
|
||||
|
||||
var BasicLogger = require("./logger/basic_logger"),
|
||||
NullLogger = require("./logger/null_logger"),
|
||||
Config = require("./config"),
|
||||
var Config = require("./config"),
|
||||
_ = require("./utils/helpers");
|
||||
|
||||
var BasicLogger = function basiclogger(str) {
|
||||
var prefix = new Date().toISOString() + " : ";
|
||||
console.log(prefix + str);
|
||||
};
|
||||
|
||||
var NullLogger = function nulllogger() {};
|
||||
|
||||
var Logger = module.exports = {
|
||||
setup: function(opts) {
|
||||
if (_.isObject(opts)) {
|
||||
Config.logging = _.extend(Config.logging, opts);
|
||||
}
|
||||
setup: setup,
|
||||
|
||||
var logger = Config.logging.logger,
|
||||
level = Config.logging.level || "info";
|
||||
|
||||
// --debug CLI flag overrides any other option
|
||||
if (_.includes(process.argv, "--debug")) {
|
||||
level = "debug";
|
||||
}
|
||||
|
||||
logger = (logger == null) ? BasicLogger : logger;
|
||||
|
||||
this.logger = logger || NullLogger;
|
||||
this.level = level;
|
||||
|
||||
return this;
|
||||
should: {
|
||||
log: true,
|
||||
debug: false
|
||||
},
|
||||
|
||||
toString: function() {
|
||||
return this.logger.toString();
|
||||
log: function log(str) {
|
||||
if (Logger.should.log) {
|
||||
Logger.logger.call(Logger.logger, str);
|
||||
}
|
||||
},
|
||||
|
||||
debug: function debug(str) {
|
||||
if (Logger.should.log && Logger.should.debug) {
|
||||
Logger.logger.call(Logger.logger, str);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Logger.setup();
|
||||
function setup(opts) {
|
||||
if (_.isObject(opts)) { _.extend(Config, opts); }
|
||||
|
||||
levels.forEach(function(level) {
|
||||
Logger[level] = function() {
|
||||
if (levels.indexOf(level) >= levels.indexOf(Logger.level)) {
|
||||
return Logger.logger[level].apply(Logger.logger, arguments);
|
||||
var logger = Config.logger;
|
||||
|
||||
// if no logger supplied, use basic logger
|
||||
if (logger == null) { logger = BasicLogger; }
|
||||
|
||||
// if logger is still falsy, use NullLogger
|
||||
Logger.logger = logger || NullLogger;
|
||||
|
||||
Logger.should.log = !Config.silent;
|
||||
Logger.should.debug = Config.debug;
|
||||
|
||||
// --silent CLI flag overrides
|
||||
if (_.includes(process.argv, "--silent")) {
|
||||
Logger.should.log = false;
|
||||
}
|
||||
|
||||
// --debug CLI flag overrides
|
||||
if (_.includes(process.argv, "--debug")) {
|
||||
Logger.should.debug = false;
|
||||
}
|
||||
|
||||
return Logger;
|
||||
}
|
||||
|
||||
setup();
|
||||
Config.subscribe(setup);
|
||||
|
||||
// deprecated holdovers
|
||||
["info", "warn", "error", "fatal"].forEach(function(method) {
|
||||
var called = false;
|
||||
|
||||
function showDeprecationNotice() {
|
||||
console.log("The method Logger#" + method + " has been deprecated.");
|
||||
console.log("It will be removed in Cylon 2.0.0.");
|
||||
console.log("Please switch to using the #log or #debug Logger methods");
|
||||
|
||||
called = true;
|
||||
}
|
||||
|
||||
Logger[method] = function() {
|
||||
if (!called) { showDeprecationNotice(); }
|
||||
Logger.log.apply(null, arguments);
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
/**
|
||||
* Cylon.js - Basic Logger
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var getArgs = function(args) {
|
||||
return args.length >= 1 ? [].slice.call(args, 0) : [];
|
||||
};
|
||||
|
||||
var logString = function(type) {
|
||||
var time = new Date().toISOString(),
|
||||
upcase = String(type).toUpperCase(),
|
||||
padded = String(" " + upcase).slice(-5);
|
||||
|
||||
return upcase[0] + ", [" + time + "] " + padded + " -- :";
|
||||
};
|
||||
|
||||
// The BasicLogger logs to console.log
|
||||
var BasicLogger = module.exports = {
|
||||
toString: function() { return "BasicLogger"; },
|
||||
};
|
||||
|
||||
["debug", "info", "warn", "error", "fatal"].forEach(function(type) {
|
||||
BasicLogger[type] = function() {
|
||||
var args = getArgs(arguments);
|
||||
return console.log.apply(console, [].concat(logString(type), args));
|
||||
};
|
||||
});
|
|
@ -1,19 +0,0 @@
|
|||
/**
|
||||
* Cylon.js - Null Logger
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// The NullLogger is designed for cases where you want absolutely nothing to
|
||||
// print to anywhere. Every proxied method from the Logger returns a noop.
|
||||
var NullLogger = module.exports = {
|
||||
toString: function() { return "NullLogger"; }
|
||||
};
|
||||
|
||||
["debug", "info", "warn", "error", "fatal"].forEach(function(type) {
|
||||
NullLogger[type] = function() {};
|
||||
});
|
|
@ -0,0 +1,82 @@
|
|||
"use strict";
|
||||
|
||||
var EventEmitter = require("events").EventEmitter;
|
||||
|
||||
var Config = require("./config"),
|
||||
Logger = require("./logger"),
|
||||
Utils = require("./utils"),
|
||||
Robot = require("./robot"),
|
||||
_ = require("./utils/helpers");
|
||||
|
||||
var mcp = module.exports = new EventEmitter();
|
||||
|
||||
mcp.robots = {};
|
||||
mcp.commands = {};
|
||||
mcp.events = [ "robot_added", "robot_removed" ];
|
||||
|
||||
/**
|
||||
* Creates a new Robot with the provided options.
|
||||
*
|
||||
* @param {Object} opts robot options
|
||||
* @return {Robot} the new robot
|
||||
*/
|
||||
mcp.create = function create(opts) {
|
||||
opts = opts || {};
|
||||
|
||||
// check if a robot with the same name exists already
|
||||
if (opts.name && mcp.robots[opts.name]) {
|
||||
var original = opts.name;
|
||||
opts.name = Utils.makeUnique(original, Object.keys(mcp.robots));
|
||||
|
||||
var str = "Robot names must be unique. Renaming '";
|
||||
str += original + "' to '" + opts.name + "'";
|
||||
|
||||
Logger.log(str);
|
||||
}
|
||||
|
||||
var bot = new Robot(opts);
|
||||
mcp.robots[bot.name] = bot;
|
||||
mcp.emit("robot_added", bot.name);
|
||||
|
||||
return bot;
|
||||
};
|
||||
|
||||
mcp.start = function start(callback) {
|
||||
var fns = _.pluck(mcp.robots, "start");
|
||||
|
||||
_.parallel(fns, function() {
|
||||
var mode = Utils.fetch(Config, "workMode", "async");
|
||||
if (mode === "sync") { _.invoke(mcp.robots, "startWork"); }
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Halts all MCP robots.
|
||||
*
|
||||
* @param {Function} callback function to call when done halting robots
|
||||
* @return {void}
|
||||
*/
|
||||
mcp.halt = function halt(callback) {
|
||||
callback = callback || function() {};
|
||||
|
||||
var timeout = setTimeout(callback, Config.haltTimeout || 3000);
|
||||
|
||||
_.parallel(_.pluck(mcp.robots, "halt"), function() {
|
||||
clearTimeout(timeout);
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Serializes MCP robots, commands, and events into a JSON-serializable Object.
|
||||
*
|
||||
* @return {Object} a serializable representation of the MCP
|
||||
*/
|
||||
mcp.toJSON = function() {
|
||||
return {
|
||||
robots: _.invoke(mcp.robots, "toJSON"),
|
||||
commands: Object.keys(mcp.commands),
|
||||
events: mcp.events
|
||||
};
|
||||
};
|
|
@ -1,15 +1,8 @@
|
|||
/**
|
||||
* Cylon.js - Module Registry
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var Logger = require("./logger"),
|
||||
_ = require("./utils/helpers");
|
||||
_ = require("./utils/helpers"),
|
||||
path = require("path");
|
||||
|
||||
// Explicitly these modules here, so Browserify can grab them later
|
||||
require("./test/loopback");
|
||||
|
@ -38,7 +31,11 @@ var Registry = module.exports = {
|
|||
var pkg;
|
||||
|
||||
try {
|
||||
if (this.isModuleInDevelopment(module)) {
|
||||
pkg = require(path.resolve(".") + "/index");
|
||||
} else {
|
||||
pkg = require(module);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.code === "MODULE_NOT_FOUND") {
|
||||
missingModuleError(module);
|
||||
|
@ -107,6 +104,10 @@ var Registry = module.exports = {
|
|||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
isModuleInDevelopment: function(module) {
|
||||
return (path.basename(path.resolve(".")) === module);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
388
lib/robot.js
388
lib/robot.js
|
@ -1,11 +1,3 @@
|
|||
/**
|
||||
* 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"),
|
||||
|
@ -14,44 +6,40 @@ var initializer = require("./initializer"),
|
|||
Config = require("./config"),
|
||||
_ = require("./utils/helpers");
|
||||
|
||||
var Async = require("async"),
|
||||
EventEmitter = require("events").EventEmitter;
|
||||
var validator = require("./validator");
|
||||
|
||||
// Public: Creates a new Robot
|
||||
//
|
||||
// opts - object containing Robot options
|
||||
// name - optional, string name of the robot
|
||||
// connection/connections - object connections to connect to
|
||||
// device/devices - object devices to connect to
|
||||
// work - work to be performed when the Robot is started
|
||||
//
|
||||
// Returns a new Robot
|
||||
var EventEmitter = require("events").EventEmitter;
|
||||
|
||||
// used when creating default robot names
|
||||
var ROBOT_ID = 1;
|
||||
|
||||
/**
|
||||
* Creates a new Robot instance based on provided options
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} opts object with Robot options
|
||||
* @param {String} [name] the name the robot should have
|
||||
* @param {Object} [connections] object containing connection info for the Robot
|
||||
* @param {Object} [devices] object containing device information for the Robot
|
||||
* @param {Function} [work] a function the Robot will run when started
|
||||
* @returns {Robot} new Robot instance
|
||||
*/
|
||||
var Robot = module.exports = function Robot(opts) {
|
||||
Utils.classCallCheck(this, Robot);
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
var methods = [
|
||||
"toString",
|
||||
"halt",
|
||||
"startDevices",
|
||||
"startConnections",
|
||||
"start",
|
||||
"initRobot",
|
||||
"initDevices",
|
||||
"initConnections",
|
||||
"log"
|
||||
];
|
||||
validator.validate(opts);
|
||||
|
||||
methods.forEach(function(method) {
|
||||
this[method] = this[method].bind(this);
|
||||
}, this);
|
||||
// auto-bind prototype methods
|
||||
for (var prop in Object.getPrototypeOf(this)) {
|
||||
if (this[prop] && prop !== "constructor") {
|
||||
this[prop] = this[prop].bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
this.initRobot(opts);
|
||||
|
||||
this.checkForBadSyntax(opts);
|
||||
|
||||
this.initConnections(opts);
|
||||
this.initDevices(opts);
|
||||
|
||||
_.each(opts, function(opt, name) {
|
||||
if (this[name] !== undefined) {
|
||||
return;
|
||||
|
@ -96,16 +84,11 @@ var Robot = module.exports = function Robot(opts) {
|
|||
|
||||
Utils.subclass(Robot, EventEmitter);
|
||||
|
||||
// Public: Generates a random name for a Robot.
|
||||
//
|
||||
// Returns a string name
|
||||
Robot.randomName = function() {
|
||||
return "Robot " + (Math.floor(Math.random() * 100000));
|
||||
};
|
||||
|
||||
// Public: Expresses the Robot in a JSON-serializable format
|
||||
//
|
||||
// Returns an Object containing Robot data
|
||||
/**
|
||||
* Condenses information on a Robot to a JSON-serializable format
|
||||
*
|
||||
* @return {Object} serializable information on the Robot
|
||||
*/
|
||||
Robot.prototype.toJSON = function() {
|
||||
return {
|
||||
name: this.name,
|
||||
|
@ -116,6 +99,13 @@ Robot.prototype.toJSON = function() {
|
|||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new Connection to the Robot with the provided name and details.
|
||||
*
|
||||
* @param {String} name string name for the Connection to use
|
||||
* @param {Object} conn options for the Connection initializer
|
||||
* @return {Object} the robot
|
||||
*/
|
||||
Robot.prototype.connection = function(name, conn) {
|
||||
conn.robot = this;
|
||||
conn.name = name;
|
||||
|
@ -128,72 +118,35 @@ Robot.prototype.connection = function(name, conn) {
|
|||
|
||||
str = "Connection names must be unique.";
|
||||
str += "Renaming '" + original + "' to '" + conn.name + "'";
|
||||
this.log("warn", str);
|
||||
this.log(str);
|
||||
}
|
||||
if ("adapter" in conn) {
|
||||
conn.adaptor = conn.adapter;
|
||||
}
|
||||
|
||||
this.connections[conn.name] = initializer("adaptor", conn);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Public: Initializes all vars for robot
|
||||
//
|
||||
// opts - options array passed to constructor
|
||||
//
|
||||
// Returns null
|
||||
/**
|
||||
* Initializes all values for a new Robot.
|
||||
*
|
||||
* @param {Object} opts object passed to Robot constructor
|
||||
* @return {void}
|
||||
*/
|
||||
Robot.prototype.initRobot = function(opts) {
|
||||
this.name = opts.name || Robot.randomName();
|
||||
this.name = opts.name || "Robot " + ROBOT_ID++;
|
||||
this.running = false;
|
||||
|
||||
this.connections = {};
|
||||
this.devices = {};
|
||||
this.adaptors = {};
|
||||
this.drivers = {};
|
||||
this.commands = {};
|
||||
this.running = false;
|
||||
|
||||
this.work = opts.work || opts.play;
|
||||
|
||||
this.commands = {};
|
||||
|
||||
if (!this.work) {
|
||||
this.work = function() { this.log("debug", "No work yet."); };
|
||||
}
|
||||
};
|
||||
|
||||
// Public: Checks options for bad Cylon syntax
|
||||
//
|
||||
// Returns nothing
|
||||
Robot.prototype.checkForBadSyntax = function(opts) {
|
||||
var self = this;
|
||||
|
||||
var RobotDSLError = new Error("Unable to start robot due to a syntax error");
|
||||
RobotDSLError.name = "RobotDSLError";
|
||||
|
||||
function has(prop) { return opts[prop] != null; }
|
||||
|
||||
function checkForSingleObjectSyntax(type) {
|
||||
var plural = type + "s";
|
||||
|
||||
if (has(type) && !has(plural)) {
|
||||
[
|
||||
"The single-object '" + type + "' syntax for robots is not valid.",
|
||||
"Instead, use the multiple-value '" + plural + "' key syntax.",
|
||||
"Details: http://cylonjs.com/documentation/guides/working-with-robots/"
|
||||
].forEach(function(str) { self.log("fatal", str); });
|
||||
|
||||
throw RobotDSLError;
|
||||
}
|
||||
}
|
||||
|
||||
["connection", "device"].forEach(checkForSingleObjectSyntax);
|
||||
};
|
||||
|
||||
// Public: Initializes all connections for the robot
|
||||
//
|
||||
// opts - options array passed to constructor
|
||||
//
|
||||
// Returns initialized connections
|
||||
Robot.prototype.initConnections = function(opts) {
|
||||
this.log("info", "Initializing connections.");
|
||||
|
||||
if (opts.connections == null) {
|
||||
return this.connections;
|
||||
this.work = function() { this.log("No work yet."); };
|
||||
}
|
||||
|
||||
_.each(opts.connections, function(conn, key) {
|
||||
|
@ -210,12 +163,22 @@ Robot.prototype.initConnections = function(opts) {
|
|||
delete conn.devices;
|
||||
}
|
||||
|
||||
this.connection(name, conn);
|
||||
this.connection(name, _.extend({}, conn));
|
||||
}, this);
|
||||
|
||||
return this.connections;
|
||||
_.each(opts.devices, function(device, key) {
|
||||
var name = _.isString(key) ? key : device.name;
|
||||
this.device(name, _.extend({}, device));
|
||||
}, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new Device to the Robot with the provided name and details.
|
||||
*
|
||||
* @param {String} name string name for the Device to use
|
||||
* @param {Object} device options for the Device initializer
|
||||
* @return {Object} the robot
|
||||
*/
|
||||
Robot.prototype.device = function(name, device) {
|
||||
var str;
|
||||
|
||||
|
@ -228,13 +191,13 @@ Robot.prototype.device = function(name, device) {
|
|||
|
||||
str = "Device names must be unique.";
|
||||
str += "Renaming '" + original + "' to '" + device.name + "'";
|
||||
this.log("warn", str);
|
||||
this.log(str);
|
||||
}
|
||||
|
||||
if (_.isString(device.connection)) {
|
||||
if (this.connections[device.connection] == null) {
|
||||
str = "No connection found with the name " + device.connection + ".\n";
|
||||
this.log("fatal", str);
|
||||
this.log(str);
|
||||
process.emit("SIGINT");
|
||||
}
|
||||
|
||||
|
@ -251,36 +214,13 @@ Robot.prototype.device = function(name, device) {
|
|||
return this;
|
||||
};
|
||||
|
||||
// Public: Initializes all devices for the robot
|
||||
//
|
||||
// opts - options array passed to constructor
|
||||
//
|
||||
// Returns initialized devices
|
||||
Robot.prototype.initDevices = function(opts) {
|
||||
this.log("info", "Initializing devices.");
|
||||
|
||||
if (opts.devices == null) {
|
||||
return this.devices;
|
||||
}
|
||||
|
||||
// check that there are connections to use
|
||||
if (!Object.keys(this.connections).length) {
|
||||
throw new Error("No connections specified");
|
||||
}
|
||||
|
||||
_.each(opts.devices, function(device, key) {
|
||||
var name = _.isString(key) ? key : device.name;
|
||||
this.device(name, device);
|
||||
}, this);
|
||||
|
||||
return this.devices;
|
||||
};
|
||||
|
||||
// Public: Starts the Robot working.
|
||||
//
|
||||
// Starts the connections, devices, and work.
|
||||
//
|
||||
// Returns the result of the work
|
||||
/**
|
||||
* Starts the Robot's connections, then devices, then work.
|
||||
*
|
||||
* @param {Function} callback function to be triggered when the Robot has
|
||||
* started working
|
||||
* @return {Object} the Robot
|
||||
*/
|
||||
Robot.prototype.start = function(callback) {
|
||||
if (this.running) {
|
||||
return this;
|
||||
|
@ -294,14 +234,14 @@ Robot.prototype.start = function(callback) {
|
|||
}
|
||||
}.bind(this);
|
||||
|
||||
Async.series([
|
||||
_.series([
|
||||
this.startConnections,
|
||||
this.startDevices,
|
||||
start
|
||||
], function(err, results) {
|
||||
if (err) {
|
||||
this.log("fatal", "An error occured while trying to start the robot:");
|
||||
this.log("fatal", err);
|
||||
this.log("An error occured while trying to start the robot:");
|
||||
this.log(err);
|
||||
|
||||
this.halt(function() {
|
||||
if (_.isFunction(this.error)) {
|
||||
|
@ -322,114 +262,158 @@ Robot.prototype.start = function(callback) {
|
|||
return this;
|
||||
};
|
||||
|
||||
// Public: Starts the Robot"s work and triggers a callback
|
||||
//
|
||||
// callback - callback function to be triggered
|
||||
//
|
||||
// Returns nothing
|
||||
/**
|
||||
* Starts the Robot's work function
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
Robot.prototype.startWork = function() {
|
||||
this.log("info", "Working.");
|
||||
this.log("Working.");
|
||||
|
||||
this.emit("ready", this);
|
||||
this.work.call(this, this);
|
||||
this.running = true;
|
||||
};
|
||||
|
||||
// Public: Starts the Robot"s connections and triggers a callback
|
||||
//
|
||||
// callback - callback function to be triggered
|
||||
//
|
||||
// Returns nothing
|
||||
/**
|
||||
* Starts the Robot's connections
|
||||
*
|
||||
* @param {Function} callback function to be triggered after the connections are
|
||||
* started
|
||||
* @return {void}
|
||||
*/
|
||||
Robot.prototype.startConnections = function(callback) {
|
||||
this.log("info", "Starting connections.");
|
||||
|
||||
var starters = _.map(this.connections, function(conn, name) {
|
||||
this[name] = conn;
|
||||
this.log("Starting connections.");
|
||||
|
||||
var starters = _.map(this.connections, function(conn) {
|
||||
return function(cb) {
|
||||
var str = "Starting connection '" + name + "'";
|
||||
|
||||
if (conn.host) {
|
||||
str += " on host " + conn.host;
|
||||
} else if (conn.port) {
|
||||
str += " on port " + conn.port;
|
||||
}
|
||||
|
||||
this.log("debug", str + ".");
|
||||
return conn.connect.call(conn, cb);
|
||||
return this.startConnection(conn, cb);
|
||||
}.bind(this);
|
||||
}, this);
|
||||
|
||||
return Async.parallel(starters, callback);
|
||||
return _.parallel(starters, callback);
|
||||
};
|
||||
|
||||
// Public: Starts the Robot"s devices and triggers a callback
|
||||
//
|
||||
// callback - callback function to be triggered
|
||||
//
|
||||
// Returns nothing
|
||||
/**
|
||||
* Starts a single connection on Robot
|
||||
*
|
||||
* @param {Object} connection to start
|
||||
* @param {Function} callback function to be triggered after the connection is
|
||||
* started
|
||||
* @return {void}
|
||||
*/
|
||||
Robot.prototype.startConnection = function(connection, callback) {
|
||||
if (connection.connected === true) {
|
||||
return callback.call(connection);
|
||||
}
|
||||
|
||||
var str = "Starting connection '" + connection.name + "'";
|
||||
|
||||
if (connection.host) {
|
||||
str += " on host " + connection.host;
|
||||
} else if (connection.port) {
|
||||
str += " on port " + connection.port;
|
||||
}
|
||||
|
||||
this.log(str + ".");
|
||||
this[connection.name] = connection;
|
||||
connection.connect.call(connection, callback);
|
||||
connection.connected = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts the Robot's devices
|
||||
*
|
||||
* @param {Function} callback function to be triggered after the devices are
|
||||
* started
|
||||
* @return {void}
|
||||
*/
|
||||
Robot.prototype.startDevices = function(callback) {
|
||||
var log = this.log;
|
||||
|
||||
log("info", "Starting devices.");
|
||||
|
||||
var starters = _.map(this.devices, function(device, name) {
|
||||
this[name] = device;
|
||||
log("Starting devices.");
|
||||
|
||||
var starters = _.map(this.devices, function(device) {
|
||||
return function(cb) {
|
||||
var str = "Starting device '" + name + "'";
|
||||
return this.startDevice(device, cb);
|
||||
}.bind(this);
|
||||
}, this);
|
||||
|
||||
if (device.pin) {
|
||||
return _.parallel(starters, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts a single device on Robot
|
||||
*
|
||||
* @param {Object} device to start
|
||||
* @param {Function} callback function to be triggered after the device is
|
||||
* started
|
||||
* @return {void}
|
||||
*/
|
||||
Robot.prototype.startDevice = function(device, callback) {
|
||||
if (device.started === true) {
|
||||
return callback.call(device);
|
||||
}
|
||||
|
||||
var log = this.log;
|
||||
var str = "Starting device '" + device.name + "'";
|
||||
|
||||
if (device.pin || device.pin === 0) {
|
||||
str += " on pin " + device.pin;
|
||||
}
|
||||
|
||||
log("debug", str + ".");
|
||||
return device.start.call(device, cb);
|
||||
};
|
||||
}, this);
|
||||
log(str + ".");
|
||||
this[device.name] = device;
|
||||
device.start.call(device, callback);
|
||||
device.started = true;
|
||||
|
||||
return Async.parallel(starters, callback);
|
||||
return device.started;
|
||||
};
|
||||
|
||||
// Public: Halts the Robot.
|
||||
//
|
||||
// Halts the devices, disconnects the connections.
|
||||
//
|
||||
// callback - callback to be triggered when the Robot is stopped
|
||||
//
|
||||
// Returns nothing
|
||||
/**
|
||||
* Halts the Robot, attempting to gracefully stop devices and connections.
|
||||
*
|
||||
* @param {Function} callback to be triggered when the Robot has stopped
|
||||
* @return {void}
|
||||
*/
|
||||
Robot.prototype.halt = function(callback) {
|
||||
callback = callback || function() {};
|
||||
|
||||
if (!this.isRunning) {
|
||||
if (!this.running) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
var devices = _.pluck(this.devices, "halt"),
|
||||
connections = _.pluck(this.connections, "disconnect");
|
||||
// ensures callback(err) won't prevent others from halting
|
||||
function wrap(fn) {
|
||||
return function(cb) { fn.call(null, cb.bind(null, null)); };
|
||||
}
|
||||
|
||||
var devices = _.pluck(this.devices, "halt").map(wrap),
|
||||
connections = _.pluck(this.connections, "disconnect").map(wrap);
|
||||
|
||||
try {
|
||||
Async.parallel(devices, function() {
|
||||
Async.parallel(connections, callback);
|
||||
_.parallel(devices, function() {
|
||||
_.parallel(connections, callback);
|
||||
});
|
||||
} catch (e) {
|
||||
var msg = "An error occured while attempting to safely halt the robot";
|
||||
this.log("error", msg);
|
||||
this.log("error", e.message);
|
||||
this.log(msg);
|
||||
this.log(e.message);
|
||||
}
|
||||
|
||||
this.running = false;
|
||||
};
|
||||
|
||||
// Public: Returns basic info about the robot as a String
|
||||
//
|
||||
// Returns a String
|
||||
/**
|
||||
* Generates a String representation of a Robot
|
||||
*
|
||||
* @return {String} representation of a Robot
|
||||
*/
|
||||
Robot.prototype.toString = function() {
|
||||
return "[Robot name='" + this.name + "']";
|
||||
};
|
||||
|
||||
Robot.prototype.log = function(level) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
args.unshift("[" + this.name + "] -");
|
||||
Logger[level].apply(null, args);
|
||||
Robot.prototype.log = function(str) {
|
||||
Logger.log("[" + this.name + "] - " + str);
|
||||
};
|
||||
|
|
|
@ -1,19 +1,9 @@
|
|||
/*
|
||||
* Cylon.js - Loopback adaptor
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var Adaptor = require("../adaptor"),
|
||||
Utils = require("../utils");
|
||||
|
||||
var Loopback;
|
||||
|
||||
module.exports = Loopback = function Loopback() {
|
||||
var Loopback = module.exports = function Loopback() {
|
||||
Loopback.__super__.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
/*
|
||||
* Cylon.js - Ping driver
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var Driver = require("../driver"),
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
/*
|
||||
* Cylon.js - Test Adaptor
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var Adaptor = require("../adaptor"),
|
||||
Utils = require("../utils");
|
||||
|
||||
var TestAdaptor;
|
||||
|
||||
module.exports = TestAdaptor = function TestAdaptor() {
|
||||
var TestAdaptor = module.exports = function TestAdaptor() {
|
||||
TestAdaptor.__super__.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Utils.subclass(TestAdaptor, Adaptor);
|
||||
|
||||
|
||||
TestAdaptor.prototype.connect = function(callback) {
|
||||
callback();
|
||||
};
|
||||
|
||||
TestAdaptor.prototype.disconnect = function(callback) {
|
||||
callback();
|
||||
};
|
||||
|
||||
TestAdaptor.adaptors = ["test"];
|
||||
TestAdaptor.adaptor = function(opts) { return new TestAdaptor(opts); };
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
/*
|
||||
* Cylon.js - Test Driver
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var Driver = require("../driver"),
|
||||
Utils = require("../utils");
|
||||
|
||||
var TestDriver;
|
||||
|
||||
module.exports = TestDriver = function TestDriver() {
|
||||
var TestDriver = module.exports = function TestDriver() {
|
||||
TestDriver.__super__.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Utils.subclass(TestDriver, Driver);
|
||||
|
||||
TestDriver.prototype.start = function(callback) {
|
||||
callback();
|
||||
};
|
||||
|
||||
TestDriver.prototype.halt = function(callback) {
|
||||
callback();
|
||||
};
|
||||
|
||||
TestDriver.drivers = ["test"];
|
||||
TestDriver.driver = function(opts) { return new TestDriver(opts); };
|
||||
|
|
239
lib/utils.js
239
lib/utils.js
|
@ -1,11 +1,3 @@
|
|||
/*
|
||||
* Cylon - Utils
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var _ = require("./utils/helpers");
|
||||
|
@ -13,61 +5,63 @@ var _ = require("./utils/helpers");
|
|||
var monkeyPatches = require("./utils/monkey-patches");
|
||||
|
||||
var Utils = module.exports = {
|
||||
// Public: Alias to setInterval, combined with Number monkeypatches below to
|
||||
// create an artoo-like syntax.
|
||||
//
|
||||
// interval - interval to run action on
|
||||
// action - action to perform at interval
|
||||
//
|
||||
// Examples
|
||||
//
|
||||
// every((5).seconds(), function() {
|
||||
// console.log("Hello world (and again in 5 seconds)!");
|
||||
// });
|
||||
//
|
||||
// Returns an interval
|
||||
/**
|
||||
* A wrapper around setInterval to provide a more english-like syntax
|
||||
* (e.g. "every 5 seconds, do this thing")
|
||||
*
|
||||
* @param {Number} interval delay between action invocations
|
||||
* @param {Function} action function to trigger every time interval elapses
|
||||
* @example every((5).seconds(), function() {});
|
||||
* @return {intervalID} setInterval ID to pass to clearInterval()
|
||||
*/
|
||||
every: function every(interval, action) {
|
||||
return setInterval(action, interval);
|
||||
},
|
||||
|
||||
// Public: Alias to setTimeout, combined with Number monkeypatches below to
|
||||
// create an artoo-like syntax.
|
||||
//
|
||||
// interval - interval to run action on
|
||||
// action - action to perform at interval
|
||||
//
|
||||
// Examples
|
||||
//
|
||||
// after((10).seconds(), function() {
|
||||
// console.log("Hello world from ten seconds ago!");
|
||||
// });
|
||||
//
|
||||
// Returns an interval
|
||||
/**
|
||||
* A wrapper around setInterval to provide a more english-like syntax
|
||||
* (e.g. "after 5 seconds, do this thing")
|
||||
*
|
||||
* @param {Number} delay how long to wait
|
||||
* @param {Function} action action to perform after delay
|
||||
* @example after((5).seconds(), function() {});
|
||||
* @return {timeoutId} setTimeout ID to pass to clearTimeout()
|
||||
*/
|
||||
after: function after(delay, action) {
|
||||
return setTimeout(action, delay);
|
||||
},
|
||||
|
||||
// Public: Alias to the `every` function, but passing 0
|
||||
// Examples
|
||||
//
|
||||
// constantly(function() {
|
||||
// console.log("hello world (and again and again)!");
|
||||
// });
|
||||
//
|
||||
// Returns an interval
|
||||
/**
|
||||
* A wrapper around setInterval, with a delay of 0ms
|
||||
*
|
||||
* @param {Function} action function to invoke constantly
|
||||
* @example constantly(function() {});
|
||||
* @return {intervalID} setInterval ID to pass to clearInterval()
|
||||
*/
|
||||
constantly: function constantly(action) {
|
||||
return every(0, action);
|
||||
},
|
||||
|
||||
// Public: Sleep - do nothing for some duration of time.
|
||||
//
|
||||
// ms - number of ms to sleep for
|
||||
//
|
||||
// Examples
|
||||
//
|
||||
// sleep((1).second());
|
||||
//
|
||||
// Returns a function
|
||||
/**
|
||||
* A wrapper around clearInterval
|
||||
*
|
||||
* @param {intervalID} intervalID ID of every/after/constantly
|
||||
* @example finish(blinking);
|
||||
* @return {void}
|
||||
*/
|
||||
finish: function finish(intervalID) {
|
||||
clearInterval(intervalID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sleeps the program for a period of time.
|
||||
*
|
||||
* Use of this is not advised, as your program can't do anything else while
|
||||
* it's running.
|
||||
*
|
||||
* @param {Number} ms number of milliseconds to sleep
|
||||
* @return {void}
|
||||
*/
|
||||
sleep: function sleep(ms) {
|
||||
var start = Date.now(),
|
||||
i = 0;
|
||||
|
@ -77,19 +71,17 @@ var Utils = module.exports = {
|
|||
}
|
||||
},
|
||||
|
||||
// Public: Function to use for class inheritance.
|
||||
// Based on CoffeeScript's implementation.
|
||||
//
|
||||
// Example
|
||||
//
|
||||
// var Sphero = function Sphero() {};
|
||||
//
|
||||
// subclass(Sphero, ParentClass);
|
||||
//
|
||||
// // Sphero is now a subclass of Parent, and can access parent methods
|
||||
// // through Sphero.__super__
|
||||
//
|
||||
// Returns subclass
|
||||
/**
|
||||
* Utility for providing class inheritance.
|
||||
*
|
||||
* Based on CoffeeScript's implementation of inheritance.
|
||||
*
|
||||
* Parent class methods/properites are available on Child.__super__.
|
||||
*
|
||||
* @param {Function} child the descendent class
|
||||
* @param {Function} parent the parent class
|
||||
* @return {Function} the child class
|
||||
*/
|
||||
subclass: function subclass(child, parent) {
|
||||
var Ctor = function() {
|
||||
this.constructor = child;
|
||||
|
@ -115,16 +107,15 @@ var Utils = module.exports = {
|
|||
});
|
||||
},
|
||||
|
||||
// Public: Proxies a list of methods from one object to another. It will not
|
||||
// overwrite existing methods unless told to.
|
||||
//
|
||||
// methods - array of functions to proxy
|
||||
// target - object to proxy the functions to
|
||||
// base - (optional) object that proxied functions will be declared on.
|
||||
// Defaults to 'this'.
|
||||
// force - (optional) boolean - whether or not to force method assignment
|
||||
//
|
||||
// Returns base
|
||||
/**
|
||||
* Proxies calls from all methods in the source to a target object
|
||||
*
|
||||
* @param {String[]} methods methods to proxy
|
||||
* @param {Object} target object to proxy methods to
|
||||
* @param {Object} [base=this] object to proxy methods from
|
||||
* @param {Boolean} [force=false] whether to overwrite existing methods
|
||||
* @return {Object} the base
|
||||
*/
|
||||
proxyFunctionsToObject: function(methods, target, base, force) {
|
||||
if (base == null) {
|
||||
base = this;
|
||||
|
@ -145,39 +136,37 @@ var Utils = module.exports = {
|
|||
return base;
|
||||
},
|
||||
|
||||
// Public: Analogue to Ruby"s Hash#fetch method for looking up object
|
||||
// properties.
|
||||
//
|
||||
// obj - object to do property lookup on
|
||||
// property - string property name to attempt to look up
|
||||
// fallback - either:
|
||||
// - a fallback value to return if `property` can"t be found
|
||||
// - a function to be executed if `property` can"t be found. The function
|
||||
// will be passed `property` as an argument.
|
||||
//
|
||||
// Examples
|
||||
//
|
||||
// var object = { property: "hello world" };
|
||||
// fetch(object, "property");
|
||||
// //=> "hello world"
|
||||
//
|
||||
// fetch(object, "notaproperty", "default value");
|
||||
// //=> "default value"
|
||||
//
|
||||
// var notFound = function(prop) { return prop + " not found!" };
|
||||
// fetch(object, "notaproperty", notFound)
|
||||
// // "notaproperty not found!"
|
||||
//
|
||||
// var badFallback = function(prop) { prop + " not found!" };
|
||||
// fetch(object, "notaproperty", badFallback)
|
||||
// // Error: no return value from provided callback function
|
||||
//
|
||||
// fetch(object, "notaproperty");
|
||||
// // Error: key not found: "notaproperty"
|
||||
//
|
||||
// Returns the value of obj[property], a fallback value, or the results of
|
||||
// running "fallback". If the property isn"t found, and "fallback" hasn"t been
|
||||
// provided, will throw an error.
|
||||
classCallCheck: function(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
throw new TypeError("Cannot call a class as a function");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Approximation of Ruby's Hash#fetch method for object property lookup
|
||||
*
|
||||
* @param {Object} obj object to do lookup on
|
||||
* @param {String} property property name to attempt to access
|
||||
* @param {*} fallback a fallback value if property can't be found. if
|
||||
* a function, will be invoked with the string property name.
|
||||
* @throws Error if fallback needed but not provided, or fallback fn doesn't
|
||||
* return anything
|
||||
* @example
|
||||
* fetch({ property: "hello world" }, "property"); //=> "hello world"
|
||||
* @example
|
||||
* fetch({}, "notaproperty", "default value"); //=> "default value"
|
||||
* @example
|
||||
* var notFound = function(prop) { return prop + " not found!" };
|
||||
* fetch({}, "notaproperty", notFound); //=> "notaproperty not found!"
|
||||
* @example
|
||||
* var badFallback = function(prop) { prop + " not found!" };
|
||||
* fetch({}, "notaproperty", badFallback);
|
||||
* // Error: no return value from provided callback function
|
||||
* @example
|
||||
* fetch(object, "notaproperty");
|
||||
* // Error: key not found: "notaproperty"
|
||||
* @return {*} fetched property, fallback, or fallback function return value
|
||||
*/
|
||||
fetch: function(obj, property, fallback) {
|
||||
if (obj.hasOwnProperty(property)) {
|
||||
return obj[property];
|
||||
|
@ -200,13 +189,15 @@ var Utils = module.exports = {
|
|||
return fallback;
|
||||
},
|
||||
|
||||
// Public: Given a name, and an array of existing names, returns a unique
|
||||
// name.
|
||||
//
|
||||
// name - name that"s colliding with existing names
|
||||
// arr - array of existing names
|
||||
//
|
||||
// Returns the new name as a string
|
||||
/**
|
||||
* Given a name, and an array of existing names, returns a unique new name
|
||||
*
|
||||
* @param {String} name the name that's colliding with existing names
|
||||
* @param {String[]} arr array of existing names
|
||||
* @example
|
||||
* makeUnique("hello", ["hello", "hello-1", "hello-2"]); //=> "hello3"
|
||||
* @return {String} the new name
|
||||
*/
|
||||
makeUnique: function(name, arr) {
|
||||
var newName;
|
||||
|
||||
|
@ -222,20 +213,12 @@ var Utils = module.exports = {
|
|||
}
|
||||
},
|
||||
|
||||
// Public: Adds necessary utils to global namespace, along with base class
|
||||
// extensions.
|
||||
//
|
||||
// Examples
|
||||
//
|
||||
// Number.prototype.seconds // undefined
|
||||
// after // undefined
|
||||
//
|
||||
// Utils.bootstrap();
|
||||
//
|
||||
// Number.prototype.seconds // [function]
|
||||
// (after === Utils.after) // true
|
||||
//
|
||||
// Returns Cylon.Utils
|
||||
/**
|
||||
* Adds every/after/constantly to the global namespace, and installs
|
||||
* monkey-patches.
|
||||
*
|
||||
* @return {Object} utils object
|
||||
*/
|
||||
bootstrap: function bootstrap() {
|
||||
global.every = this.every;
|
||||
global.after = this.after;
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
/**
|
||||
* Cylon.js - Helper Utilities
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/* eslint no-use-before-define: 0 */
|
||||
|
||||
var __slice = Array.prototype.slice;
|
||||
|
||||
var H = module.exports = {};
|
||||
|
@ -210,3 +204,72 @@ function includes(arr, value) {
|
|||
extend(H, {
|
||||
includes: includes
|
||||
});
|
||||
|
||||
function parallel(functions, done) {
|
||||
var total = functions.length,
|
||||
completed = 0,
|
||||
results = [],
|
||||
error;
|
||||
|
||||
if (typeof done !== "function") { done = function() {}; }
|
||||
|
||||
function callback(err, result) {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (err || error) {
|
||||
error = err;
|
||||
done(err);
|
||||
return;
|
||||
}
|
||||
|
||||
completed++;
|
||||
results.push(result);
|
||||
|
||||
if (completed === total) {
|
||||
done(null, results);
|
||||
}
|
||||
}
|
||||
|
||||
if (!functions.length) { done(); }
|
||||
|
||||
functions.forEach(function(fn) { fn(callback); });
|
||||
}
|
||||
|
||||
extend(H, {
|
||||
parallel: parallel
|
||||
});
|
||||
|
||||
function series(functions, done) {
|
||||
var results = [],
|
||||
error;
|
||||
|
||||
if (typeof done !== "function") { done = function() {}; }
|
||||
|
||||
function callback(err, result) {
|
||||
if (err || error) {
|
||||
error = err;
|
||||
return done(err);
|
||||
}
|
||||
|
||||
results.push(result);
|
||||
|
||||
if (!functions.length) {
|
||||
return done(null, results);
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
function next() {
|
||||
functions.shift()(callback);
|
||||
}
|
||||
|
||||
if (!functions.length) { done(null, results); }
|
||||
next();
|
||||
}
|
||||
|
||||
extend(H, {
|
||||
series: series
|
||||
});
|
||||
|
|
|
@ -1,40 +1,53 @@
|
|||
/**
|
||||
* Cylon.js - Monkey Patches
|
||||
* cylonjs.com
|
||||
*
|
||||
* Copyright (c) 2013-2015 The Hybrid Group
|
||||
* Licensed under the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
/* eslint no-extend-native: 0 */
|
||||
/* eslint no-extend-native: 0 key-spacing: 0 */
|
||||
|
||||
"use strict";
|
||||
|
||||
var max = Math.max,
|
||||
min = Math.min;
|
||||
|
||||
var originals = {
|
||||
seconds: Number.prototype.seconds,
|
||||
second: Number.prototype.second,
|
||||
fromScale: Number.prototype.fromScale,
|
||||
toScale: Number.prototype.toScale
|
||||
};
|
||||
|
||||
module.exports.uninstall = function() {
|
||||
for (var opt in originals) {
|
||||
if (originals[opt] == null) {
|
||||
Number.prototype[opt] = originals[opt];
|
||||
} else {
|
||||
delete Number.prototype[opt];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.install = function() {
|
||||
// Public: Monkey-patches Number to have Rails-like //seconds() function.
|
||||
// Warning, due to the way the Javascript parser works, applying functions on
|
||||
// numbers is kind of weird. See examples for details.
|
||||
//
|
||||
// Examples
|
||||
//
|
||||
// 2.seconds()
|
||||
// //=> SyntaxError: Unexpected token ILLEGAL
|
||||
//
|
||||
// 10..seconds()
|
||||
// //=> 10000
|
||||
//
|
||||
// (5).seconds()
|
||||
// //=> 5000
|
||||
// // This is the preferred way to represent numbers when calling these
|
||||
// // methods on them
|
||||
//
|
||||
// Returns an integer representing time in milliseconds
|
||||
/**
|
||||
* Multiplies a number by 60000 to convert minutes
|
||||
* to milliseconds
|
||||
*
|
||||
* @example
|
||||
* (2).minutes(); //=> 120000
|
||||
* @return {Number} time in milliseconds
|
||||
*/
|
||||
Number.prototype.minutes = function() {
|
||||
return this * 60000;
|
||||
};
|
||||
|
||||
/**
|
||||
* Multiplies a number by 1000 to get time in milliseconds
|
||||
* Alias for Number.prototype.minutes
|
||||
*
|
||||
* @see Number.prototype.minute
|
||||
* @example
|
||||
* (1).minute(); //=>60000
|
||||
* @return {Number} time in milliseconds
|
||||
*/
|
||||
Number.prototype.minute = Number.prototype.minutes;
|
||||
|
||||
/**
|
||||
* Multiplies a number by 1000 to convert seconds
|
||||
* to milliseconds
|
||||
*
|
||||
* @example
|
||||
* (2).seconds(); //=> 2000
|
||||
|
@ -54,6 +67,40 @@ module.exports.install = function() {
|
|||
*/
|
||||
Number.prototype.second = Number.prototype.seconds;
|
||||
|
||||
/**
|
||||
* Passthru to get time in milliseconds
|
||||
*
|
||||
* @example
|
||||
* (200).milliseconds(); //=> 200
|
||||
* @return {Number} time in milliseconds
|
||||
*/
|
||||
Number.prototype.milliseconds = function() {
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Alias for Number.prototype.milliseconds
|
||||
*
|
||||
* @see Number.prototype.milliseconds
|
||||
* @example
|
||||
* (100).ms(); //=> 100
|
||||
* @return {Number} time in milliseconds
|
||||
*/
|
||||
Number.prototype.ms = Number.prototype.milliseconds;
|
||||
|
||||
/**
|
||||
* Converts microseconds to milliseconds.
|
||||
* Note that timing of events in terms of microseconds
|
||||
* is not very accurate in JS.
|
||||
*
|
||||
* @example
|
||||
* (2000).microseconds(); //=> 2
|
||||
* @return {Number} time in milliseconds
|
||||
*/
|
||||
Number.prototype.microseconds = function() {
|
||||
return this / 1000;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a number from a current scale to a 0 - 1 scale.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
"use strict";
|
||||
|
||||
// validates an Object containing Robot parameters
|
||||
|
||||
var Logger = require("./logger"),
|
||||
_ = require("./utils/helpers");
|
||||
|
||||
function hasProp(object, prop) {
|
||||
return object.hasOwnProperty(prop);
|
||||
}
|
||||
|
||||
function die() {
|
||||
var RobotDSLError = new Error("Unable to start robot due to a syntax error");
|
||||
RobotDSLError.name = "RobotDSLError";
|
||||
throw RobotDSLError;
|
||||
}
|
||||
|
||||
function warn(messages) {
|
||||
messages = [].concat(messages);
|
||||
messages.map(function(msg) { Logger.log(msg); });
|
||||
}
|
||||
|
||||
function fatal(messages) {
|
||||
messages = [].concat(messages);
|
||||
messages.map(function(msg) { Logger.log(msg); });
|
||||
die();
|
||||
}
|
||||
|
||||
var checks = {};
|
||||
|
||||
checks.singleObjectSyntax = function(opts, key) {
|
||||
var single = hasProp(opts, key),
|
||||
plural = hasProp(opts, key + "s");
|
||||
|
||||
if (single && !plural) {
|
||||
fatal([
|
||||
"The single-object '" + key + "' syntax for robots is not valid.",
|
||||
"Instead, use the multiple-value '" + key + "s' key syntax.",
|
||||
"Details: http://cylonjs.com/documentation/guides/working-with-robots/"
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
checks.singleObjectSyntax = function(opts) {
|
||||
["connection", "device"].map(function(key) {
|
||||
var single = hasProp(opts, key),
|
||||
plural = hasProp(opts, key + "s");
|
||||
|
||||
if (single && !plural) {
|
||||
fatal([
|
||||
"The single-object '" + key + "' syntax for robots is not valid.",
|
||||
"Instead, use the multiple-value '" + key + "s' key syntax.",
|
||||
"Details: http://cylonjs.com/documentation/guides/working-with-robots/"
|
||||
]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
checks.deviceWithoutDriver = function(opts) {
|
||||
if (opts.devices) {
|
||||
_.each(opts.devices, function(device, name) {
|
||||
if (!device.driver || device.driver === "") {
|
||||
fatal("No driver supplied for device " + name);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
checks.devicesWithoutConnection = function(opts) {
|
||||
var connections = opts.connections,
|
||||
devices = opts.devices;
|
||||
|
||||
if (devices && connections && Object.keys(connections).length > 1) {
|
||||
var first = Object.keys(connections)[0];
|
||||
|
||||
_.each(devices, function(device, name) {
|
||||
if (!device.connection || device.connection === "") {
|
||||
warn([
|
||||
"No explicit connection provided for device " + name,
|
||||
"Will default to using connection " + first
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
checks.noConnections = function(opts) {
|
||||
var connections = Object.keys(opts.connections || {}).length,
|
||||
devices = Object.keys(opts.devices || {}).length;
|
||||
|
||||
if (devices && !connections) {
|
||||
fatal(["No connections provided for devices"]);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.validate = function validate(opts) {
|
||||
opts = opts || {};
|
||||
|
||||
_.each(checks, function(check) {
|
||||
check(opts);
|
||||
});
|
||||
};
|
36
package.json
36
package.json
|
@ -1,19 +1,14 @@
|
|||
{
|
||||
"name": "cylon",
|
||||
"version": "1.0.0",
|
||||
"main": "lib/cylon.js",
|
||||
"description": "A JavaScript robotics framework for Node.js",
|
||||
"version": "1.3.0",
|
||||
"description": "JavaScript framework for robotics, drones, and the Internet of Things (IoT) using Node.js",
|
||||
"homepage": "http://cylonjs.com",
|
||||
"bugs": "https://github.com/hybridgroup/cylon/issues",
|
||||
|
||||
"author": "The Hybrid Group <cylonjs@hybridgroup.com>",
|
||||
|
||||
"contributors": [
|
||||
"Ron Evans <ron@hybridgroup.com>",
|
||||
"Andrew Stewart <andrew@hybridgroup.com>",
|
||||
"Edgar Silva <edgar@hybridgroup.com>",
|
||||
"Mario 'Kuroir' Ricalde <mario@hybridgroup.com>",
|
||||
"Adrian Zankich <adrian@hybridgroup.com>"
|
||||
"Contributors List (https://github.com/hybridgroup/cylon/blob/master/CONTRIBUTORS.markdown)"
|
||||
],
|
||||
|
||||
"repository": {
|
||||
|
@ -21,13 +16,26 @@
|
|||
"url": "https://github.com/hybridgroup/cylon"
|
||||
},
|
||||
|
||||
"license": "Apache 2.0",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
"keywords": [
|
||||
"cylon",
|
||||
"cylonjs",
|
||||
"cylons",
|
||||
"robot",
|
||||
"robots",
|
||||
"robotics",
|
||||
"iot",
|
||||
"hardware",
|
||||
"drones",
|
||||
"internet of things"
|
||||
],
|
||||
|
||||
"hardware": {
|
||||
"*": false,
|
||||
"./": false,
|
||||
"async": true,
|
||||
"./lib": true
|
||||
"./lib": true,
|
||||
"index.js": true
|
||||
},
|
||||
|
||||
"engines" : {
|
||||
|
@ -39,10 +47,6 @@
|
|||
"chai": "2.2.0",
|
||||
"mocha": "2.2.4",
|
||||
"sinon": "1.14.1",
|
||||
"eslint": "0.19.0"
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"async": "0.9.0"
|
||||
"eslint": "0.22.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ env:
|
|||
|
||||
globals:
|
||||
expect: true
|
||||
source: true
|
||||
lib: true
|
||||
stub: true
|
||||
spy: true
|
||||
chai: true
|
||||
|
|
|
@ -22,17 +22,15 @@ global.spy = sinon.spy;
|
|||
global.stub = sinon.stub;
|
||||
|
||||
// convenience function to require modules in lib directory
|
||||
global.source = function(module) {
|
||||
global.lib = function(module) {
|
||||
return require(path.normalize("./../lib/" + module));
|
||||
};
|
||||
|
||||
var Cylon = source("cylon");
|
||||
var Cylon = require("./../");
|
||||
|
||||
Cylon.config({
|
||||
mode: "manual",
|
||||
logging: {
|
||||
logger: false
|
||||
}
|
||||
silent: true
|
||||
});
|
||||
|
||||
Cylon.Logger.setup();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
var Adaptor = source("adaptor"),
|
||||
Utils = source("utils");
|
||||
var Adaptor = lib("adaptor"),
|
||||
Utils = lib("utils");
|
||||
|
||||
describe("Adaptor", function() {
|
||||
var adaptor;
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
"use strict";
|
||||
|
||||
var API = lib("api"),
|
||||
MCP = lib("mcp");
|
||||
|
||||
describe("API", function() {
|
||||
describe("#create", function() {
|
||||
afterEach(function() {
|
||||
API.instances = [];
|
||||
});
|
||||
|
||||
context("with a provided API server and opts", function() {
|
||||
var Server, opts, instance;
|
||||
|
||||
beforeEach(function() {
|
||||
instance = { start: spy() };
|
||||
opts = { https: false };
|
||||
Server = stub().returns(instance);
|
||||
|
||||
API.create(Server, opts);
|
||||
});
|
||||
|
||||
it("creates an API instance", function() {
|
||||
expect(Server).to.be.calledWithNew;
|
||||
expect(Server).to.be.calledWith(opts);
|
||||
});
|
||||
|
||||
it("passes MCP through to the instance as opts.mcp", function() {
|
||||
expect(opts.mcp).to.be.eql(MCP);
|
||||
});
|
||||
|
||||
it("stores the API instance in @instances", function() {
|
||||
expect(API.instances).to.be.eql([instance]);
|
||||
});
|
||||
|
||||
it("tells the API instance to start", function() {
|
||||
expect(instance.start).to.be.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
var Basestar = source("basestar"),
|
||||
Utils = source("utils");
|
||||
var Basestar = lib("basestar"),
|
||||
Utils = lib("utils");
|
||||
|
||||
var EventEmitter = require("events").EventEmitter;
|
||||
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
"use strict";
|
||||
|
||||
var config = lib("config");
|
||||
|
||||
describe("config", function() {
|
||||
it("contains configuration options", function() {
|
||||
expect(config.testMode).to.be.eql(false);
|
||||
});
|
||||
|
||||
describe("#update", function() {
|
||||
var callback;
|
||||
|
||||
beforeEach(function() {
|
||||
callback = spy();
|
||||
config.subscribe(callback);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
config.unsubscribe(callback);
|
||||
delete config.newValue;
|
||||
});
|
||||
|
||||
it("updates the configuration", function() {
|
||||
expect(config.newValue).to.be.eql(undefined);
|
||||
config.update({ newValue: "value" });
|
||||
expect(config.newValue).to.be.eql("value");
|
||||
});
|
||||
|
||||
it("notifies subscribers of changes", function() {
|
||||
var update = { newValue: "value" };
|
||||
expect(callback).to.not.be.called;
|
||||
config.update(update);
|
||||
expect(callback).to.be.calledWith(update);
|
||||
});
|
||||
|
||||
it("rejects changes that conflict with config functions", function() {
|
||||
config.update({ update: null });
|
||||
expect(config.update).to.be.a("function");
|
||||
});
|
||||
|
||||
it("does nothing with empty changesets", function() {
|
||||
config.update({});
|
||||
expect(callback).to.not.be.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe("#subscribe", function() {
|
||||
var callback = spy();
|
||||
|
||||
afterEach(function() {
|
||||
delete config.test;
|
||||
config.unsubscribe(callback);
|
||||
});
|
||||
|
||||
it("subscribes a callback to change updates", function() {
|
||||
config.subscribe(callback);
|
||||
config.update({ test: true });
|
||||
expect(callback).to.be.calledWith({ test: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe("#unsubscribe", function() {
|
||||
var callback;
|
||||
|
||||
beforeEach(function() {
|
||||
callback = spy();
|
||||
config.subscribe(callback);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
delete config.test;
|
||||
});
|
||||
|
||||
it("unsubscribes a callback from change updates", function() {
|
||||
config.update({ test: true });
|
||||
expect(callback).to.be.called;
|
||||
|
||||
config.unsubscribe(callback);
|
||||
|
||||
config.update({ test: false });
|
||||
expect(callback).to.be.calledOnce;
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,201 +1,78 @@
|
|||
"use strict";
|
||||
|
||||
var Cylon = source("cylon"),
|
||||
Robot = source("robot");
|
||||
var Cylon = lib("../index");
|
||||
|
||||
var Logger = source("logger"),
|
||||
Adaptor = source("adaptor"),
|
||||
Driver = source("driver"),
|
||||
Config = source("config");
|
||||
var MCP = lib("mcp"),
|
||||
API = lib("api"),
|
||||
Robot = lib("robot"),
|
||||
Driver = lib("driver"),
|
||||
Adaptor = lib("adaptor"),
|
||||
Utils = lib("utils"),
|
||||
Config = lib("config"),
|
||||
Logger = lib("logger");
|
||||
|
||||
var IO = {
|
||||
DigitalPin: lib("io/digital-pin"),
|
||||
Utils: lib("io/utils")
|
||||
};
|
||||
|
||||
describe("Cylon", function() {
|
||||
describe("exports", function() {
|
||||
it("sets Logger to the Logger module", function() {
|
||||
expect(Cylon.Logger).to.be.eql(Logger);
|
||||
it("exports the MCP as Cylon.MCP", function() {
|
||||
expect(Cylon.MCP).to.be.eql(MCP);
|
||||
});
|
||||
|
||||
it("sets Adaptor to the Adaptor module", function() {
|
||||
expect(Cylon.Adaptor).to.be.eql(Adaptor);
|
||||
it("exports the Robot as Cylon.Robot", function() {
|
||||
expect(Cylon.Robot).to.be.eql(Robot);
|
||||
});
|
||||
|
||||
it("sets Driver to the Driver module", function() {
|
||||
it("exports the Driver as Cylon.Driver", function() {
|
||||
expect(Cylon.Driver).to.be.eql(Driver);
|
||||
});
|
||||
|
||||
it("sets @apiInstances to an empty array by default", function() {
|
||||
expect(Cylon.apiInstances).to.be.eql([]);
|
||||
it("exports the Adaptor as Cylon.Adaptor", function() {
|
||||
expect(Cylon.Adaptor).to.be.eql(Adaptor);
|
||||
});
|
||||
|
||||
it("sets @robots to an empty object by default", function() {
|
||||
expect(Cylon.robots).to.be.eql({});
|
||||
it("exports the Utils as Cylon.Utils", function() {
|
||||
expect(Cylon.Utils).to.be.eql(Utils);
|
||||
});
|
||||
|
||||
it("sets @robots to an empty object by default", function() {
|
||||
expect(Cylon.commands).to.be.eql({});
|
||||
|
||||
it("exports the Logger as Cylon.Logger", function() {
|
||||
expect(Cylon.Logger).to.be.eql(Logger);
|
||||
});
|
||||
|
||||
it("exports the IO DigitalPin and Utils as Cylon.IO", function() {
|
||||
expect(Cylon.IO).to.be.eql(IO);
|
||||
});
|
||||
|
||||
describe("#robot", function() {
|
||||
afterEach(function() {
|
||||
Cylon.robots = {};
|
||||
});
|
||||
|
||||
it("uses passed options to create a new Robot", function() {
|
||||
var opts = { name: "Ultron" };
|
||||
var robot = Cylon.robot(opts);
|
||||
|
||||
expect(robot.toString()).to.be.eql("[Robot name='Ultron']");
|
||||
expect(Cylon.robots.Ultron).to.be.eql(robot);
|
||||
});
|
||||
|
||||
it("avoids duplicating names", function() {
|
||||
Cylon.robot({ name: "Ultron" });
|
||||
Cylon.robot({ name: "Ultron" });
|
||||
|
||||
var bots = Object.keys(Cylon.robots);
|
||||
expect(bots).to.be.eql(["Ultron", "Ultron-1"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#api", function() {
|
||||
afterEach(function() {
|
||||
Cylon.apiInstances = [];
|
||||
});
|
||||
|
||||
context("with a provided API server and opts", function() {
|
||||
var API, opts, instance;
|
||||
|
||||
beforeEach(function() {
|
||||
instance = { start: spy() };
|
||||
opts = { https: false };
|
||||
API = stub().returns(instance);
|
||||
|
||||
Cylon.api(API, opts);
|
||||
});
|
||||
|
||||
it("creates an API instance", function() {
|
||||
expect(API).to.be.calledWithNew;
|
||||
expect(API).to.be.calledWith(opts);
|
||||
});
|
||||
|
||||
it("passes Cylon through to the instance as opts.mcp", function() {
|
||||
expect(opts.mcp).to.be.eql(Cylon);
|
||||
});
|
||||
|
||||
it("stores the API instance in @apiInstances", function() {
|
||||
expect(Cylon.apiInstances).to.be.eql([instance]);
|
||||
});
|
||||
|
||||
it("tells the API instance to start", function() {
|
||||
expect(instance.start).to.be.called;
|
||||
});
|
||||
it("proxies to MCP.create", function() {
|
||||
expect(Cylon.robot).to.be.eql(MCP.create);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#start", function() {
|
||||
it("calls #start() on all robots", function() {
|
||||
var bot1 = { start: spy() },
|
||||
bot2 = { start: spy() };
|
||||
|
||||
Cylon.robots = {
|
||||
bot1: bot1,
|
||||
bot2: bot2
|
||||
};
|
||||
|
||||
Cylon.start();
|
||||
|
||||
expect(bot1.start).to.be.called;
|
||||
expect(bot2.start).to.be.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe("#config", function() {
|
||||
beforeEach(function() {
|
||||
for (var c in Config) {
|
||||
delete Config[c];
|
||||
}
|
||||
|
||||
stub(Logger, "setup");
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
Logger.setup.restore();
|
||||
});
|
||||
|
||||
it("sets config variables", function() {
|
||||
Cylon.config({ a: 1, b: 2 });
|
||||
expect(Config.a).to.be.eql(1);
|
||||
expect(Config.b).to.be.eql(2);
|
||||
});
|
||||
|
||||
it("updates existing config", function() {
|
||||
Cylon.config({ a: 1, b: 2 });
|
||||
Cylon.config({ a: 3 });
|
||||
expect(Config.a).to.be.eql(3);
|
||||
expect(Config.b).to.be.eql(2);
|
||||
});
|
||||
|
||||
it("returns updated config", function() {
|
||||
var config = Cylon.config({ a: 1, b: 2 });
|
||||
expect(Config).to.be.eql(config);
|
||||
});
|
||||
|
||||
it("doesn't ignores non-object arguments", function() {
|
||||
var config = Cylon.config({ a: 1, b: 2 });
|
||||
Cylon.config(["a", 1, "b", 2]);
|
||||
Cylon.config("hello world");
|
||||
expect(Config).to.be.eql(config);
|
||||
});
|
||||
|
||||
it("updates the Logger setup if that changed", function() {
|
||||
Cylon.config({ a: 1 });
|
||||
expect(Logger.setup).to.not.be.called;
|
||||
|
||||
Cylon.config({ a: 1, logging: { logger: false } });
|
||||
expect(Logger.setup).to.be.called;
|
||||
it("proxies to MCP.start", function() {
|
||||
expect(Cylon.start).to.be.eql(MCP.start);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#halt", function() {
|
||||
it("calls #halt() on all robots", function() {
|
||||
var bot1 = { halt: spy() },
|
||||
bot2 = { halt: spy() };
|
||||
|
||||
Cylon.robots = {
|
||||
bot1: bot1,
|
||||
bot2: bot2
|
||||
};
|
||||
|
||||
Cylon.halt();
|
||||
|
||||
expect(bot1.halt).to.be.called;
|
||||
expect(bot2.halt).to.be.called;
|
||||
it("proxies to MCP.halt", function() {
|
||||
expect(Cylon.halt).to.be.eql(MCP.halt);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#toJSON", function() {
|
||||
var json, bot1, bot2;
|
||||
|
||||
beforeEach(function() {
|
||||
bot1 = new Robot();
|
||||
bot2 = new Robot();
|
||||
|
||||
Cylon.robots = { bot1: bot1, bot2: bot2 };
|
||||
Cylon.commands.echo = function(arg) { return arg; };
|
||||
|
||||
json = Cylon.toJSON();
|
||||
describe("#api", function() {
|
||||
it("proxies to API.create", function() {
|
||||
expect(Cylon.api).to.be.eql(API.create);
|
||||
});
|
||||
});
|
||||
|
||||
it("contains all robots the MCP knows about", function() {
|
||||
expect(json.robots).to.be.eql([bot1.toJSON(), bot2.toJSON()]);
|
||||
});
|
||||
|
||||
it("contains an array of MCP commands", function() {
|
||||
expect(json.commands).to.be.eql(["echo"]);
|
||||
});
|
||||
|
||||
it("contains an array of MCP events", function() {
|
||||
expect(json.events).to.be.eql(["robot_added", "robot_removed"]);
|
||||
describe("#config", function() {
|
||||
it("proxies to Config.update", function() {
|
||||
expect(Cylon.config).to.be.eql(Config.update);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
var fs = require("fs");
|
||||
|
||||
var DigitalPin = source("io/digital-pin"),
|
||||
Utils = source("utils");
|
||||
var DigitalPin = lib("io/digital-pin"),
|
||||
Utils = lib("utils");
|
||||
|
||||
describe("Cylon.IO.DigitalPin", function() {
|
||||
var pin = new DigitalPin({ pin: "4", mode: "w" });
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
var Driver = source("driver"),
|
||||
Utils = source("utils");
|
||||
var Driver = lib("driver"),
|
||||
Utils = lib("utils");
|
||||
|
||||
describe("Driver", function() {
|
||||
var connection, driver;
|
||||
|
@ -139,8 +139,23 @@ describe("Driver", function() {
|
|||
});
|
||||
|
||||
it("handles edge cases", function() {
|
||||
var commands = ["HelloWorld", "getPNGStream", "getHSetting"],
|
||||
snake_case = ["hello_world", "get_png_stream", "get_h_setting"];
|
||||
var commands = [
|
||||
"HelloWorld",
|
||||
"getPNGStream",
|
||||
"getHSetting",
|
||||
"getRGB",
|
||||
"convertRGBToHSL",
|
||||
"getTemperatureInF"
|
||||
];
|
||||
|
||||
var snake_case = [
|
||||
"hello_world",
|
||||
"get_png_stream",
|
||||
"get_h_setting",
|
||||
"get_rgb",
|
||||
"convert_rgb_to_hsl",
|
||||
"get_temperature_in_f"
|
||||
];
|
||||
|
||||
commands.forEach(function(cmd) {
|
||||
driver[cmd] = function() {};
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
"use strict";
|
||||
|
||||
var initializer = lib("initializer"),
|
||||
Registry = lib("registry"),
|
||||
Config = lib("config");
|
||||
|
||||
var Loopback = lib("test/loopback"),
|
||||
Ping = lib("test/ping"),
|
||||
TestAdaptor = lib("test/test-adaptor"),
|
||||
TestDriver = lib("test/test-driver");
|
||||
|
||||
describe("Initializer", function() {
|
||||
beforeEach(function() {
|
||||
spy(Registry, "findBy");
|
||||
stub(Registry, "register");
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
Registry.findBy.restore();
|
||||
Registry.register.restore();
|
||||
});
|
||||
|
||||
it("creates an instance of the requested adaptor/driver", function() {
|
||||
var adaptor = initializer("adaptor", { adaptor: "loopback" });
|
||||
expect(adaptor).to.be.an.instanceOf(Loopback);
|
||||
|
||||
var driver = initializer("driver", { driver: "ping" });
|
||||
expect(driver).to.be.an.instanceOf(Ping);
|
||||
});
|
||||
|
||||
context("if the module isn't registered", function() {
|
||||
var module;
|
||||
|
||||
beforeEach(function() {
|
||||
Registry.findBy.restore();
|
||||
|
||||
module = {
|
||||
adaptor: stub(),
|
||||
driver: stub()
|
||||
};
|
||||
|
||||
stub(Registry, "findBy")
|
||||
.onFirstCall().returns(false)
|
||||
.onSecondCall().returns(module);
|
||||
});
|
||||
|
||||
context("if a module key was provided", function() {
|
||||
it("attempts to register it", function() {
|
||||
initializer("adaptor", { adaptor: "adaptor", module: "test" });
|
||||
expect(Registry.register).to.be.calledWith("test");
|
||||
expect(module.adaptor).to.be.called;
|
||||
});
|
||||
});
|
||||
|
||||
context("if no module key was provided", function() {
|
||||
it("attempts to find it automatically", function() {
|
||||
initializer("driver", { driver: "driver" });
|
||||
expect(Registry.register).to.be.calledWith("cylon-driver");
|
||||
expect(module.driver).to.be.called;
|
||||
});
|
||||
});
|
||||
|
||||
context("if the module still can't be found", function() {
|
||||
beforeEach(function() {
|
||||
Registry.findBy.onSecondCall().returns(false);
|
||||
});
|
||||
|
||||
it("throws an error", function() {
|
||||
function fn() {
|
||||
return initializer("adaptor", { adaptor: "badadaptor" });
|
||||
}
|
||||
|
||||
expect(fn).to.throw("Unable to find adaptor for badadaptor");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context("if in test mode", function() {
|
||||
var tm = Config.testMode, adaptor, driver;
|
||||
|
||||
beforeEach(function() {
|
||||
Config.testMode = true;
|
||||
|
||||
driver = initializer("driver", { driver: "ping" });
|
||||
adaptor = initializer("adaptor", { adaptor: "loopback" });
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
Config.testMode = tm;
|
||||
});
|
||||
|
||||
it("creates a test adaptor/driver", function() {
|
||||
expect(driver).to.be.an.instanceOf(TestDriver);
|
||||
expect(adaptor).to.be.an.instanceOf(TestAdaptor);
|
||||
});
|
||||
|
||||
it("stubs out the driver/adaptor behaviour", function() {
|
||||
expect(driver.ping).to.be.a("function");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
var Utils = source("io/utils.js");
|
||||
var Utils = lib("io/utils.js");
|
||||
|
||||
describe("IOUtils", function() {
|
||||
describe("#periodAndDuty", function() {
|
||||
|
|
|
@ -1,47 +1,39 @@
|
|||
"use strict";
|
||||
|
||||
var Logger = source("logger"),
|
||||
Config = source("config");
|
||||
var Logger = lib("logger"),
|
||||
Config = lib("config");
|
||||
|
||||
describe("Logger", function() {
|
||||
afterEach(function() {
|
||||
// to be friendly to other specs
|
||||
Config.logging = { logger: false, level: "debug" };
|
||||
Config.logger = false;
|
||||
Config.silent = false;
|
||||
Logger.setup();
|
||||
});
|
||||
|
||||
describe("#setup", function() {
|
||||
context("with no arguments", function() {
|
||||
it("sets up a BasicLogger", function() {
|
||||
Config.logging = {};
|
||||
Config.logger = null;
|
||||
Logger.setup();
|
||||
expect(Logger.toString()).to.be.eql("BasicLogger");
|
||||
expect(Logger.logger.name).to.be.eql("basiclogger");
|
||||
});
|
||||
});
|
||||
|
||||
context("with false", function() {
|
||||
it("sets up a NullLogger", function() {
|
||||
Config.logging = { logger: false };
|
||||
Config.logger = false;
|
||||
Logger.setup();
|
||||
expect(Logger.toString()).to.be.eql("NullLogger");
|
||||
expect(Logger.logger.name).to.be.eql("nulllogger");
|
||||
});
|
||||
});
|
||||
|
||||
context("with a custom logger", function() {
|
||||
it("uses the custom logger", function() {
|
||||
var logger = { toString: function() { return "custom"; } };
|
||||
Config.logging = { logger: logger };
|
||||
function customlogger() {}
|
||||
Config.logger = customlogger;
|
||||
Logger.setup();
|
||||
expect(Logger.toString()).to.be.eql("custom");
|
||||
});
|
||||
});
|
||||
|
||||
context("with a custom logger, provided directly", function() {
|
||||
it("uses the custom logger", function() {
|
||||
var logger = { toString: function() { return "custom"; } };
|
||||
Logger.setup({ logger: logger });
|
||||
expect(Logger.toString()).to.be.eql("custom");
|
||||
expect(Config.logging.logger).to.be.eql(logger);
|
||||
expect(Logger.logger.name).to.be.eql("customlogger");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -50,100 +42,31 @@ describe("Logger", function() {
|
|||
var logger;
|
||||
|
||||
beforeEach(function() {
|
||||
logger = Config.logging.logger = {
|
||||
debug: spy(),
|
||||
info: spy(),
|
||||
warn: spy(),
|
||||
error: spy(),
|
||||
fatal: spy()
|
||||
};
|
||||
|
||||
Logger.setup();
|
||||
logger = spy();
|
||||
Logger.logger = logger;
|
||||
});
|
||||
|
||||
describe("#debug", function() {
|
||||
it("proxies to the Logger's #debug method", function() {
|
||||
Logger.debug("Hello", "World");
|
||||
expect(logger.debug).to.be.calledWith("Hello", "World");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#info", function() {
|
||||
it("proxies to the Logger's #info method", function() {
|
||||
Logger.info("Hello", "World");
|
||||
expect(logger.info).to.be.calledWith("Hello", "World");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#warn", function() {
|
||||
it("proxies to the Logger's #warn method", function() {
|
||||
Logger.warn("Hello", "World");
|
||||
expect(logger.warn).to.be.calledWith("Hello", "World");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#error", function() {
|
||||
it("proxies to the Logger's #error method", function() {
|
||||
Logger.error("Hello", "World");
|
||||
expect(logger.error).to.be.calledWith("Hello", "World");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#fatal", function() {
|
||||
it("proxies to the Logger's #fatal method", function() {
|
||||
Logger.fatal("Hello", "World");
|
||||
expect(logger.fatal).to.be.calledWith("Hello", "World");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("log levels", function() {
|
||||
var logger;
|
||||
|
||||
beforeEach(function() {
|
||||
logger = {
|
||||
debug: spy(),
|
||||
info: spy(),
|
||||
warn: spy(),
|
||||
error: spy(),
|
||||
fatal: spy()
|
||||
};
|
||||
|
||||
Config.logging = {
|
||||
logger: logger,
|
||||
level: "warn"
|
||||
};
|
||||
|
||||
Logger.setup();
|
||||
});
|
||||
|
||||
it("prevents logging below the specified level", function() {
|
||||
it("proxies to the logger method", function() {
|
||||
Logger.should.debug = true;
|
||||
Logger.debug("debug message");
|
||||
Logger.info("info message");
|
||||
|
||||
expect(logger.debug).to.not.be.called;
|
||||
expect(logger.info).to.not.be.called;
|
||||
Logger.should.debug = false;
|
||||
expect(logger).to.be.calledWith("debug message");
|
||||
});
|
||||
});
|
||||
|
||||
it("still logs levels equal/greater than the specified level", function() {
|
||||
Logger.warn("warn message");
|
||||
Logger.error("error message");
|
||||
Logger.fatal("fatal message");
|
||||
|
||||
expect(logger.warn).to.be.calledWith("warn message");
|
||||
expect(logger.error).to.be.calledWith("error message");
|
||||
expect(logger.fatal).to.be.calledWith("fatal message");
|
||||
});
|
||||
|
||||
it("defaults to 'info' level", function() {
|
||||
delete Config.logging.level;
|
||||
Logger.setup();
|
||||
|
||||
Logger.debug("debug message");
|
||||
Logger.info("info message");
|
||||
|
||||
expect(logger.debug).to.not.be.called;
|
||||
expect(logger.info).to.be.calledWith("info message");
|
||||
describe("#log", function() {
|
||||
it("proxies to the logger method", function() {
|
||||
Logger.log("log message");
|
||||
expect(logger).to.be.calledWith("log message");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("automatically updates if configuration changed", function() {
|
||||
var custom = spy();
|
||||
expect(Logger.logger.name).to.be.eql("basiclogger");
|
||||
Config.update({ logger: custom });
|
||||
expect(Logger.logger).to.be.eql(custom);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
var logger = source("logger/basic_logger");
|
||||
|
||||
var date = new Date(0).toISOString();
|
||||
|
||||
describe("BasicLogger", function() {
|
||||
var clock;
|
||||
|
||||
beforeEach(function() {
|
||||
stub(console, "log");
|
||||
clock = sinon.useFakeTimers(0);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
console.log.restore();
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
describe("#toString", function() {
|
||||
it("returns 'BasicLogger'", function() {
|
||||
expect(logger.toString()).to.be.eql("BasicLogger");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#debug", function() {
|
||||
it("logs to the console with a debug string", function() {
|
||||
var logstring = "D, [" + date + "] DEBUG -- :";
|
||||
|
||||
logger.debug("Hello, World");
|
||||
expect(console.log).to.be.calledWith(logstring, "Hello, World");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#info", function() {
|
||||
it("logs to the console with a info string", function() {
|
||||
var logstring = "I, [" + date + "] INFO -- :";
|
||||
|
||||
logger.info("Hello, World");
|
||||
expect(console.log).to.be.calledWith(logstring, "Hello, World");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#warn", function() {
|
||||
it("logs to the console with a warn string", function() {
|
||||
var logstring = "W, [" + date + "] WARN -- :";
|
||||
|
||||
logger.warn("Hello, World");
|
||||
expect(console.log).to.be.calledWith(logstring, "Hello, World");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#error", function() {
|
||||
it("logs to the console with a error string", function() {
|
||||
var logstring = "E, [" + date + "] ERROR -- :";
|
||||
|
||||
logger.error("Hello, World");
|
||||
expect(console.log).to.be.calledWith(logstring, "Hello, World");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#fatal", function() {
|
||||
it("logs to the console with a fatal string", function() {
|
||||
var logstring = "F, [" + date + "] FATAL -- :";
|
||||
|
||||
logger.fatal("Hello, World");
|
||||
expect(console.log).to.be.calledWith(logstring, "Hello, World");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,100 @@
|
|||
"use strict";
|
||||
|
||||
var MCP = lib("mcp"),
|
||||
Robot = lib("robot");
|
||||
|
||||
describe("MCP", function() {
|
||||
it("contains a collection of robots", function() {
|
||||
expect(MCP.robots).to.be.eql({});
|
||||
});
|
||||
|
||||
it("contains a collection of commands", function() {
|
||||
expect(MCP.commands).to.be.eql({});
|
||||
});
|
||||
|
||||
it("contains a collection of events", function() {
|
||||
expect(MCP.events).to.be.eql(["robot_added", "robot_removed"]);
|
||||
});
|
||||
|
||||
describe("#create", function() {
|
||||
afterEach(function() {
|
||||
MCP.robots = {};
|
||||
});
|
||||
|
||||
it("uses passed options to create a new Robot", function() {
|
||||
var opts = { name: "Ultron" };
|
||||
var robot = MCP.create(opts);
|
||||
|
||||
expect(robot.toString()).to.be.eql("[Robot name='Ultron']");
|
||||
expect(MCP.robots.Ultron).to.be.eql(robot);
|
||||
});
|
||||
|
||||
it("avoids duplicating names", function() {
|
||||
MCP.create({ name: "Ultron" });
|
||||
MCP.create({ name: "Ultron" });
|
||||
|
||||
var bots = Object.keys(MCP.robots);
|
||||
expect(bots).to.be.eql(["Ultron", "Ultron-1"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#start", function() {
|
||||
it("calls #start() on all robots", function() {
|
||||
var bot1 = { start: spy() },
|
||||
bot2 = { start: spy() };
|
||||
|
||||
MCP.robots = {
|
||||
bot1: bot1,
|
||||
bot2: bot2
|
||||
};
|
||||
|
||||
MCP.start();
|
||||
|
||||
expect(bot1.start).to.be.called;
|
||||
expect(bot2.start).to.be.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe("#halt", function() {
|
||||
it("calls #halt() on all robots", function() {
|
||||
var bot1 = { halt: spy() },
|
||||
bot2 = { halt: spy() };
|
||||
|
||||
MCP.robots = {
|
||||
bot1: bot1,
|
||||
bot2: bot2
|
||||
};
|
||||
|
||||
MCP.halt();
|
||||
|
||||
expect(bot1.halt).to.be.called;
|
||||
expect(bot2.halt).to.be.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe("#toJSON", function() {
|
||||
var json, bot1, bot2;
|
||||
|
||||
beforeEach(function() {
|
||||
bot1 = new Robot();
|
||||
bot2 = new Robot();
|
||||
|
||||
MCP.robots = { bot1: bot1, bot2: bot2 };
|
||||
MCP.commands.echo = function(arg) { return arg; };
|
||||
|
||||
json = MCP.toJSON();
|
||||
});
|
||||
|
||||
it("contains all robots the MCP knows about", function() {
|
||||
expect(json.robots).to.be.eql([bot1.toJSON(), bot2.toJSON()]);
|
||||
});
|
||||
|
||||
it("contains an array of MCP commands", function() {
|
||||
expect(json.commands).to.be.eql(["echo"]);
|
||||
});
|
||||
|
||||
it("contains an array of MCP events", function() {
|
||||
expect(json.events).to.be.eql(["robot_added", "robot_removed"]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
var Registry = source("registry");
|
||||
var Registry = lib("registry");
|
||||
|
||||
var path = "./../spec/support/mock_module.js";
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
"use strict";
|
||||
|
||||
var Driver = source("driver"),
|
||||
Adaptor = source("adaptor"),
|
||||
Robot = source("robot"),
|
||||
Logger = source("logger");
|
||||
var Driver = lib("driver"),
|
||||
Adaptor = lib("adaptor"),
|
||||
Robot = lib("robot"),
|
||||
Logger = lib("logger");
|
||||
|
||||
describe("Robot", function() {
|
||||
var work, extraFunction, robot;
|
||||
|
@ -32,17 +32,8 @@ describe("Robot", function() {
|
|||
});
|
||||
|
||||
context("if not provided", function() {
|
||||
beforeEach(function() {
|
||||
stub(Robot, "randomName").returns("New Robot");
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
Robot.randomName.restore();
|
||||
});
|
||||
|
||||
it("is set to a random name", function() {
|
||||
var bot = new Robot({});
|
||||
expect(bot.name).to.be.eql("New Robot");
|
||||
it("is set to an incrementing name", function() {
|
||||
expect(new Robot({}).name).to.match(/Robot \d/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -79,7 +70,7 @@ describe("Robot", function() {
|
|||
});
|
||||
};
|
||||
|
||||
expect(fn).to.throw(Error, "No connections specified");
|
||||
expect(fn).to.throw(Error);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -273,27 +264,20 @@ describe("Robot", function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("initConnections", function() {
|
||||
describe("initRobot", function() {
|
||||
var bot;
|
||||
|
||||
beforeEach(function() {
|
||||
bot = new Robot();
|
||||
});
|
||||
|
||||
context("when not passed anything", function() {
|
||||
it("does not modify the bot's connections", function() {
|
||||
bot.initConnections({});
|
||||
expect(bot.connections).to.be.eql({});
|
||||
});
|
||||
});
|
||||
|
||||
context("when passed an object containing connection details", function() {
|
||||
context("when connection details are provided", function() {
|
||||
it("creates new connections with each of the ones provided", function() {
|
||||
var connections = {
|
||||
loopback: { adaptor: "loopback" }
|
||||
};
|
||||
|
||||
bot.initConnections({ connections: connections });
|
||||
bot.initRobot({ connections: connections });
|
||||
expect(bot.connections.loopback).to.be.instanceOf(Adaptor);
|
||||
});
|
||||
|
||||
|
@ -312,7 +296,7 @@ describe("Robot", function() {
|
|||
}
|
||||
};
|
||||
|
||||
bot.initConnections(opts);
|
||||
bot.initRobot(opts);
|
||||
});
|
||||
|
||||
it("adds the devices to opts.devices", function() {
|
||||
|
@ -326,6 +310,23 @@ describe("Robot", function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
context("when device details are provided", function() {
|
||||
it("creates new devices with each of the ones provided", function() {
|
||||
var opts = {
|
||||
connections: {
|
||||
loopback: { adaptor: "loopback" }
|
||||
},
|
||||
|
||||
devices: {
|
||||
ping: { driver: "ping" }
|
||||
}
|
||||
};
|
||||
|
||||
bot.initRobot(opts);
|
||||
expect(bot.devices.ping).to.be.instanceOf(Driver);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#device", function() {
|
||||
|
@ -354,34 +355,7 @@ describe("Robot", function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("initDevices", function() {
|
||||
var bot;
|
||||
|
||||
beforeEach(function() {
|
||||
bot = new Robot({
|
||||
connections: {
|
||||
loopback: { adaptor: "loopback" }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
context("when not passed anything", function() {
|
||||
it("does not modify the bot's devices", function() {
|
||||
bot.initDevices({});
|
||||
expect(bot.devices).to.be.eql({});
|
||||
});
|
||||
});
|
||||
|
||||
context("when passed an object containing device details", function() {
|
||||
it("creates new devices with each of the ones provided", function() {
|
||||
var devices = {
|
||||
ping: { driver: "ping" }
|
||||
};
|
||||
|
||||
bot.initDevices({ devices: devices });
|
||||
expect(bot.devices.ping).to.be.instanceOf(Driver);
|
||||
});
|
||||
});
|
||||
describe("initRobot", function() {
|
||||
});
|
||||
|
||||
describe("#start", function() {
|
||||
|
@ -441,6 +415,13 @@ describe("Robot", function() {
|
|||
expect(bot.connections.alpha.connect).to.be.called;
|
||||
expect(bot.connections.bravo.connect).to.be.called;
|
||||
});
|
||||
|
||||
it("defines a named connection on robot for each connection", function() {
|
||||
bot.startConnections();
|
||||
|
||||
expect(bot.alpha).to.be.an.instanceOf(Adaptor);
|
||||
expect(bot.bravo).to.be.an.instanceOf(Adaptor);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#startDevices", function() {
|
||||
|
@ -468,6 +449,32 @@ describe("Robot", function() {
|
|||
expect(bot.devices.alpha.start).to.be.called;
|
||||
expect(bot.devices.bravo.start).to.be.called;
|
||||
});
|
||||
|
||||
it("runs #start on each device only once", function() {
|
||||
bot.startDevices();
|
||||
bot.startDevices();
|
||||
|
||||
expect(bot.devices.alpha.start).to.be.called.once;
|
||||
expect(bot.devices.bravo.start).to.be.called.once;
|
||||
});
|
||||
|
||||
it("runs #start on a newly added device", function() {
|
||||
bot.startDevices();
|
||||
bot.device("charlie", { driver: "ping" });
|
||||
stub(bot.devices.charlie, "start").returns(true);
|
||||
bot.startDevices();
|
||||
|
||||
expect(bot.devices.alpha.start).to.be.called.once;
|
||||
expect(bot.devices.bravo.start).to.be.called.once;
|
||||
expect(bot.devices.charlie.start).to.be.called.once;
|
||||
});
|
||||
|
||||
it("defines a named device on robot for each device", function() {
|
||||
bot.startDevices();
|
||||
|
||||
expect(bot.alpha).to.be.an.instanceOf(Driver);
|
||||
expect(bot.bravo).to.be.an.instanceOf(Driver);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#halt", function() {
|
||||
|
@ -484,18 +491,13 @@ describe("Robot", function() {
|
|||
}
|
||||
});
|
||||
|
||||
bot.isRunning = true;
|
||||
bot.running = true;
|
||||
|
||||
device = bot.devices.ping;
|
||||
connection = bot.connections.loopback;
|
||||
|
||||
stub(device, "halt").yields(true);
|
||||
stub(connection, "disconnect").yields(true);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
device.halt.restore();
|
||||
connection.disconnect.restore();
|
||||
stub(device, "halt").yields();
|
||||
stub(connection, "disconnect").yields();
|
||||
});
|
||||
|
||||
it("calls #halt on all devices and connections", function() {
|
||||
|
@ -504,6 +506,38 @@ describe("Robot", function() {
|
|||
expect(device.halt).to.be.called;
|
||||
expect(connection.disconnect).to.be.called;
|
||||
});
|
||||
|
||||
context("if a subcall triggers it's callback with an error", function() {
|
||||
beforeEach(function() {
|
||||
bot = new Robot({
|
||||
devices: {
|
||||
ping: { driver: "ping" },
|
||||
ping2: { driver: "ping" }
|
||||
},
|
||||
|
||||
connections: {
|
||||
loopback: { adaptor: "loopback" },
|
||||
loopback2: { adaptor: "loopback" }
|
||||
}
|
||||
});
|
||||
|
||||
bot.running = true;
|
||||
|
||||
stub(bot.devices.ping, "halt").yields("error!");
|
||||
stub(bot.devices.ping2, "halt").yields();
|
||||
stub(bot.connections.loopback, "disconnect").yields("another err!");
|
||||
stub(bot.connections.loopback2, "disconnect").yields();
|
||||
});
|
||||
|
||||
it("doesn't effect the rest of the shutdown", function() {
|
||||
bot.halt();
|
||||
|
||||
expect(bot.devices.ping.halt).to.be.called;
|
||||
expect(bot.devices.ping2.halt).to.be.called;
|
||||
expect(bot.connections.loopback.disconnect).to.be.called;
|
||||
expect(bot.connections.loopback2.disconnect).to.be.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#toString", function() {
|
||||
|
@ -514,22 +548,17 @@ describe("Robot", function() {
|
|||
|
||||
describe("#log", function() {
|
||||
beforeEach(function() {
|
||||
stub(Logger, "info");
|
||||
stub(Logger, "fatal");
|
||||
|
||||
robot.log("info", "an informative message");
|
||||
robot.log("fatal", "a fatal error");
|
||||
stub(Logger, "log");
|
||||
robot.log("an informative message");
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
Logger.info.restore();
|
||||
Logger.fatal.restore();
|
||||
Logger.log.restore();
|
||||
});
|
||||
|
||||
it("it passes messages onto Logger, with the Robot's name", function() {
|
||||
var nameStr = "[" + robot.name + "] - ";
|
||||
expect(Logger.info).to.be.calledWith(nameStr, "an informative message");
|
||||
expect(Logger.fatal).to.be.calledWith(nameStr, "a fatal error");
|
||||
expect(Logger.log).to.be.calledWith(nameStr + "an informative message");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,74 +1,8 @@
|
|||
"use strict";
|
||||
|
||||
var utils = source("utils");
|
||||
var utils = lib("utils");
|
||||
|
||||
describe("Utils", function() {
|
||||
describe("Monkey-patches", function() {
|
||||
describe("Number", function() {
|
||||
describe("#seconds", function() {
|
||||
it("allows for expressing time in seconds", function() {
|
||||
expect((5).seconds()).to.be.eql(5000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#second", function() {
|
||||
it("allows for expressing time in seconds", function() {
|
||||
expect((1).second()).to.be.eql(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#fromScale", function() {
|
||||
it("converts a value from one scale to 0-1 scale", function() {
|
||||
expect((5).fromScale(0, 10)).to.be.eql(0.5);
|
||||
});
|
||||
|
||||
it("converts floats", function() {
|
||||
expect((2.5).fromScale(0, 10)).to.be.eql(0.25);
|
||||
});
|
||||
|
||||
context("if the number goes above the top of the scale", function() {
|
||||
it("should return 1", function() {
|
||||
expect((15).fromScale(0, 10)).to.be.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
context("if the number goes below the bottom of the scale", function() {
|
||||
it("should return 0", function() {
|
||||
expect((15).fromScale(0, 10)).to.be.eql(1);
|
||||
expect((5).fromScale(10, 20)).to.be.eql(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#toScale", function() {
|
||||
it("converts a value from 0-1 scale to another", function() {
|
||||
expect((0.5).toScale(0, 10)).to.be.eql(5);
|
||||
});
|
||||
|
||||
context("when value goes below bottom of scale", function() {
|
||||
it("returns the bottom of the scale", function() {
|
||||
expect((-5).toScale(0, 10)).to.be.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
context("when value goes above top of scale", function() {
|
||||
it("returns the top of the scale", function() {
|
||||
expect((15).toScale(0, 10)).to.be.eql(10);
|
||||
});
|
||||
});
|
||||
|
||||
it("converts to floats", function() {
|
||||
expect((0.25).toScale(0, 10)).to.be.eql(2.5);
|
||||
});
|
||||
|
||||
it("can be chained with #fromScale", function() {
|
||||
var num = (5).fromScale(0, 20).toScale(0, 10);
|
||||
expect(num).to.be.eql(2.5);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#makeUnique", function() {
|
||||
it("returns the original name if it's not a conflict", function() {
|
||||
var res = utils.makeUnique("hello", ["world"]);
|
||||
|
@ -137,6 +71,25 @@ describe("Utils", function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#finish", function() {
|
||||
beforeEach(function() {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.clock.restore();
|
||||
});
|
||||
|
||||
it("stops calling an interval function", function() {
|
||||
var func = spy();
|
||||
var interval = utils.every(10, func);
|
||||
this.clock.tick(15);
|
||||
utils.finish(interval);
|
||||
this.clock.tick(15);
|
||||
expect(func).to.be.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
describe("#subclass", function() {
|
||||
var BaseClass = function BaseClass(opts) {
|
||||
this.greeting = opts.greeting;
|
||||
|
@ -162,8 +115,7 @@ describe("Utils", function() {
|
|||
describe("#proxyFunctionsToObject", function() {
|
||||
var methods = ["asString", "toString", "returnString"];
|
||||
|
||||
var ProxyClass = (function() {
|
||||
function ProxyClass() {}
|
||||
var ProxyClass = function ProxyClass() {};
|
||||
|
||||
ProxyClass.prototype.asString = function() {
|
||||
return "[object ProxyClass]";
|
||||
|
@ -177,17 +129,10 @@ describe("Utils", function() {
|
|||
return string;
|
||||
};
|
||||
|
||||
return ProxyClass;
|
||||
})();
|
||||
|
||||
var TestClass = (function() {
|
||||
function TestClass() {
|
||||
var TestClass = function TestClass() {
|
||||
this.testInstance = new ProxyClass();
|
||||
utils.proxyFunctionsToObject(methods, this.testInstance, this, true);
|
||||
}
|
||||
|
||||
return TestClass;
|
||||
})();
|
||||
};
|
||||
|
||||
var testclass = new TestClass();
|
||||
|
||||
|
@ -251,4 +196,22 @@ describe("Utils", function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#classCallCheck", function() {
|
||||
it("checks that an object is an instance of a constructor", function() {
|
||||
var fn = function(instance, Constructor) {
|
||||
return utils.classCallCheck.bind(null, instance, Constructor);
|
||||
};
|
||||
|
||||
function Class() {
|
||||
utils.classCallCheck(this, Class);
|
||||
}
|
||||
|
||||
expect(fn([], Array)).to.not.throw;
|
||||
expect(fn([], Number)).to.throw(TypeError);
|
||||
|
||||
expect(Class).to.throw(TypeError);
|
||||
expect(function() { return new Class(); }).not.to.throw(TypeError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
var _ = source("utils/helpers");
|
||||
var _ = lib("utils/helpers");
|
||||
|
||||
describe("Helpers", function() {
|
||||
describe("extend", function() {
|
||||
|
@ -289,4 +289,90 @@ describe("Helpers", function() {
|
|||
expect(fn(arr, {})).to.be.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#parallel", function() {
|
||||
var fn1, fn2, fn3, callback;
|
||||
|
||||
beforeEach(function() {
|
||||
fn1 = stub();
|
||||
fn2 = stub();
|
||||
fn3 = stub();
|
||||
callback = stub();
|
||||
});
|
||||
|
||||
it("executes a set of functions in parallel", function() {
|
||||
_.parallel([fn1, fn2, fn3], callback);
|
||||
|
||||
expect(fn1).to.be.called;
|
||||
expect(fn2).to.be.called;
|
||||
expect(fn3).to.be.called;
|
||||
expect(callback).to.not.be.called;
|
||||
|
||||
fn1.yield(null, true);
|
||||
expect(callback).to.not.be.called;
|
||||
fn2.yield(null, true);
|
||||
expect(callback).to.not.be.called;
|
||||
fn3.yield(null, true);
|
||||
|
||||
expect(callback).to.be.calledWith(null, [true, true, true]);
|
||||
});
|
||||
|
||||
it("stops immediately if there's an error", function() {
|
||||
_.parallel([fn1, fn2, fn3], callback);
|
||||
|
||||
fn1.yield(true, null);
|
||||
|
||||
expect(callback).to.be.calledWith(true);
|
||||
|
||||
fn2.yields(null, true);
|
||||
fn3.yields(null, true);
|
||||
|
||||
expect(callback).to.be.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
describe("#series", function() {
|
||||
var fn1, fn2, fn3, callback;
|
||||
|
||||
beforeEach(function() {
|
||||
fn1 = stub();
|
||||
fn2 = stub();
|
||||
fn3 = stub();
|
||||
callback = stub();
|
||||
});
|
||||
|
||||
it("executes a set of functions in series", function() {
|
||||
_.series([fn1, fn2, fn3], callback);
|
||||
|
||||
expect(fn1).to.be.called;
|
||||
expect(fn2).to.not.be.called;
|
||||
expect(fn3).to.not.be.called;
|
||||
expect(callback).to.not.be.called;
|
||||
|
||||
fn1.yield(null, true);
|
||||
|
||||
expect(fn2).to.be.called;
|
||||
expect(fn3).to.not.be.called;
|
||||
expect(callback).to.not.be.called;
|
||||
|
||||
fn2.yield(null, true);
|
||||
|
||||
expect(fn3).to.be.called;
|
||||
expect(callback).to.not.be.called;
|
||||
|
||||
fn3.yield(null, true);
|
||||
|
||||
expect(callback).to.be.calledWith(null, [true, true, true]);
|
||||
});
|
||||
|
||||
it("stops immediately if there's an error", function() {
|
||||
_.series([fn1, fn2, fn3], callback);
|
||||
|
||||
fn1.yield(true, null);
|
||||
|
||||
expect(fn2).to.not.be.called;
|
||||
expect(fn3).to.not.be.called;
|
||||
expect(callback).to.be.calledWith(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
// jshint expr:true
|
||||
"use strict";
|
||||
|
||||
var patches = lib("utils/monkey-patches");
|
||||
|
||||
describe("monkey-patches", function() {
|
||||
beforeEach(function() {
|
||||
patches.uninstall();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
patches.install();
|
||||
});
|
||||
|
||||
describe("#install", function() {
|
||||
it("monkey-patches methods onto global classes", function() {
|
||||
var proto = Number.prototype;
|
||||
|
||||
expect(proto.seconds).to.be.undefined;
|
||||
expect(proto.second).to.be.undefined;
|
||||
|
||||
patches.install();
|
||||
|
||||
expect(proto.seconds).to.be.a("function");
|
||||
expect(proto.second).to.be.a("function");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#uninstall", function() {
|
||||
it("removes existing monkey-patching", function() {
|
||||
var proto = Number.prototype;
|
||||
|
||||
patches.install();
|
||||
|
||||
expect(proto.seconds).to.be.a("function");
|
||||
expect(proto.second).to.be.a("function");
|
||||
|
||||
patches.uninstall();
|
||||
|
||||
expect(proto.seconds).to.be.undefined;
|
||||
expect(proto.second).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe("Number", function() {
|
||||
beforeEach(function() {
|
||||
patches.install();
|
||||
});
|
||||
|
||||
describe("#seconds", function() {
|
||||
it("allows for expressing time in seconds", function() {
|
||||
expect((5).seconds()).to.be.eql(5000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#second", function() {
|
||||
it("allows for expressing time in seconds", function() {
|
||||
expect((1).second()).to.be.eql(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#milliseconds", function() {
|
||||
it("allows for expressing time in milliseconds", function() {
|
||||
expect((5).milliseconds()).to.be.eql(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#ms", function() {
|
||||
it("allows for expressing time in milliseconds", function() {
|
||||
expect((5).ms()).to.be.eql(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#microseconds", function() {
|
||||
it("allows for expressing time in microseconds", function() {
|
||||
expect((5000).microseconds()).to.be.eql(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#minutes", function() {
|
||||
it("allows for expressing time in minutes", function() {
|
||||
expect((5).minutes()).to.be.eql(300000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#minute", function() {
|
||||
it("allows for expressing time per minute", function() {
|
||||
expect((1).minute()).to.be.eql(60000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#fromScale", function() {
|
||||
it("converts a value from one scale to 0-1 scale", function() {
|
||||
expect((5).fromScale(0, 10)).to.be.eql(0.5);
|
||||
});
|
||||
|
||||
it("converts floats", function() {
|
||||
expect((2.5).fromScale(0, 10)).to.be.eql(0.25);
|
||||
});
|
||||
|
||||
context("if the number goes above the top of the scale", function() {
|
||||
it("should return 1", function() {
|
||||
expect((15).fromScale(0, 10)).to.be.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
context("if the number goes below the bottom of the scale", function() {
|
||||
it("should return 0", function() {
|
||||
expect((15).fromScale(0, 10)).to.be.eql(1);
|
||||
expect((5).fromScale(10, 20)).to.be.eql(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#toScale", function() {
|
||||
it("converts a value from 0-1 scale to another", function() {
|
||||
expect((0.5).toScale(0, 10)).to.be.eql(5);
|
||||
});
|
||||
|
||||
context("when value goes below bottom of scale", function() {
|
||||
it("returns the bottom of the scale", function() {
|
||||
expect((-5).toScale(0, 10)).to.be.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
context("when value goes above top of scale", function() {
|
||||
it("returns the top of the scale", function() {
|
||||
expect((15).toScale(0, 10)).to.be.eql(10);
|
||||
});
|
||||
});
|
||||
|
||||
it("converts to floats", function() {
|
||||
expect((0.25).toScale(0, 10)).to.be.eql(2.5);
|
||||
});
|
||||
|
||||
it("can be chained with #fromScale", function() {
|
||||
var num = (5).fromScale(0, 20).toScale(0, 10);
|
||||
expect(num).to.be.eql(2.5);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue