├── .gitignore ├── .npmignore ├── catbotSchematic.png ├── docs └── catbotSchematic.xcf ├── .eslintrc.js ├── examples ├── example.js └── analogJoystick.js ├── lib ├── catbotrcDefault.json ├── config.js └── utils.js ├── .editorconfig ├── package.json ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | joy.js 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | joy.js 3 | catbotSchematic.xcf 4 | -------------------------------------------------------------------------------- /catbotSchematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catsAndSolenoids/catbot/HEAD/catbotSchematic.png -------------------------------------------------------------------------------- /docs/catbotSchematic.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catsAndSolenoids/catbot/HEAD/docs/catbotSchematic.xcf -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "standard", 3 | "installedESLint": true, 4 | "plugins": [ 5 | "standard", 6 | "promise" 7 | ] 8 | }; -------------------------------------------------------------------------------- /examples/example.js: -------------------------------------------------------------------------------- 1 | const catbot = require('../index') 2 | 3 | const opts = { 4 | hwJoystick: true, 5 | boardOpt: {repl: false}, 6 | catConf: {hw: {laser: {pin: 12}}} 7 | } 8 | 9 | function catCb (err, hardware) { 10 | if (err) throw err 11 | hardware.laser.on() 12 | } 13 | 14 | catbot(catCb, opts) 15 | -------------------------------------------------------------------------------- /examples/analogJoystick.js: -------------------------------------------------------------------------------- 1 | /* 2 | basic use of the analog joystick 3 | */ 4 | const catbot = require('../index') 5 | 6 | const opts = { 7 | hwJoystick: true // this enable the harware joystick 8 | } 9 | 10 | function catCb (err, hardware) { 11 | if (err) throw err 12 | hardware.laser.on() // set the laser on 13 | } 14 | 15 | catbot(catCb, opts) // catCb will be ran when the board is ready 16 | -------------------------------------------------------------------------------- /lib/catbotrcDefault.json: -------------------------------------------------------------------------------- 1 | { 2 | "hw": { 3 | "laser": { 4 | "pin": 12 5 | }, 6 | "servoX": { 7 | "pin": 10, 8 | "inverted": true 9 | }, 10 | "servoY": { 11 | "pin": 11, 12 | "inverted": true 13 | }, 14 | "jstk": { 15 | "x": "A0", 16 | "y": "A1", 17 | "jBt": 9, 18 | "isPullup": true, 19 | "deadZone": 0.05 20 | } 21 | }, 22 | "inputRange": [0, 180], 23 | "servoRange": [10, 170], 24 | "hwJstk": false, 25 | "isDefaultConfig" : true 26 | } 27 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs-extra') 3 | const rc = require('rc') 4 | const debug = require('debug')('catbot:conf') 5 | const baseConf = fs.readJsonSync(path.join(__dirname, '/catbotrcDefault.json')) 6 | const _ = require('lodash') 7 | 8 | /** 9 | * get the configuration using the rc module, conf is searched in all dir til the root from here walking up 10 | * then merge it with the default configuration 11 | * @returns {Object} the configuration object 12 | */ 13 | function getConf (cf) { 14 | // merge basic conf with possible .catbotrc files 15 | let conf = rc('catbot', baseConf) 16 | if (cf) { 17 | // merges it with provided options 18 | conf = _.merge(conf, cf) 19 | } 20 | debug('conf', conf) 21 | return conf 22 | } 23 | 24 | module.exports = getConf 25 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_size = 2 11 | 12 | # Matches multiple files with brace expansion notation 13 | # Set default charset 14 | [*.{js,py}] 15 | charset = utf-8 16 | 17 | # 4 space indentation 18 | [*.py] 19 | indent_style = space 20 | indent_size = 4 21 | 22 | # Tab indentation (no size specified) 23 | [Makefile] 24 | indent_style = tab 25 | 26 | # Indentation override for all JS under lib directory 27 | [*.js] 28 | indent_style = space 29 | indent_size = 2 30 | 31 | # Matches the exact files either package.json or .travis.yml 32 | [{package.json,.travis.yml}] 33 | indent_style = space 34 | indent_size = 2 -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * apply a deadzone on the joystick inputs 3 | * 4 | * @param {number} number value from the joystick 5 | * @param {number} threshold of the axe 6 | * @returns {number} deadzoned value 7 | */ 8 | var applyDeadzone = function (number, threshold) { 9 | let percentage = (Math.abs(number) - threshold) / (1 - threshold) 10 | 11 | if (percentage < 0) { 12 | percentage = 0 13 | } 14 | 15 | return percentage * (number > 0 ? 1 : -1) 16 | } 17 | 18 | /** 19 | * convert value from a range to another 20 | * 21 | * @param {int} value to convert 22 | * @param {array} r1 range of the initial value 23 | * @param {array} r2 desired range scale 24 | * @returns {int} value converted to the desired scale in the dest range 25 | */ 26 | function convertRange (value, r1, r2) { 27 | return (value - r1[ 0 ]) * (r2[ 1 ] - r2[ 0 ]) / (r1[ 1 ] - r1[ 0 ]) + r2[ 0 ] 28 | } 29 | 30 | module.exports = { 31 | applyDeadzone, 32 | convertRange 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "catbot", 3 | "version": "1.0.3", 4 | "description": "arduino remote cat controller", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "eslint index.js lib/*.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/gorhgorh/catbot.git" 12 | }, 13 | "keywords": [ 14 | "arduino", 15 | "johnny-five", 16 | "cat" 17 | ], 18 | "author": "Jérôme Loï", 19 | "license": "BSD", 20 | "dependencies": { 21 | "chalk": "^2.4.2", 22 | "debug": "^4.1.1", 23 | "johnny-five": "^1.1.0", 24 | "lodash": "^4.17.4", 25 | "node-getopt": "^0.3.2", 26 | "fs-extra": "^8.1.0", 27 | "rc": "^1.1.6" 28 | }, 29 | "devDependencies": { 30 | "eslint": "^3.13.1", 31 | "eslint-config-standard": "^6.2.1", 32 | "eslint-plugin-promise": "^3.4.0", 33 | "eslint-plugin-standard": "^2.0.1" 34 | }, 35 | "bugs": { 36 | "url": "https://github.com/gorhgorh/catbot/issues" 37 | }, 38 | "homepage": "https://github.com/gorhgorh/catbot#readme", 39 | "directories": { 40 | "example": "examples" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CATBOT 2 | 3 | ## TL;DR 4 | 5 | to use the cat bot you need to require it ... 6 | 7 | ```javascript 8 | var catbot = require('catbot'); 9 | 10 | // callback function that will be called when the board is ready 11 | function catCb (error, hardware) { 12 | /* 13 | Board is ready, use your robot: 14 | hardware = { 15 | board : Board 16 | , x : Servo 17 | , y : Servo 18 | , laser : Led 19 | , to : Function 20 | } 21 | */ 22 | hardware.laser.on() // set the laser on 23 | } 24 | 25 | const opts = { 26 | hwJoystick: true // this enable the harware joystick 27 | } 28 | 29 | catbot(catCb, opts) // catCb will be ran when the board is ready 30 | ``` 31 | 32 | ## Use it 33 | 34 | ### instantiate the bot 35 | see the code above. 36 | 37 | ### inside the bot : 38 | move the X axis servo to 30° ```hardware.x.to(30)``` same goes for Y 39 | 40 | - switch the laser on ```hardware.laser.on()``` see j5 docs for [servo][1] 41 | - toggle the laser ```hardware.laser.toggle()``` see j5 docs for [led][2] 42 | - joystick events ```harware.joystick.on('change', function () {...})``` see j5 docs for [joysticks][3] 43 | - move both servo ```hardware.to([90, 90])``` 44 | 45 | note on the 'to' method: this method is using inputRange and servoRange to scale value, see the detailed option for example 46 | 47 | ## default Configuration 48 | 49 | ### Pins 50 | default config for the pins, wire your device as follow : 51 | 52 | - servoX : pin 10 53 | - servoY : pin 11 54 | - laser : pin 12 55 | - joystick 56 | - x axis : A0 57 | - y axis : A1 58 | - bt : pin 9 59 | 60 | here is a diagram of the cat wiring 61 | 62 | ![catbot wiring](catbotSchematic.png) 63 | 64 | the led with in the schematic is the laser, my Fritzing did not have a laser module. 65 | 66 | ## Options 67 | 68 | the second argument of the catbot function is an option oject, here are the default and explanation of the settings, this object if optional, and you just need to add the value you want to override 69 | 70 | ```javascript 71 | { 72 | hwJoystick: false // this enable/disable creation and pooling of the the harware analogue joystick 73 | catConfig: { 74 | "inputRange": [0, 180], 75 | "servoRange": [10, 170], 76 | "hwJstk": false, 77 | "hw": { 78 | "laser": { 79 | "pin": 12 80 | }, 81 | "servoX": { 82 | "pin": 10, 83 | "inverted": true 84 | }, 85 | "servoY": { 86 | "pin": 11, 87 | "inverted": true 88 | }, 89 | "jstk": { 90 | "x": "A0", 91 | "y": "A1", 92 | "jBt": 9, 93 | "isPullup": true, 94 | "deadZone": 0.05 95 | } 96 | } 97 | } 98 | } 99 | ``` 100 | 101 | ### inputRange, servoRange 102 | 103 | these are two arrays of 2 numbers reprsenting the min-max scale the catbot will match. 104 | 105 | example, gamepax axes are usually from a range -1 to 1 with 0 as a center, while servos take a number between 0 and 180 representing the degree. in this case 106 | 107 | ``` 108 | ... 109 | inputRange: [-1, 1] 110 | servoRange: [0, 180] 111 | ... 112 | ``` 113 | 114 | will map the gamepad axes to the coresponding angle 115 | 116 | #### hwJoystick 117 | enable or disable listening for the analog joystick, note that this will produce event that will move the turret, so you won't be able to controll it on joystick mode. 118 | 119 | #### hw: hardware 120 | pin and option configuration for the catbot harware contain the following options 121 | 122 | #### laser 123 | pin : the pin the laser is attached to (default: 12) 124 | 125 | #### servoX, servoY 126 | - pin : the pin the servo is attached to (default: 10, 11) 127 | - inverted: is the axis inverted (default: true) 128 | 129 | #### jstk 130 | - x: pin for the X axis (default: A0) 131 | - y: pin for the Y axis (default: A1) 132 | - jBt: pin for the joystick button (default: 9) 133 | - isPullup: does the button need a pullup resistor (set internaly) to filter noise (default: true) 134 | - deadZone: apply a deadzone on the joy input to avoid noize from joy calibration (default: 0.05) 135 | 136 | [1]: http://johnny-five.io/api/servo/ 137 | [2]: http://johnny-five.io/api/led/ 138 | [3]: http://johnny-five.io/api/joystick/ 139 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const five = require('johnny-five') 2 | 3 | const chalk = require('chalk') 4 | const green = chalk.green 5 | const blue = chalk.cyan 6 | 7 | const debug = require('debug')('catbot:board') 8 | const utils = require('./lib/utils') 9 | const applyDeadzone = utils.applyDeadzone 10 | const rng = utils.convertRange 11 | const getConf = require('./lib/config') 12 | 13 | function makeCat (catCb, opts) { 14 | console.log(green(msg)) 15 | if (!catCb) { 16 | catCb = function () { 17 | debug('no cb provided') 18 | } 19 | } 20 | if (!opts) opts = {} 21 | if (!opts.boardOpt) opts.boardOpt = {repl: false} 22 | if (!opts.catConf) { 23 | opts.catConf = getConf() 24 | } else { 25 | opts.catConf = getConf(opts.catConf) 26 | } 27 | if (!opts.inputRange) { 28 | debug('inputRange missing') 29 | if (opts.catConf.inputRange) { 30 | opts.inputRange = [opts.catConf.inputRange[0], opts.catConf.inputRange[1]] 31 | } else { 32 | opts.inputRange = [0, 180] 33 | } 34 | } 35 | if (!opts.servoRange) { 36 | debug('servorange missing') 37 | if (opts.catConf.servoRange) { 38 | opts.servoRange = [opts.catConf.servoRange[0], opts.catConf.servoRange[1]] 39 | } else { 40 | opts.servoRange = [0, 180] 41 | } 42 | } 43 | debug('input', opts.inputRange, 'servo', opts.servoRange) 44 | // debug('opts') 45 | // debug(opts.catConf.hw) 46 | 47 | const hw = opts.catConf.hw 48 | const cat = {hw: {}} 49 | 50 | cat.board = new five.Board(opts.boardOpt) 51 | cat.board.on('ready', function () { 52 | debug('board is ready') 53 | console.log(blue('catbot is ready to use')) 54 | const laser = new five.Led(hw.laser.pin) 55 | const servoX = new five.Servo({pin: hw.servoX.pin, startAt: 90, range: opts.servoRange}) 56 | const servoY = new five.Servo({pin: hw.servoY.pin, startAt: 90, range: opts.servoRange}) 57 | 58 | // if analog joystick is enabled 59 | if (opts.hwJoystick || opts.catConf.hwJstk) { 60 | let joyTimer 61 | let isJoyOn = false 62 | debug('init hwJoystick') 63 | console.log(green('Joystick configured')) 64 | console.log(blue('Press the joystick button for 2 sec to enable/disable it, quick press to toggle laser')) 65 | const hwJoy = new five.Joystick({ 66 | pins: [hw.jstk.x, hw.jstk.y] 67 | }) 68 | 69 | const button = new five.Button({ 70 | pin: 9, 71 | isPullup: hw.jstk.isPullup 72 | }) 73 | 74 | hwJoy.on('change', function () { 75 | if (isJoyOn === true) { 76 | const X = applyDeadzone(this.x, hw.jstk.deadZone) 77 | const Y = applyDeadzone(this.y, hw.jstk.deadZone) 78 | debug(X, Y) 79 | if (X !== 0 || Y !== 0) { 80 | servoX.to(rng(this.x, [1, -1], opts.servoRange)) 81 | servoY.to(rng(this.y, [1, -1], opts.servoRange)) 82 | } else { 83 | servoX.to(rng(0, [1, -1], [10, 170])) 84 | servoY.to(rng(0, [1, -1], [10, 170])) 85 | } 86 | } 87 | }) 88 | 89 | button.on('up', function () { 90 | clearTimeout(joyTimer) 91 | laser.toggle() 92 | }) 93 | button.on('down', function () { 94 | console.log(blue('hold 2 sec to toggle joymode')) 95 | joyTimer = setTimeout(function () { 96 | isJoyOn = !isJoyOn 97 | console.log(blue('joyMode:'), isJoyOn) 98 | }, 2000) 99 | }) 100 | 101 | // export a ref to the hardware to the cat object 102 | cat.hwJoy = hwJoy 103 | } 104 | 105 | /** 106 | * orient the turret to the passed positions 107 | * 108 | * @param {Array} pos array of 2 value pos X and Y {numbers} 109 | * @param {Array} [servos] optional array of j5 servos obj 110 | */ 111 | cat.to = function (pos, servos) { 112 | debug('cat.to called') 113 | if (!servos) servos = [servoX, servoY] 114 | servos[0].to(rng(pos[0], opts.inputRange, opts.servoRange)) 115 | servos[1].to(rng(pos[1], opts.inputRange, opts.servoRange)) 116 | } 117 | 118 | // enable inject hardware to REPL if REPL is enabled 119 | if (opts.boardOpt.repl === true) { 120 | console.log(green('REPL mode enabled')) 121 | cat.board.repl.inject({ 122 | x: servoX, 123 | y: servoY, 124 | laser: laser, 125 | to: cat.to 126 | }) 127 | } 128 | 129 | // export a ref to the hardware to the cat object 130 | cat.laser = laser 131 | cat.x = servoX 132 | cat.y = servoY 133 | return catCb(null, cat) 134 | }) 135 | } 136 | 137 | const msg = ` 138 | \`*-. 139 | ) _\`-. 140 | . : \`. . 141 | : _ ' \\ 142 | ; *\` _. \`*-._ 143 | \`-.-' \`-. 144 | ; \` \`. 145 | :. . \\ 146 | . \\ . : .-' . 147 | ' \`+.; ; ' : 148 | : ' | ; ;-. 149 | ; ' : :\`-: _.\`* 150 | .*' / .*' ; .*\`- +' \`*' 151 | . \`*-* \`*-* \`*-*'\`) 152 | ` 153 | 154 | module.exports = makeCat 155 | --------------------------------------------------------------------------------