├── .gitignore ├── docs └── gearExplanation.gif ├── package.json ├── test └── utils.js ├── objects ├── motor.js └── sensor.js ├── README.md ├── utils.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | .idea/ -------------------------------------------------------------------------------- /docs/gearExplanation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byWulf/brickpi3-nodejs/HEAD/docs/gearExplanation.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "brickpi3", 3 | "version": "0.9.8", 4 | "description": "Control your BrickPi3 with nodejs", 5 | "main": "index.js", 6 | "dependencies": { 7 | "pi-spi": "^1.0.1", 8 | "es7-sleep": "^1.0.0" 9 | }, 10 | "devDependencies": { 11 | "mocha": "^4.0.1" 12 | }, 13 | "scripts": { 14 | "test": "mocha" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/bywulf/brickpi3-nodejs.git" 19 | }, 20 | "keywords": [ 21 | "brickpi", 22 | "brickpi3", 23 | "lego", 24 | "raspberryPi", 25 | "robot" 26 | ], 27 | "author": "Michael Wolf ", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/bywulf/brickpi3-nodejs/issues" 31 | }, 32 | "homepage": "https://github.com/bywulf/brickpi-nodejs3#readme" 33 | } 34 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const utils = require(__dirname + '/../utils'); 3 | 4 | describe('Gear', function() { 5 | const Gear = utils.Gear; 6 | describe('getFactor', function() { 7 | it ('should return 1 when 5 teeth connected to 5 teeth', function() { 8 | let gear = new Gear(5).connect(5); 9 | assert.equal(gear.getFactor(), 1); 10 | }); 11 | it ('should return 1 when 5 teeth connected to 20 teeth', function() { 12 | let gear = new Gear(5).connect(20); 13 | assert.equal(gear.getFactor(), 1); 14 | }); 15 | it ('should return 1 when 20 teeth connected to 5 teeth', function() { 16 | let gear = new Gear(20).connect(5); 17 | assert.equal(gear.getFactor(), 1); 18 | }); 19 | it ('should return 1 when 20 teeth connected to 5 teeth connected to 20 teeth', function() { 20 | let gear = new Gear(20).connect(5).connect(20); 21 | assert.equal(gear.getFactor(), 1); 22 | }); 23 | 24 | it ('should return -1 when 5 teeth drive 5 teeth', function() { 25 | let gear = new Gear(5).drive(5); 26 | assert.equal(gear.getFactor(), -1); 27 | }); 28 | it ('should return -0.25 when 5 teeth drive 20 teeth', function() { 29 | let gear = new Gear(5).drive(20); 30 | assert.equal(gear.getFactor(), -0.25); 31 | }); 32 | it ('should return -4 when 20 teeth drive 5 teeth', function() { 33 | let gear = new Gear(20).drive(5); 34 | assert.equal(gear.getFactor(), -4); 35 | }); 36 | it ('should return 4 when 20 teeth drive 5 teeth driving 5 teeth', function() { 37 | let gear = new Gear(20).drive(5).drive(5); 38 | assert.equal(gear.getFactor(), 4); 39 | }); 40 | 41 | it('should return 16 when 5 teeth connected to 20 teeth joined with 5 teeth connected to 20 teeth joined with 5 teeth', function() { 42 | let gear = new Gear(5).connect(20).drive(5).connect(20).drive(5); 43 | assert.equal(gear.getFactor(), 16); 44 | }); 45 | }) 46 | }); -------------------------------------------------------------------------------- /objects/motor.js: -------------------------------------------------------------------------------- 1 | const sleep = require('es7-sleep'); 2 | 3 | class Motor { 4 | constructor(BP, port) { 5 | this.BP = BP; 6 | this.port = port; 7 | } 8 | 9 | /** 10 | * Set the motor power in percent 11 | * 12 | * @param {number} power The power from -100 to 100, or -128 for float 13 | * @param {function} breakFunction 14 | * @return {Promise} 15 | */ 16 | async setPower(power, breakFunction = null) { 17 | await this.BP.set_motor_power(this.port, power); 18 | 19 | if (typeof breakFunction === 'function') { 20 | while (!await breakFunction()) { 21 | await sleep(20); 22 | } 23 | 24 | await this.BP.set_motor_power(this.port, 0); 25 | } 26 | } 27 | /** 28 | * Set the motor target position in degrees 29 | * 30 | * @param {number} position The target position 31 | * @param {number} power If given, sets the power limit of the motor 32 | * @return {Promise} Resolves, when the target position is reached 33 | */ 34 | async setPosition(position, power = 0) { 35 | if (power > 0) { 36 | await this.BP.set_motor_limits(this.port, power); 37 | } 38 | await this.BP.set_motor_position(this.port, position); 39 | 40 | let lastEncoder = null; 41 | while (true) { 42 | await sleep(20); 43 | 44 | let encoder = await this.BP.get_motor_encoder(this.port); 45 | 46 | if (lastEncoder !== null && lastEncoder === encoder) { 47 | return encoder; 48 | } 49 | lastEncoder = encoder; 50 | } 51 | } 52 | 53 | /** 54 | * Set the motor target position KP constant 55 | * 56 | * @param {number} kp The KP constant (default 25) 57 | * @return {Promise} 58 | */ 59 | setPositionKp(kp = 25) { 60 | return this.BP.set_motor_position_kp(this.port, kp); 61 | } 62 | 63 | /** 64 | * Set the motor target position KD constant 65 | * 66 | * @param {number} kd The KD constant (default 70) 67 | * @return {Promise} 68 | */ 69 | setPositionKd(kd = 70) { 70 | return this.BP.set_motor_position_kd(this.port, kd); 71 | } 72 | 73 | /** 74 | * Set the motor target speed in degrees per second 75 | * 76 | * @param {number} dps The target speed in degrees per second 77 | * @return {Promise} 78 | */ 79 | setDps(dps) { 80 | return this.BP.set_motor_dps(this.port, dps); 81 | } 82 | 83 | /** 84 | * Set the motor speed limit 85 | * 86 | * @param {number} power The power limit in percent (0 to 100), with 0 being no limit (100) 87 | * @param {number} dps The speed limit in degrees per second, with 0 being no limit 88 | * @return {Promise} 89 | */ 90 | setLimits(power = 0, dps = 0) { 91 | return this.BP.set_motor_limits(this.port, power, dps); 92 | } 93 | 94 | /** 95 | * Read a motor status 96 | * 97 | * Returns a list: 98 | * flags -- 8-bits of bit-flags that indicate motor status: 99 | * bit 0 -- LOW_VOLTAGE_FLOAT - The motors are automatically disabled because the battery voltage is too low 100 | * bit 1 -- OVERLOADED - The motors aren't close to the target (applies to position control and dps speed control). 101 | * power -- the raw PWM power in percent (-100 to 100) 102 | * encoder -- The encoder position 103 | * dps -- The current speed in Degrees Per Second 104 | * 105 | * @return {Promise.} 106 | */ 107 | getStatus() { 108 | return this.BP.get_motor_status(this.port); 109 | } 110 | 111 | /** 112 | * Offset a motor encoder 113 | * Zero the encoder by offsetting it by the current position 114 | * 115 | * @param {number} position The encoder offset 116 | * @return {Promise} 117 | */ 118 | setEncoder(position) { 119 | return this.BP.offset_motor_encoder(this.port, position); 120 | } 121 | 122 | /** 123 | * Read a motor encoder in degrees 124 | * 125 | * Returns the encoder position in degrees 126 | * 127 | * @return {Promise.} 128 | */ 129 | getEncoder() { 130 | return this.BP.get_motor_encoder(this.port); 131 | } 132 | 133 | /** 134 | * Resets the encoder to 0 on its current position 135 | * 136 | * @return {Promise} 137 | */ 138 | async resetEncoder() { 139 | return this.setEncoder(await this.getEncoder()); 140 | } 141 | } 142 | 143 | module.exports = Motor; -------------------------------------------------------------------------------- /objects/sensor.js: -------------------------------------------------------------------------------- 1 | const sleep = require('es7-sleep'); 2 | 3 | class Sensor { 4 | constructor(BP, port, timeLimit) { 5 | this.BP = BP; 6 | this.port = port; 7 | this.timeLimit = timeLimit || 3000; 8 | } 9 | 10 | /** 11 | * Set the sensor type. 12 | * 13 | * params is used for the following sensor types: 14 | * CUSTOM -- a 16-bit integer used to configure the hardware. 15 | * I2C -- a list of settings: 16 | * params[0] -- Settings/flags 17 | * params[1] -- target Speed in microseconds (0-255). Realistically the speed will vary. 18 | * if SENSOR_I2C_SETTINGS_SAME flag set in I2C Settings: 19 | * params[2] -- Delay in microseconds between transactions. 20 | * params[3] -- Address 21 | * params[4] -- List of bytes to write 22 | * params[5] -- Number of bytes to read 23 | * 24 | * @param {number} type The sensor type 25 | * @param {*} params the parameters needed for some sensor types. 26 | * @return {Promise} 27 | */ 28 | async setType(type, params = 0) { 29 | return this.BP.set_sensor_type(this.port, type, params); 30 | } 31 | 32 | /** 33 | * Set the default waiting time for sensor configuration configuration. 34 | * 35 | * @param {number} waiting time for sensor configuration 36 | */ 37 | setConfigurationTimeLimit(timeLimit) { 38 | this.timeLimit = timeLimit; 39 | } 40 | 41 | /** 42 | * Read a sensor value 43 | * 44 | * Returns the value(s) for the specified sensor. 45 | * The following sensor types each return a single value: 46 | * NONE ----------------------- 0 47 | * TOUCH ---------------------- 0 or 1 (released or pressed) 48 | * NXT_TOUCH ------------------ 0 or 1 (released or pressed) 49 | * EV3_TOUCH ------------------ 0 or 1 (released or pressed) 50 | * NXT_ULTRASONIC ------------- distance in CM 51 | * NXT_LIGHT_ON -------------- reflected light 52 | * NXT_LIGHT_OFF -------------- ambient light 53 | * NXT_COLOR_RED -------------- red reflected light 54 | * NXT_COLOR_GREEN ------------ green reflected light 55 | * NXT_COLOR_BLUE ------------- blue reflected light 56 | * NXT_COLOR_OFF -------------- ambient light 57 | * EV3_GYRO_ABS --------------- absolute rotation position in degrees 58 | * EV3_GYRO_DPS --------------- rotation rate in degrees per second 59 | * EV3_COLOR_REFLECTED -------- red reflected light 60 | * EV3_COLOR_AMBIENT ---------- ambient light 61 | * EV3_COLOR_COLOR ------------ detected color 62 | * EV3_ULTRASONIC_CM ---------- distance in CM 63 | * EV3_ULTRASONIC_INCHES ------ distance in inches 64 | * EV3_ULTRASONIC_LISTEN ------ 0 or 1 (no other ultrasonic sensors or another ultrasonic sensor detected) 65 | * EV3_INFRARED_PROXIMITY ----- distance 0-100% 66 | * 67 | * The following sensor types each return a list of values 68 | * CUSTOM --------------------- Pin 1 ADC (5v scale from 0 to 4095), Pin 6 ADC (3.3v scale from 0 to 4095), Pin 5 digital, Pin 6 digital 69 | * I2C ------------------------ the I2C bytes read 70 | * NXT_COLOR_FULL ------------- detected color, red light reflected, green light reflected, blue light reflected, ambient light 71 | * EV3_GYRO_ABS_DPS ----------- absolute rotation position in degrees, rotation rate in degrees per second 72 | * EV3_COLOR_RAW_REFLECTED ---- red reflected light, unknown value (maybe a raw ambient value?) 73 | * EV3_COLOR_COLOR_COMPONENTS - red reflected light, green reflected light, blue reflected light, unknown value (maybe a raw value?) 74 | * EV3_INFRARED_SEEK ---------- a list for each of the four channels. For each channel heading (-25 to 25), distance (-128 or 0 to 100) 75 | * EV3_INFRARED_REMOTE -------- a list for each of the four channels. For each channel red up, red down, blue up, blue down, boadcast 76 | * 77 | * @return {Promise.>} 78 | */ 79 | async getValue() { 80 | return this.BP.get_sensor(this.port, this.timeLimit); 81 | } 82 | 83 | /** 84 | * Waits for a simple sensor to become a given value. Works with all sensors, which return a scalar value (and not an 85 | * array). If the sensor returns the value, the promise is resolved. 86 | * 87 | * @param {number} value 88 | * @return {Promise} 89 | */ 90 | async waitFor(value) { 91 | while (true) { 92 | await sleep(10); 93 | 94 | let currentValue = await this.BP.get_sensor(this.port, this.timeLimit); 95 | 96 | if (currentValue === value) { 97 | return currentValue; 98 | } 99 | } 100 | } 101 | } 102 | 103 | module.exports = Sensor; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BrickPi3 package for nodejs 2 | With this package you can control your BrickPi3 with nodejs. 3 | 4 | This package has the same interface as the python library and was copied 1:1 with sligth language adjustments (bitwise operators are a bit different and the return values are promises). So just look at their examples (https://github.com/DexterInd/BrickPi3/tree/master/Software/Python/Examples) to see, how you can archive different tasks. Just remember, that you get promises back from most of the methods. 5 | 6 | If you find any bugs, please open an issue or submit a pull request. I couldn't test everything, because I only have ev3 large motors. So I need your help to know if everything else works :) 7 | 8 | ## Install 9 | ```bash 10 | npm install --save brickpi3 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```javascript 16 | const brickpi3 = require('brickpi3'); 17 | 18 | (async () => { 19 | try { 20 | let BP = new brickpi3.BrickPi3(); 21 | 22 | //Make sure to stop and free all motors and sensors when the programm exits 23 | brickpi3.utils.resetAllWhenFinished(BP); 24 | 25 | //Resetting offset position of motor A to 0 26 | let encoder = await BP.get_motor_encoder(BP.PORT_A); 27 | await BP.offset_motor_encoder(BP.PORT_A, encoder); 28 | await BP.set_motor_power(BP.PORT_A, 10); 29 | } catch (err) { 30 | console.log(err); 31 | } 32 | })(); 33 | ``` 34 | 35 | ### Utils 36 | When you need to find the extreme offsets of the motor (f.e. an arm can only get from point a to point b but not beyond), there is a helper function available like explained in this video: https://youtu.be/d0bghBZZMUg?t=1m35s 37 | 38 | ```javascript 39 | const brickpi3 = require('brickpi3'); 40 | 41 | (async () => { 42 | try { 43 | let BP = new brickpi3.BrickPi3(); 44 | brickpi3.utils.resetAllWhenFinished(BP); 45 | 46 | await brickpi3.utils.resetMotorEncoder(BP, BP.PORT_A, brickpi3.utils.RESET_MOTOR_LIMIT.MIDPOINT_LIMIT); 47 | await BP.set_motor_position(BP.PORT_A, 0); 48 | console.log("Motor should now be in the middle of its two extremes"); 49 | } catch (err) { 50 | console.log(err); 51 | } 52 | })(); 53 | ``` 54 | 55 | For easier working with motors and sensors, you can get an instance of each of them and then access their methods: 56 | ```javascript 57 | const brickpi3 = require('brickpi3'); 58 | 59 | (async () => { 60 | try { 61 | let BP = new brickpi3.BrickPi3(); 62 | brickpi3.utils.resetAllWhenFinished(BP); 63 | 64 | //Get the instance of one motor and sensor 65 | let motor = brickpi3.utils.getMotor(BP, BP.PORT_A); 66 | //getSensor has an optional third parameter which allows to overwrite the default configuration time limit 67 | let sensor = brickpi3.utils.getSensor(BP, BP.PORT_2); 68 | 69 | //First set the type of the sensor 70 | await sensor.setType(BP.SENSOR_TYPE.EV3_TOUCH); 71 | 72 | //You can also change the default configuration time limit (3000ms) for the sensor configuration after initialization 73 | sensor.setConfigurationTimeLimit(4000); 74 | 75 | //Reset the motors encoder to 0 76 | await motor.resetEncoder(); 77 | 78 | //Rotates the motor one revolution - will resolve only when finished 79 | await motor.setPosition(360); 80 | 81 | //Powers on the motor until the callback function is true (good to use with sensors f.e.) 82 | await motor.setPower(50, async () => { 83 | return await sensor.getValue() === 1; 84 | }); 85 | } catch (err) { 86 | console.log(err); 87 | } 88 | })(); 89 | ``` 90 | 91 | If you want to calculate, what gear ratio some connected gears have, there is also a helper: 92 | ```javascript 93 | const brickpi3 = require('brickpi3'); 94 | 95 | //A 8-teeth-gear drives a 24-teeth-gear, which is connected to another 8-teeth-gear, which drives another 24-teeth-gear 96 | console.log(new brickpi3.utils.Gear(8).drive(24).connect(8).drive(24).getFactor()); 97 | // => 0.111 (so if you rotate the initial 8-teeth-gear one evolution, the last 24-teeth-gear will rotate 0.111 rounds in the same direction) 98 | ``` 99 | ![](https://raw.githubusercontent.com/bywulf/brickpi3-nodejs/master/docs/gearExplanation.gif) 100 | 101 | ### BrickPi3 Stacking 102 | If you have multiple brickPi3's, you can stack them and control even more motors/sensors with a single raspberry pi. 103 | 104 | First attach each brickPi separatly and execute the following script. You need to write down the id of each brickPi. 105 | 106 | ```javascript 107 | const brickpi3 = require('brickpi3'); 108 | 109 | (async () => { 110 | try { 111 | let BP = new brickpi3.BrickPi3(); 112 | console.log(await BP.get_id()); 113 | } catch (err) { 114 | console.log(err); 115 | } 116 | })(); 117 | ``` 118 | 119 | Then you can set the address for each of your brickPi's in the initializing part and access the four sensors and motors with each brickPi object instance. 120 | 121 | ```javascript 122 | const brickpi3 = require('brickpi3'); 123 | 124 | (async () => { 125 | try { 126 | await brickpi3.set_address(1, 'A778704A514D355934202020FF110722'); 127 | await brickpi3.set_address(2, 'DF9E6AC3514D355934202020FF112718'); 128 | 129 | const BP1 = new brickpi3.BrickPi3(1); 130 | const BP2 = new brickpi3.BrickPi3(2); 131 | 132 | brickpi3.utils.resetAllWhenFinished(BP1); 133 | brickpi3.utils.resetAllWhenFinished(BP2); 134 | 135 | const motor1 = brickpi3.utils.getMotor(BP1, BP1.PORT_A); 136 | const motor2 = brickpi3.utils.getMotor(BP2, BP2.PORT_A); 137 | 138 | //Reset Motor A offset of your first brickPi 139 | await motor1.resetEncoder(); 140 | 141 | //Reset Motor A offset of your second brickPi 142 | await motor2.resetEncoder(); 143 | 144 | //Let the motor A of your first brickPi turn constantly 145 | await motor1.setPower(10); 146 | 147 | //Let the motor A of your second brickPi rotate by 45° every two seconds. 148 | let target = 0; 149 | const moveOn = async () => { 150 | target = target + 45; 151 | await motor2.setPosition(target); 152 | 153 | setTimeout(moveOn, 2000); 154 | }; 155 | moveOn(); 156 | 157 | } catch(err) { 158 | console.log(err); 159 | } 160 | })(); 161 | ``` 162 | 163 | ## Special thanks 164 | Thanks to Sumit Kumar (http://twitter.com/tweetsofsumit) for providing an example on how to communicate with the brickPi3 in nodejs on his repository. 165 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | const sleep = require('es7-sleep'); 2 | const Motor = require('./objects/motor'); 3 | const Sensor = require('./objects/sensor'); 4 | 5 | const RESET_MOTOR_LIMIT = { 6 | CURRENT_POSITION: 1, 7 | FORWARD_LIMIT: 2, 8 | BACKWARD_LIMIT: 3, 9 | MIDPOINT_LIMIT: 4 10 | }; 11 | 12 | /** 13 | * Resets the motors offset encoder to the given type of offset. 14 | * 15 | * Valid limit values: 16 | * * RESET_MOTOR_LIMIT.CURRENT_POSITION: The current position becomes the 0-point. 17 | * * RESET_MOTOR_LIMIT.FORWARD_LIMIT: It rotates forward until it detects an obstacle and uses this as the new 0-point. 18 | * * RESET_MOTOR_LIMIT.BACKWARD_LIMIT: It rotates backward until it detects an obstacle and uses this as the new 0-point. 19 | * * RESET_MOTOR_LIMIT.MIDPOINT_LIMIT: It first rotates forwards and searches for an obstacle, then rotates backward and 20 | * searches for an obstacle. The middle between those two extreme points becomes the new 0-point. 21 | * 22 | * @param {Object} brickPiInstance Instance of the BrickPi class in which the motor is plugged in 23 | * @param {number} motorPort The port of the motor to be manipulated (brickPiInstance.PORT_A, .PORT_B, .PORT_C, .PORT_D) 24 | * @param {number} limitType Search for this point. See above description. 25 | * @param {number} newOffset Set the found point to this offset value. 26 | * @param {number} maxPower When the power of the motor drops below this value, it is considered to be an obstacle. (WARNING: power is currently "dps", because I couldn't find a way to get the current power of the motor.. get_motor_status states that it returnes the power, but instead returnes the speed :/) 27 | * @param {number} timeLimit If no obstacle is found within this time limit, the promise gets rejected. 28 | * @param {number} motorPower Power of the motor (lower for smoother finding) 29 | * @return {Promise} When the new offset is set, the promise resolves. 30 | */ 31 | const resetMotorEncoder = async (brickPiInstance, motorPort, limitType = RESET_MOTOR_LIMIT.CURRENT_POSITION, newOffset = 0, maxPower = 25, timeLimit = 10000, motorPower = 100) => { 32 | let startTime = Date.now(); 33 | const checkPower = async () => { 34 | while (Date.now() - startTime <= timeLimit) { 35 | await sleep(20); 36 | 37 | let status = await brickPiInstance.get_motor_status(motorPort); 38 | if (Math.abs(status[3]) <= maxPower) { 39 | await brickPiInstance.set_motor_power(motorPort, 0); 40 | return status[2]; 41 | } 42 | } 43 | 44 | await brickPiInstance.set_motor_power(motorPort, 0); 45 | throw new Error('resetMotorEncoder: timeLimit exceeded'); 46 | }; 47 | 48 | if (limitType === RESET_MOTOR_LIMIT.CURRENT_POSITION) { 49 | let offset = await brickPiInstance.get_motor_encoder(motorPort); 50 | await brickPiInstance.offset_motor_encoder(motorPort, offset - newOffset); 51 | 52 | } else if (limitType === RESET_MOTOR_LIMIT.FORWARD_LIMIT || limitType === RESET_MOTOR_LIMIT.BACKWARD_LIMIT) { 53 | let power = motorPower; 54 | if (limitType === RESET_MOTOR_LIMIT.BACKWARD_LIMIT) power = -motorPower; 55 | 56 | await brickPiInstance.set_motor_power(motorPort, power); 57 | let offset = await checkPower(); 58 | await brickPiInstance.offset_motor_encoder(motorPort, offset - newOffset); 59 | 60 | } else if (limitType === RESET_MOTOR_LIMIT.MIDPOINT_LIMIT) { 61 | await brickPiInstance.set_motor_power(motorPort, motorPower); 62 | let offsetForward = await checkPower(); 63 | 64 | await brickPiInstance.set_motor_power(motorPort, -motorPower); 65 | let offsetBackward = await checkPower(); 66 | 67 | await brickPiInstance.offset_motor_encoder(motorPort, offsetBackward + (offsetForward - offsetBackward) / 2 - newOffset); 68 | } else { 69 | throw new Error('resetMotorEncoder: Invalid limitType.'); 70 | } 71 | }; 72 | 73 | let resetBrickPis = []; 74 | let shutdownHandlerRegistered = false; 75 | const resetAllWhenFinished = (brickPiInstance) => { 76 | if (!shutdownHandlerRegistered) { 77 | shutdownHandlerRegistered = true; 78 | 79 | async function exitHandler(err) { 80 | if (err) console.log(err.stack); 81 | 82 | for (let i = 0; i < resetBrickPis.length; i++) { 83 | await resetBrickPis[i].reset_all(); 84 | } 85 | 86 | process.exit(); 87 | } 88 | 89 | process.stdin.resume(); 90 | process.on('exit', exitHandler); 91 | process.on('SIGINT', exitHandler); 92 | process.on('uncaughtException', exitHandler); 93 | } 94 | 95 | resetBrickPis.push(brickPiInstance); 96 | }; 97 | 98 | /** 99 | * Sets the motors position and resolves, when the final position is reached 100 | * @deprecated Will be removed in 1.0.0. Use brickpi3.utils.getMotor(BP, port).setPosition(targetPosition); 101 | * 102 | * @param brickPiInstance 103 | * @param motorPort 104 | * @param targetPosition 105 | * @return {Promise} 106 | */ 107 | const setMotorPosition = async (brickPiInstance, motorPort, targetPosition) => { 108 | await brickPiInstance.set_motor_position(motorPort, targetPosition); 109 | 110 | let lastEncoder = null; 111 | while (true) { 112 | await sleep(20); 113 | 114 | let encoder = await brickPiInstance.get_motor_encoder(motorPort); 115 | 116 | if (lastEncoder !== null && lastEncoder === encoder) { 117 | return; 118 | } 119 | lastEncoder = encoder; 120 | } 121 | }; 122 | 123 | /** 124 | * Waits for a simple sensor to become a given value. Works with all sensors, which return a scalar value (and not an 125 | * array). If the sensor returns the value, the promise is resolved. 126 | * @deprecated Will be removed in 1.0.0. Use brickpi3.utils.getSensor(BP, BP.PORT_1).waitFor(1); 127 | * 128 | * @param {Object} brickPiInstance 129 | * @param {*} sensorPort 130 | * @param {number} targetValue 131 | * @param {number} timeLimit 132 | * @return {Promise} 133 | */ 134 | const waitForSensor = async (brickPiInstance, sensorPort, targetValue, timeLimit = 10000) => { 135 | let startTime = Date.now(); 136 | 137 | while (Date.now() - startTime <= timeLimit) { 138 | await sleep(10); 139 | 140 | let value = await brickPiInstance.get_sensor(sensorPort, timeLimit); 141 | if (value === targetValue) { 142 | return; 143 | } 144 | } 145 | 146 | throw new Error('waitForSensor: timeLimit exceeded'); 147 | }; 148 | 149 | const getMotor = (brickPiInstance, motorPort) => { 150 | return new Motor(brickPiInstance, motorPort); 151 | }; 152 | 153 | const getSensor = (brickPiInstance, sensorPort, timeLimit=null) => { 154 | return new Sensor(brickPiInstance, sensorPort, timeLimit); 155 | }; 156 | 157 | const Gear = function(teeth) { 158 | this._joinedParent = null; 159 | this._drivenParent = null; 160 | 161 | /** 162 | * 163 | * @param {number|Gear} teethOrGear 164 | * @return {Gear} 165 | */ 166 | this.drive = (teethOrGear) => { 167 | if(!(teethOrGear instanceof Gear)) { 168 | teethOrGear = new Gear(teethOrGear); 169 | } 170 | 171 | teethOrGear._drivenParent = this; 172 | 173 | return teethOrGear; 174 | }; 175 | 176 | /** 177 | * 178 | * @param {number|Gear} teethOrGear 179 | * @return {Gear} 180 | */ 181 | this.connect = (teethOrGear) => { 182 | if(!(teethOrGear instanceof Gear)) { 183 | teethOrGear = new Gear(teethOrGear); 184 | } 185 | 186 | teethOrGear._joinedParent = this; 187 | 188 | return teethOrGear; 189 | }; 190 | 191 | /** 192 | * @return {number} 193 | */ 194 | this.getTeeth = () => { 195 | return teeth; 196 | }; 197 | 198 | /** 199 | * @return {number} 200 | */ 201 | this.getFactor = () => { 202 | let currentGear = this; 203 | let currentFactor = 1; 204 | 205 | while (currentGear._joinedParent !== null || currentGear._drivenParent !== null) { 206 | if (currentGear._drivenParent !== null) { 207 | currentFactor *= -1 * currentGear._drivenParent.getTeeth() / currentGear.getTeeth(); 208 | currentGear = currentGear._drivenParent; 209 | } else { 210 | currentGear = currentGear._joinedParent; 211 | } 212 | } 213 | 214 | return currentFactor; 215 | } 216 | }; 217 | 218 | module.exports = { 219 | RESET_MOTOR_LIMIT: RESET_MOTOR_LIMIT, 220 | resetMotorEncoder: resetMotorEncoder, 221 | resetAllWhenFinished: resetAllWhenFinished, 222 | setMotorPosition: setMotorPosition, 223 | waitForSensor: waitForSensor, 224 | getMotor: getMotor, 225 | getSensor: getSensor, 226 | Gear: Gear 227 | }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const SPI = require('pi-spi'); 2 | const FIRMWARE_VERSION_REQUIRED = "1.4.x"; // Make sure the top 2 of 3 numbers match 3 | 4 | const spi = SPI.initialize('/dev/spidev0.1'); 5 | spi.clockSpeed(500000); 6 | 7 | class SensorError extends Error { 8 | constructor(message) { 9 | super(message); 10 | } 11 | } 12 | 13 | class IOError extends Error { 14 | constructor(message) { 15 | super(message); 16 | } 17 | } 18 | 19 | const BPSPI_MESSAGE_TYPE = { 20 | NONE: 0, 21 | 22 | GET_MANUFACTURER : 1, 23 | GET_NAME: 2, 24 | GET_HARDWARE_VERSION: 3, 25 | GET_FIRMWARE_VERSION: 4, 26 | GET_ID: 5, 27 | SET_LED: 6, 28 | GET_VOLTAGE_3V3: 7, 29 | GET_VOLTAGE_5V: 8, 30 | GET_VOLTAGE_9V: 9, 31 | GET_VOLTAGE_VCC: 10, 32 | SET_ADDRESS: 11, 33 | 34 | SET_SENSOR_TYPE: 12, 35 | 36 | GET_SENSOR_1: 13, 37 | GET_SENSOR_2: 14, 38 | GET_SENSOR_3: 15, 39 | GET_SENSOR_4: 16, 40 | 41 | I2C_TRANSACT_1: 17, 42 | I2C_TRANSACT_2: 18, 43 | I2C_TRANSACT_3: 19, 44 | I2C_TRANSACT_4: 20, 45 | 46 | SET_MOTOR_POWER: 21, 47 | 48 | SET_MOTOR_POSITION: 22, 49 | 50 | SET_MOTOR_POSITION_KP: 23, 51 | 52 | SET_MOTOR_POSITION_KD: 24, 53 | 54 | SET_MOTOR_DPS: 25, 55 | 56 | SET_MOTOR_DPS_KP: 26, 57 | 58 | SET_MOTOR_DPS_KD: 27, 59 | 60 | SET_MOTOR_LIMITS: 28, 61 | 62 | OFFSET_MOTOR_ENCODER: 29, 63 | 64 | GET_MOTOR_A_ENCODER: 30, 65 | GET_MOTOR_B_ENCODER: 31, 66 | GET_MOTOR_C_ENCODER: 32, 67 | GET_MOTOR_D_ENCODER: 33, 68 | 69 | GET_MOTOR_A_STATUS: 34, 70 | GET_MOTOR_B_STATUS: 35, 71 | GET_MOTOR_C_STATUS: 36, 72 | GET_MOTOR_D_STATUS: 37, 73 | }; 74 | 75 | /** 76 | * Set the SPI address of the BrickPi3 77 | * 78 | * @param {number} address the new SPI address to use (1 to 255) 79 | * @param {string} id the BrickPi3's unique serial number ID (so that the address can be set while multiple BrickPi3s are stacked on a Raspberry Pi). 80 | * @return {Promise} 81 | */ 82 | function set_address(address, id) { 83 | return new Promise((resolve, reject) => { 84 | 85 | address = parseInt(address); 86 | if (typeof id === 'undefined') id = ''; 87 | 88 | if (address < 1 || address > 255) { 89 | throw new IOError('brickpi3.set_address error: SPI address must be in the range of 1 to 255') 90 | } 91 | 92 | let id_arr; 93 | if (id.length !== 32) { 94 | if (id === '') { 95 | id = '00000000000000000000000000000000'; 96 | } else { 97 | throw new IOError('brickpi3.set_address error: wrong serial number id length. Must be a 32-digit hex string.'); 98 | } 99 | } 100 | 101 | // noinspection JSCheckFunctionSignatures 102 | id_arr = Buffer.from(id, "hex"); 103 | if (id_arr.byteLength !== 16) { 104 | throw new IOError('brickpi3.set_address error: unknown serial number id problem. Make sure to use a valid 32-digit hex string serial number.'); 105 | } 106 | 107 | const buffer = Buffer.from([0, BPSPI_MESSAGE_TYPE.SET_ADDRESS, address, ...id_arr]); 108 | spi.transfer(buffer, (e, responseBuffer) => { 109 | if (e) { 110 | reject(e); 111 | } 112 | 113 | resolve(responseBuffer); 114 | }); 115 | }); 116 | } 117 | 118 | /** 119 | * Do any necessary configuration, and optionally detect the BrickPi3 120 | * 121 | * Optionally specify the SPI address as something other than 1 122 | * Optionally disable the detection of the BrickPi3 hardware. This can be used for debugging and testing when the BrickPi3 would otherwise not pass the detection tests. 123 | * 124 | * @param address 125 | * @constructor 126 | */ 127 | function BrickPi3(address = 1) { 128 | this.PORT_1 = 0x01; 129 | this.PORT_2 = 0x02; 130 | this.PORT_3 = 0x04; 131 | this.PORT_4 = 0x08; 132 | 133 | this.PORT_A = 0x01; 134 | this.PORT_B = 0x02; 135 | this.PORT_C = 0x04; 136 | this.PORT_D = 0x08; 137 | 138 | this.MOTOR_FLOAT = -128; 139 | 140 | this.SensorType = [0, 0, 0, 0]; 141 | this.I2CInBytes = [0, 0, 0, 0]; 142 | 143 | this.BPSPI_MESSAGE_TYPE = BPSPI_MESSAGE_TYPE; 144 | 145 | this.SENSOR_TYPE = { 146 | NONE: 1, 147 | I2C: 2, 148 | CUSTOM: 3, 149 | 150 | TOUCH: 4, 151 | NXT_TOUCH: 5, 152 | EV3_TOUCH: 6, 153 | 154 | NXT_LIGHT_ON: 7, 155 | NXT_LIGHT_OFF: 8, 156 | 157 | NXT_COLOR_RED: 9, 158 | NXT_COLOR_GREEN: 10, 159 | NXT_COLOR_BLUE: 11, 160 | NXT_COLOR_FULL: 12, 161 | NXT_COLOR_OFF: 13, 162 | 163 | NXT_ULTRASONIC: 14, 164 | 165 | EV3_GYRO_ABS: 15, 166 | EV3_GYRO_DPS: 16, 167 | EV3_GYRO_ABS_DPS: 17, 168 | 169 | EV3_COLOR_REFLECTED: 18, 170 | EV3_COLOR_AMBIENT: 19, 171 | EV3_COLOR_COLOR: 20, 172 | EV3_COLOR_RAW_REFLECTED: 21, 173 | EV3_COLOR_COLOR_COMPONENTS: 22, 174 | 175 | EV3_ULTRASONIC_CM: 23, 176 | EV3_ULTRASONIC_INCHES: 24, 177 | EV3_ULTRASONIC_LISTEN: 25, 178 | 179 | EV3_INFRARED_PROXIMITY: 26, 180 | EV3_INFRARED_SEEK: 27, 181 | EV3_INFRARED_REMOTE: 28, 182 | }; 183 | 184 | this.SENSOR_STATE = { 185 | VALID_DATA: 0, 186 | NOT_CONFIGURED: 1, 187 | CONFIGURING: 2, 188 | NO_DATA: 3, 189 | I2C_ERROR: 4 190 | }; 191 | 192 | // noinspection JSUnusedGlobalSymbols 193 | /** 194 | * Flags for use with SENSOR_TYPE.CUSTOM 195 | * 196 | * PIN1_9V 197 | * Enable 9V out on pin 1 (for LEGO NXT Ultrasonic sensor). 198 | * 199 | * PIN5_OUT 200 | * Set pin 5 state to output. Pin 5 will be set to input if this flag is not set. 201 | * 202 | * PIN5_STATE 203 | * If PIN5_OUT is set, this will set the state to output high, otherwise the state will 204 | * be output low. If PIN5_OUT is not set, this flag has no effect. 205 | * 206 | * PIN6_OUT 207 | * Set pin 6 state to output. Pin 6 will be set to input if this flag is not set. 208 | * 209 | * PIN6_STATE 210 | * If PIN6_OUT is set, this will set the state to output high, otherwise the state will 211 | * be output low. If PIN6_OUT is not set, this flag has no effect. 212 | * 213 | * PIN1_ADC 214 | * Enable the analog/digital converter on pin 1 (e.g. for NXT analog sensors). 215 | * 216 | * PIN6_ADC 217 | * Enable the analog/digital converter on pin 6. 218 | * 219 | * @type {{PIN1_9V: number, PIN5_OUT: number, PIN5_STATE: number, PIN6_OUT: number, PIN6_STATE: number, PIN1_ADC: number, PIN6_ADC: number}} 220 | */ 221 | this.SENSOR_CUSTOM = { 222 | PIN1_9V: 0x0002, 223 | PIN5_OUT: 0x0010, 224 | PIN5_STATE: 0x0020, 225 | PIN6_OUT: 0x0100, 226 | PIN6_STATE: 0x0200, 227 | PIN1_ADC: 0x1000, 228 | PIN6_ADC: 0x4000 229 | }; 230 | 231 | this.SENSOR_I2C_SETTINGS = { 232 | MID_CLOCK: 0x01, // Send the clock pulse between reading and writing. Required by the NXT US sensor. 233 | PIN1_9V: 0x02, // 9v pullup on pin 1 234 | SAME: 0x04, // Keep performing the same transaction e.g. keep polling a sensor 235 | ALLOW_STRETCH_ACK: 3, 236 | ALLOW_STRETCH_ANY: 4 237 | }; 238 | 239 | // noinspection JSUnusedGlobalSymbols 240 | this.MOTOR_STATUS_FLAG = { 241 | LOW_VOLTAGE_FLOAT: 0x01, //If the motors are floating due to low battery voltage 242 | OVERLOADED: 0x02, // If the motors aren't close to the target (applies to position control and dps speed control). 243 | }; 244 | 245 | if (address < 1 || address > 255) { 246 | throw new IOError('error: SPI address must be in the range of 1 to 255'); 247 | } 248 | this.SPI_Address = address; 249 | 250 | // noinspection JSUnusedGlobalSymbols 251 | this.detect = () => { 252 | return new Promise((resolve, reject) => { 253 | let manufacturer, board, vfw; 254 | this.get_manufacturer().then((value) => { 255 | manufacturer = value; 256 | return this.get_board(); 257 | }).then((value) => { 258 | board = value; 259 | return this.get_version_firmware(); 260 | }).then((value) => { 261 | vfw = value; 262 | 263 | if (manufacturer !== 'Dexter Industries' || board !== 'BrickPi3') { 264 | reject('No SPI response'); 265 | } else if (vfw.split('.')[0] !== FIRMWARE_VERSION_REQUIRED.split('.')[0] || vfw.split('.')[1] !== FIRMWARE_VERSION_REQUIRED.split('.')[0]) { 266 | reject('BrickPi3 firmware needs to be version ' + FIRMWARE_VERSION_REQUIRED + ' but is currently version ' + vfw); 267 | } else { 268 | resolve(true); 269 | } 270 | }); 271 | }); 272 | }; 273 | 274 | // noinspection JSUnusedGlobalSymbols 275 | /** 276 | * Conduct a SPI transaction 277 | * 278 | * @param {Array} data_out a list of bytes to send. The length of the list will determine how many bytes are transferred. 279 | * @return {Promise.} 280 | */ 281 | this.spi_transfer_array = (data_out) => { 282 | return new Promise((resolve, reject) => { 283 | spi.transfer(Buffer.from(data_out), (e, responseBuffer) => { 284 | if (e) { 285 | reject(e); 286 | } 287 | 288 | let responseArray = [...responseBuffer]; 289 | resolve(responseArray); 290 | }); 291 | }); 292 | }; 293 | 294 | // noinspection JSUnusedGlobalSymbols 295 | /** 296 | * Send an 8-bit-value over SPI 297 | * 298 | * @param {number} MessageType 299 | * @param {number} Value 300 | * @return {Promise} 301 | */ 302 | this.spi_write_8 = (MessageType, Value) => { 303 | return new Promise((resolve, reject) => { 304 | this.spi_transfer_array([this.SPI_Address, MessageType, (Value & 0xFF)]).then(() => { 305 | resolve(); 306 | }).catch((err) => { 307 | reject(err); 308 | }); 309 | }); 310 | }; 311 | 312 | // noinspection JSUnusedGlobalSymbols 313 | /** 314 | * Read a 16-bit value over SPI 315 | * 316 | * @param {number.} MessageType 317 | * @return {Promise.} 318 | */ 319 | this.spi_read_16 = (MessageType) => { 320 | return new Promise((resolve, reject) => { 321 | this.spi_transfer_array([this.SPI_Address, MessageType, 0, 0, 0, 0]).then((reply) => { 322 | if (reply[3] === 0xA5) { 323 | resolve(parseInt((reply[4] << 8) | reply[5])); 324 | } else { 325 | reject('No SPI response'); 326 | } 327 | }).catch((err) => { 328 | reject(err); 329 | }); 330 | }); 331 | }; 332 | 333 | // noinspection JSUnusedGlobalSymbols 334 | /** 335 | * Send an 16-bit-value over SPI 336 | * 337 | * @param {number} MessageType 338 | * @param {number} Value 339 | * @return {Promise} 340 | */ 341 | this.spi_write_16 = (MessageType, Value) => { 342 | return new Promise((resolve, reject) => { 343 | this.spi_transfer_array([this.SPI_Address, MessageType, ((Value >> 8) & 0xFF), (Value & 0xFF)]).then(() => { 344 | resolve(); 345 | }).catch((err) => { 346 | reject(err); 347 | }); 348 | }); 349 | }; 350 | 351 | // noinspection JSUnusedGlobalSymbols 352 | /** 353 | * Send an 24-bit-value over SPI 354 | * 355 | * @param {number} MessageType 356 | * @param {number} Value 357 | * @return {Promise} 358 | */ 359 | this.spi_write_24 = (MessageType, Value) => { 360 | return new Promise((resolve, reject) => { 361 | this.spi_transfer_array([this.SPI_Address, MessageType, ((Value >> 16) & 0xFF), ((Value >> 8) & 0xFF), (Value & 0xFF)]).then(() => { 362 | resolve(); 363 | }).catch((err) => { 364 | reject(err); 365 | }); 366 | }); 367 | }; 368 | 369 | // noinspection JSUnusedGlobalSymbols 370 | /** 371 | * Read a 32-bit value over SPI 372 | * 373 | * @param {number.} MessageType 374 | * @return {Promise.} 375 | */ 376 | this.spi_read_32 = (MessageType) => { 377 | return new Promise((resolve, reject) => { 378 | this.spi_transfer_array([this.SPI_Address, MessageType, 0, 0, 0, 0, 0, 0]).then((reply) => { 379 | if (reply[3] === 0xA5) { 380 | resolve(parseInt((reply[4] << 24) | (reply[5] << 16) | (reply[6] << 8) | reply[7])); 381 | } else { 382 | reject('No SPI response'); 383 | } 384 | }).catch((err) => { 385 | reject(err); 386 | }); 387 | }); 388 | }; 389 | 390 | // noinspection JSUnusedGlobalSymbols 391 | /** 392 | * Send an 32-bit-value over SPI 393 | * 394 | * @param {number} MessageType 395 | * @param {number} Value 396 | * @return {Promise} 397 | */ 398 | this.spi_write_32 = (MessageType, Value) => { 399 | return new Promise((resolve, reject) => { 400 | this.spi_transfer_array([this.SPI_Address, MessageType, ((Value >> 24) & 0xFF), ((Value >> 16) & 0xFF), ((Value >> 8) & 0xFF), (Value & 0xFF)]).then(() => { 401 | resolve(); 402 | }).catch((err) => { 403 | reject(err); 404 | }); 405 | }); 406 | }; 407 | 408 | // noinspection JSUnusedGlobalSymbols 409 | /** 410 | * Read the 20 character BrickPi3 manufacturer name 411 | * 412 | * @return {Promise.} BrickPi3 manufacturer name string 413 | */ 414 | this.get_manufacturer = () => { 415 | return new Promise((resolve, reject) => { 416 | this.spi_transfer_array([this.SPI_Address, this.BPSPI_MESSAGE_TYPE.GET_MANUFACTURER, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).then((reply) => { 417 | if (reply[3] === 0xA5) { 418 | let name = ''; 419 | for (let i = 4; i <= 24; i++) { 420 | if (reply[i] === 0x00) break; 421 | 422 | name += String.fromCharCode(reply[i]); 423 | } 424 | resolve(name); 425 | } 426 | reject('No SPI response'); 427 | }).catch((err) => { 428 | reject(err); 429 | }); 430 | }); 431 | }; 432 | 433 | // noinspection JSUnusedGlobalSymbols 434 | /** 435 | * Read the 20 character BrickPi3 board name 436 | * 437 | * @return {Promise.} BrickPi3 board name string 438 | */ 439 | this.get_board = () => { 440 | return new Promise((resolve, reject) => { 441 | this.spi_transfer_array([this.SPI_Address, this.BPSPI_MESSAGE_TYPE.GET_NAME, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).then((reply) => { 442 | if (reply[3] === 0xA5) { 443 | let name = ''; 444 | for (let i = 4; i <= 24; i++) { 445 | if (reply[i] === 0x00) break; 446 | 447 | name += String.fromCharCode(reply[i]); 448 | } 449 | resolve(name); 450 | } 451 | reject('No SPI response'); 452 | }).catch((err) => { 453 | reject(err); 454 | }); 455 | }); 456 | }; 457 | 458 | // noinspection JSUnusedGlobalSymbols 459 | /** 460 | * Read the hardware version 461 | * 462 | * @return {Promise.} hardware version 463 | */ 464 | this.get_version_hardware = () => { 465 | return new Promise((resolve, reject) => { 466 | this.spi_read_32(this.BPSPI_MESSAGE_TYPE.GET_HARDWARE_VERSION).then((version) => { 467 | resolve(Math.round(version / 1000000) + '.' + (Math.round(version / 1000) % 1000) + '.' + (version % 1000)); 468 | }).catch((err) => { 469 | reject(err); 470 | }); 471 | }); 472 | }; 473 | 474 | // noinspection JSUnusedGlobalSymbols 475 | /** 476 | * Read the firmware version 477 | * 478 | * @return {Promise.} firmware version 479 | */ 480 | this.get_version_firmware = () => { 481 | return new Promise((resolve, reject) => { 482 | this.spi_read_32(this.BPSPI_MESSAGE_TYPE.GET_FIRMWARE_VERSION).then((version) => { 483 | resolve(Math.round(version / 1000000) + '.' + (Math.round(version / 1000) % 1000) + '.' + (version % 1000)); 484 | }).catch((err) => { 485 | reject(err); 486 | }); 487 | }); 488 | }; 489 | 490 | // noinspection JSUnusedGlobalSymbols 491 | /** 492 | * Read the 128-bit BrickPi hardware serial number 493 | * 494 | * @return {Promise.} serial number as 32 char HEX formatted string 495 | */ 496 | this.get_id = () => { 497 | return new Promise((resolve, reject) => { 498 | this.spi_transfer_array([this.SPI_Address, this.BPSPI_MESSAGE_TYPE.GET_ID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).then((reply) => { 499 | if (reply[3] === 0xA5) { 500 | let id = ''; 501 | for (let i = 4; i <= 19; i++) { 502 | const char = reply[i].toString(16); 503 | id += (char.length < 2 ? '0' : '') + char; 504 | } 505 | resolve(id); 506 | } 507 | reject('No SPI response'); 508 | }).catch((err) => { 509 | reject(err); 510 | }); 511 | }); 512 | }; 513 | 514 | // noinspection JSUnusedGlobalSymbols 515 | /** 516 | * Control the onboard LED 517 | * 518 | * @param {number} value the value (in percent) to set the LED brightness to. -1 returns control of the LED to the firmware. 519 | * @return {Promise} 520 | */ 521 | this.set_led = (value) => { 522 | return this.spi_write_8(this.BPSPI_MESSAGE_TYPE.SET_LED, value); 523 | }; 524 | 525 | // noinspection JSUnusedGlobalSymbols 526 | /** 527 | * Get the 3.3v circuit voltage 528 | * 529 | * @return {Promise.} 3.3v circuit voltage 530 | */ 531 | this.get_voltage_3v3 = () => { 532 | return new Promise((resolve, reject) => { 533 | this.spi_read_16(this.BPSPI_MESSAGE_TYPE.GET_VOLTAGE_3V3).then((value) => { 534 | return value / 1000; 535 | }).catch((err) => { 536 | reject(err); 537 | }); 538 | }); 539 | }; 540 | 541 | // noinspection JSUnusedGlobalSymbols 542 | /** 543 | * Get the 5v circuit voltage 544 | * 545 | * @return {Promise.} 5v circuit voltage 546 | */ 547 | this.get_voltage_5v = () => { 548 | return new Promise((resolve, reject) => { 549 | this.spi_read_16(this.BPSPI_MESSAGE_TYPE.GET_VOLTAGE_5V).then((value) => { 550 | return value / 1000; 551 | }).catch((err) => { 552 | reject(err); 553 | }); 554 | }); 555 | }; 556 | 557 | // noinspection JSUnusedGlobalSymbols 558 | /** 559 | * Get the 9v circuit voltage 560 | * 561 | * @return {Promise.} 9v circuit voltage 562 | */ 563 | this.get_voltage_9v = () => { 564 | return new Promise((resolve, reject) => { 565 | this.spi_read_16(this.BPSPI_MESSAGE_TYPE.GET_VOLTAGE_9V).then((value) => { 566 | return value / 1000; 567 | }).catch((err) => { 568 | reject(err); 569 | }); 570 | }); 571 | }; 572 | 573 | // noinspection JSUnusedGlobalSymbols 574 | /** 575 | * Get the battery voltage 576 | * 577 | * @return {Promise.} battery voltage 578 | */ 579 | this.get_voltage_battery = () => { 580 | return new Promise((resolve, reject) => { 581 | this.spi_read_16(this.BPSPI_MESSAGE_TYPE.GET_VOLTAGE_VCC).then((value) => { 582 | return value / 1000; 583 | }).catch((err) => { 584 | reject(err); 585 | }); 586 | }); 587 | }; 588 | 589 | this.wait_until_configuration_is_finished = (args, configurationTimeout) => { 590 | 591 | configurationTimeout = configurationTimeout || 3000; 592 | 593 | return new Promise(async (resolve) => { 594 | 595 | let run = true; 596 | 597 | const timeout = setTimeout( () => { 598 | const msg = `timeout reached: sensor configuration not successfully within ${configurationTimeout}ms`; 599 | run = false; 600 | throw new Error(msg); 601 | }, configurationTimeout); 602 | 603 | while(run) { 604 | const reply = await this.spi_transfer_array(args); 605 | if(reply[5] === this.SENSOR_STATE.VALID_DATA){ 606 | clearTimeout(timeout); 607 | return resolve(reply); 608 | } 609 | } 610 | 611 | }); 612 | }; 613 | 614 | // noinspection JSUnusedGlobalSymbols 615 | /** 616 | * Set the sensor type. 617 | * 618 | * params is used for the following sensor types: 619 | * CUSTOM -- a 16-bit integer used to configure the hardware. 620 | * I2C -- a list of settings: 621 | * params[0] -- Settings/flags 622 | * params[1] -- target Speed in microseconds (0-255). Realistically the speed will vary. 623 | * if SENSOR_I2C_SETTINGS_SAME flag set in I2C Settings: 624 | * params[2] -- Delay in microseconds between transactions. 625 | * params[3] -- Address 626 | * params[4] -- List of bytes to write 627 | * params[5] -- Number of bytes to read 628 | * 629 | * @param {number} port The sensor port(s). PORT_1, PORT_2, PORT_3, and/or PORT_4. 630 | * @param {number} type The sensor type 631 | * @param {*} params the parameters needed for some sensor types. 632 | * @return {Promise} 633 | */ 634 | this.set_sensor_type = (port, type, params = 0) => { 635 | return new Promise((resolve, reject) => { 636 | for (let p = 0; p < 4; p++) { 637 | // noinspection JSBitwiseOperatorUsage 638 | if (port & (1 << p)) { 639 | this.SensorType[p] = type; 640 | } 641 | } 642 | 643 | let outArray; 644 | if (type === this.SENSOR_TYPE.CUSTOM) { 645 | outArray = [this.SPI_Address, this.BPSPI_MESSAGE_TYPE.SET_SENSOR_TYPE, parseInt(port), type, ((params[0] >> 8) & 0xFF), (params[0] & 0xFF)]; 646 | } else if (type === this.SENSOR_TYPE.I2C) { 647 | if (params instanceof Array && params.length >= 2) { 648 | outArray = [this.SPI_Address, this.BPSPI_MESSAGE_TYPE.SET_SENSOR_TYPE, parseInt(port), type, params[0], params[1]]; 649 | if (params[0] & this.SENSOR_I2C_SETTINGS.SAME && params.length >= 6) { 650 | outArray.push((params[2] >> 24) & 0xFF); 651 | outArray.push((params[2] >> 16) & 0xFF); 652 | outArray.push((params[2] >> 8) & 0xFF) ; 653 | outArray.push(params[2] & 0xFF); 654 | outArray.push(params[3] & 0xFF); 655 | outArray.push(params[5] & 0xFF); 656 | for (let p = 0; p < 4; p++) { 657 | // noinspection JSBitwiseOperatorUsage 658 | if (port & (1 << p)) { 659 | this.I2CInBytes[p] = params[5] & 0xFF; 660 | } 661 | } 662 | outArray.push(params[4].length); 663 | for (let i = 0; i < params[4].length; i++) { 664 | outArray.push(params[4][i]); 665 | } 666 | } 667 | } 668 | } else { 669 | outArray = [this.SPI_Address, this.BPSPI_MESSAGE_TYPE.SET_SENSOR_TYPE, parseInt(port), type]; 670 | } 671 | 672 | this.spi_transfer_array(outArray).then(() => { 673 | resolve(); 674 | }).catch((err) => { 675 | reject(err); 676 | }); 677 | }); 678 | }; 679 | 680 | // noinspection JSUnusedGlobalSymbols 681 | /** 682 | * Conduct an I2C transaction 683 | * 684 | * @param {number} port The sensor port (one at a time). PORT_1, PORT_2, PORT_3, or PORT_4. 685 | * @param {number} Address The I2C address for the device. Bits 1-7, not 0-6. 686 | * @param {Array.} OutArray A list of bytes to write to the device 687 | * @param {number} InBytes The number of bytes to read from the device 688 | * 689 | * @return {Promise} 690 | */ 691 | this.transact_i2c = (port, Address, OutArray, InBytes) => { 692 | return new Promise((resolve, reject) => { 693 | let message_type, port_index; 694 | if (port === this.PORT_1) { 695 | message_type = this.BPSPI_MESSAGE_TYPE.I2C_TRANSACT_1; 696 | port_index = 0; 697 | } else if (port === this.PORT_2) { 698 | message_type = this.BPSPI_MESSAGE_TYPE.I2C_TRANSACT_2; 699 | port_index = 1; 700 | } else if (port === this.PORT_3) { 701 | message_type = this.BPSPI_MESSAGE_TYPE.I2C_TRANSACT_3; 702 | port_index = 2; 703 | } else if (port === this.PORT_4) { 704 | message_type = this.BPSPI_MESSAGE_TYPE.I2C_TRANSACT_4; 705 | port_index = 3; 706 | } else { 707 | throw new IOError('transact_i2c error. Must be one sensor port at a time. PORT_1, PORT_2, PORT_3, or PORT_4.'); 708 | } 709 | 710 | if (this.SensorType[port_index] !== this.SENSOR_TYPE.I2C) { 711 | reject(); 712 | return; 713 | } 714 | 715 | let outArray = [this.SPI_Address, message_type, Address, InBytes]; 716 | this.I2CInBytes[port_index] = InBytes; 717 | let OutBytes = OutArray.length; 718 | if (OutBytes > 16) { 719 | outArray.push(16); 720 | for (let i = 0; i < 16; i++) { 721 | outArray.push(OutArray[i]); 722 | } 723 | } else { 724 | outArray.push(OutBytes); 725 | for (let i = 0; i < OutBytes; i++) { 726 | outArray.push(OutArray[i]); 727 | } 728 | } 729 | this.spi_transfer_array(outArray).then(() => { 730 | resolve(); 731 | }).catch((err) => { 732 | reject(err); 733 | }); 734 | }); 735 | }; 736 | 737 | // noinspection JSUnusedGlobalSymbols 738 | /** 739 | * Read a sensor value 740 | * 741 | * Returns the value(s) for the specified sensor. 742 | * The following sensor types each return a single value: 743 | * NONE ----------------------- 0 744 | * TOUCH ---------------------- 0 or 1 (released or pressed) 745 | * NXT_TOUCH ------------------ 0 or 1 (released or pressed) 746 | * EV3_TOUCH ------------------ 0 or 1 (released or pressed) 747 | * NXT_ULTRASONIC ------------- distance in CM 748 | * NXT_LIGHT_ON -------------- reflected light 749 | * NXT_LIGHT_OFF -------------- ambient light 750 | * NXT_COLOR_RED -------------- red reflected light 751 | * NXT_COLOR_GREEN ------------ green reflected light 752 | * NXT_COLOR_BLUE ------------- blue reflected light 753 | * NXT_COLOR_OFF -------------- ambient light 754 | * EV3_GYRO_ABS --------------- absolute rotation position in degrees 755 | * EV3_GYRO_DPS --------------- rotation rate in degrees per second 756 | * EV3_COLOR_REFLECTED -------- red reflected light 757 | * EV3_COLOR_AMBIENT ---------- ambient light 758 | * EV3_COLOR_COLOR ------------ detected color 759 | * EV3_ULTRASONIC_CM ---------- distance in CM 760 | * EV3_ULTRASONIC_INCHES ------ distance in inches 761 | * EV3_ULTRASONIC_LISTEN ------ 0 or 1 (no other ultrasonic sensors or another ultrasonic sensor detected) 762 | * EV3_INFRARED_PROXIMITY ----- distance 0-100% 763 | * 764 | * The following sensor types each return a list of values 765 | * CUSTOM --------------------- Pin 1 ADC (5v scale from 0 to 4095), Pin 6 ADC (3.3v scale from 0 to 4095), Pin 5 digital, Pin 6 digital 766 | * I2C ------------------------ the I2C bytes read 767 | * NXT_COLOR_FULL ------------- detected color, red light reflected, green light reflected, blue light reflected, ambient light 768 | * EV3_GYRO_ABS_DPS ----------- absolute rotation position in degrees, rotation rate in degrees per second 769 | * EV3_COLOR_RAW_REFLECTED ---- red reflected light, unknown value (maybe a raw ambient value?) 770 | * EV3_COLOR_COLOR_COMPONENTS - red reflected light, green reflected light, blue reflected light, unknown value (maybe a raw value?) 771 | * EV3_INFRARED_SEEK ---------- a list for each of the four channels. For each channel heading (-25 to 25), distance (-128 or 0 to 100) 772 | * EV3_INFRARED_REMOTE -------- a list for each of the four channels. For each channel red up, red down, blue up, blue down, boadcast 773 | * 774 | * @param {number} port The sensor port (one at a time). PORT_1, PORT_2, PORT_3, or PORT_4. 775 | * @return {Promise.>} 776 | */ 777 | this.get_sensor = (port, timeLimit) => { 778 | return new Promise((resolve, reject) => { 779 | let message_type, port_index; 780 | if (port === this.PORT_1) { 781 | message_type = this.BPSPI_MESSAGE_TYPE.GET_SENSOR_1; 782 | port_index = 0; 783 | } else if (port === this.PORT_2) { 784 | message_type = this.BPSPI_MESSAGE_TYPE.GET_SENSOR_2; 785 | port_index = 1; 786 | } else if (port === this.PORT_3) { 787 | message_type = this.BPSPI_MESSAGE_TYPE.GET_SENSOR_3; 788 | port_index = 2; 789 | } else if (port === this.PORT_4) { 790 | message_type = this.BPSPI_MESSAGE_TYPE.GET_SENSOR_4; 791 | port_index = 3; 792 | } else { 793 | throw new IOError('get_sensor error. Must be one sensor port at a time. PORT_1, PORT_2, PORT_3, or PORT_4.'); 794 | } 795 | 796 | if (this.SensorType[port_index] === this.SENSOR_TYPE.CUSTOM) { 797 | this.wait_until_configuration_is_finished([this.SPI_Address, message_type, 0, 0, 0, 0, 0, 0, 0, 0], timeLimit).then((reply) => { 798 | if (reply[3] === 0xA5) { 799 | if (reply[4] === this.SensorType[port_index] && reply[5] === this.SENSOR_STATE.VALID_DATA) { 800 | resolve([(((reply[8] & 0x0F) << 8) | reply[9]), (((reply[8] >> 4) & 0x0F) | (reply[7] << 4)), (reply[6] & 0x01), ((reply[6] >> 1) & 0x01)]); 801 | } else { 802 | throw new SensorError('get_sensor error: Invalid sensor data'); 803 | } 804 | } else { 805 | throw new IOError('get_sensor error: No SPI response'); 806 | } 807 | }).catch((err) => { 808 | reject(err); 809 | }); 810 | } else if (this.SensorType[port_index] === this.SENSOR_TYPE.I2C) { 811 | let outArray = [this.SPI_Address, message_type, 0, 0, 0, 0]; 812 | for (let i = 0; i < this.I2CInBytes[port_index]; i++) { 813 | outArray.push(0); 814 | } 815 | this.wait_until_configuration_is_finished(outArray, timeLimit).then((reply) => { 816 | if (reply[3] === 0xA5) { 817 | if (reply[4] === this.SensorType[port_index] && reply[5] === this.SENSOR_STATE.VALID_DATA && reply.length - 6 === this.I2CInBytes[port_index]) { 818 | let values = []; 819 | for (let i = 6; i < reply.length; i++) { 820 | values.push(reply[i]); 821 | } 822 | resolve(values); 823 | } else { 824 | throw new SensorError('get_sensor error: Invalid sensor data'); 825 | } 826 | } else { 827 | throw new IOError('get_sensor error: No SPI response'); 828 | } 829 | }).catch((err) => { 830 | reject(err); 831 | }); 832 | } else if ([ 833 | this.SENSOR_TYPE.TOUCH, 834 | this.SENSOR_TYPE.NXT_TOUCH, 835 | this.SENSOR_TYPE.EV3_TOUCH, 836 | this.SENSOR_TYPE.NXT_ULTRASONIC, 837 | this.SENSOR_TYPE.EV3_COLOR_REFLECTED, 838 | this.SENSOR_TYPE.EV3_COLOR_AMBIENT, 839 | this.SENSOR_TYPE.EV3_COLOR_COLOR, 840 | this.SENSOR_TYPE.EV3_ULTRASONIC_LISTEN, 841 | this.SENSOR_TYPE.EV3_INFRARED_PROXIMITY 842 | ].indexOf(this.SensorType[port_index]) > -1) { 843 | this.wait_until_configuration_is_finished([this.SPI_Address, message_type, 0, 0, 0, 0, 0], timeLimit).then((reply) => { 844 | if (reply[3] === 0xA5) { 845 | if ((reply[4] === this.SensorType[port_index] || (this.SensorType[port_index] === this.SENSOR_TYPE.TOUCH && (reply[4] === this.SENSOR_TYPE.NXT_TOUCH || reply[4] === this.SENSOR_TYPE.EV3_TOUCH))) && reply[5] === this.SENSOR_STATE.VALID_DATA) { 846 | resolve(reply[6]); 847 | } else { 848 | throw new SensorError('get_sensor error: Invalid sensor data'); 849 | } 850 | } else { 851 | throw new IOError('get_sensor error: No SPI response'); 852 | } 853 | }).catch((err) => { 854 | reject(err); 855 | }); 856 | } else if (this.SensorType[port_index] === this.SENSOR_TYPE.NXT_COLOR_FULL) { 857 | this.wait_until_configuration_is_finished([this.SPI_Address, message_type, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], timeLimit).then((reply) => { 858 | if (reply[3] === 0xA5) { 859 | if (reply[4] === this.SensorType[port_index] && reply[5] === this.SENSOR_STATE.VALID_DATA) { 860 | resolve([reply[6], ((reply[7] << 2) | ((reply[11] >> 6) & 0x03)), ((reply[8] << 2) | ((reply[11] >> 4) & 0x03)), ((reply[9] << 2) | ((reply[11] >> 2) & 0x03)), ((reply[10] << 2) | (reply[11] & 0x03))]); 861 | } else { 862 | throw new SensorError('get_sensor error: Invalid sensor data'); 863 | } 864 | } else { 865 | throw new IOError('get_sensor error: No SPI response'); 866 | } 867 | }).catch((err) => { 868 | reject(err); 869 | }); 870 | } else if ([ 871 | this.SENSOR_TYPE.NXT_LIGHT_ON, 872 | this.SENSOR_TYPE.NXT_LIGHT_OFF, 873 | this.SENSOR_TYPE.NXT_COLOR_RED, 874 | this.SENSOR_TYPE.NXT_COLOR_GREEN, 875 | this.SENSOR_TYPE.NXT_COLOR_BLUE, 876 | this.SENSOR_TYPE.NXT_COLOR_OFF, 877 | this.SENSOR_TYPE.EV3_GYRO_ABS, 878 | this.SENSOR_TYPE.EV3_GYRO_DPS, 879 | this.SENSOR_TYPE.EV3_ULTRASONIC_CM, 880 | this.SENSOR_TYPE.EV3_ULTRASONIC_INCHES 881 | ].indexOf(this.SensorType[port_index]) > -1) { 882 | this.wait_until_configuration_is_finished([this.SPI_Address, message_type, 0, 0, 0, 0, 0, 0], timeLimit).then((reply) => { 883 | if (reply[3] === 0xA5) { 884 | if (reply[4] === this.SensorType[port_index] && reply[5] === this.SENSOR_STATE.VALID_DATA) { 885 | let value = parseInt((reply[6] << 8) | reply[7]); 886 | 887 | if (this.SensorType[port_index] === this.SENSOR_TYPE.EV3_ULTRASONIC_CM || this.SensorType[port_index] === this.SENSOR_TYPE.EV3_ULTRASONIC_INCHES) { 888 | value = value / 10; 889 | } 890 | 891 | resolve(value); 892 | } else { 893 | throw new SensorError('get_sensor error: Invalid sensor data'); 894 | } 895 | } else { 896 | throw new IOError('get_sensor error: No SPI response'); 897 | } 898 | }).catch((err) => { 899 | reject(err); 900 | }); 901 | } else if ([ 902 | this.SENSOR_TYPE.EV3_COLOR_RAW_REFLECTED, 903 | this.SENSOR_TYPE.EV3_GYRO_ABS_DPS 904 | ].indexOf(this.SensorType[port_index]) > -1) { 905 | this.wait_until_configuration_is_finished([this.SPI_Address, message_type, 0, 0, 0, 0, 0, 0, 0, 0], timeLimit).then((reply) => { 906 | if (reply[3] === 0xA5) { 907 | if (reply[4] === this.SensorType[port_index] && reply[5] === this.SENSOR_STATE.VALID_DATA) { 908 | resolve([parseInt((reply[6] << 8) | reply[7]), parseInt((reply[8] << 8) | reply[9])]); 909 | } else { 910 | throw new SensorError('get_sensor error: Invalid sensor data'); 911 | } 912 | } else { 913 | throw new IOError('get_sensor error: No SPI response'); 914 | } 915 | }).catch((err) => { 916 | reject(err); 917 | }); 918 | } else if (this.SensorType[port_index] === this.SENSOR_TYPE.EV3_COLOR_COLOR_COMPONENTS) { 919 | this.wait_until_configuration_is_finished([this.SPI_Address, message_type, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], timeLimit).then((reply) => { 920 | if (reply[3] === 0xA5) { 921 | if (reply[4] === this.SensorType[port_index] && reply[5] === this.SENSOR_STATE.VALID_DATA) { 922 | resolve([parseInt((reply[6] << 8) | reply[7]), parseInt((reply[8] << 8) | reply[9]), parseInt((reply[10] << 8) | reply[11]), parseInt((reply[12] << 8) | reply[13])]); 923 | } else { 924 | throw new SensorError('get_sensor error: Invalid sensor data'); 925 | } 926 | } else { 927 | throw new IOError('get_sensor error: No SPI response'); 928 | } 929 | }).catch((err) => { 930 | reject(err); 931 | }); 932 | } else if (this.SensorType[port_index] === this.SENSOR_TYPE.EV3_INFRARED_SEEK) { 933 | this.wait_until_configuration_is_finished([this.SPI_Address, message_type, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], timeLimit).then((reply) => { 934 | if (reply[3] === 0xA5) { 935 | if (reply[4] === this.SensorType[port_index] && reply[5] === this.SENSOR_STATE.VALID_DATA) { 936 | resolve([[parseInt(reply[6]), parseInt(reply[7])], [parseInt(reply[8]), parseInt(reply[9])], [parseInt(reply[10]), parseInt(reply[11])], [parseInt(reply[12]), parseInt(reply[13])]]); 937 | } else { 938 | throw new SensorError('get_sensor error: Invalid sensor data'); 939 | } 940 | } else { 941 | throw new IOError('get_sensor error: No SPI response'); 942 | } 943 | }).catch((err) => { 944 | reject(err); 945 | }); 946 | } else if (this.SensorType[port_index] === this.SENSOR_TYPE.EV3_INFRARED_REMOTE) { 947 | this.wait_until_configuration_is_finished([this.SPI_Address, message_type, 0, 0, 0, 0, 0, 0, 0, 0], timeLimit).then((reply) => { 948 | if (reply[3] === 0xA5) { 949 | if (reply[4] === this.SensorType[port_index] && reply[5] === this.SENSOR_STATE.VALID_DATA) { 950 | let results = [0, 0, 0, 0]; 951 | for (let r = 0; r < results.length; r++) { 952 | let value = parseInt(reply[6 + r]); 953 | if (value === 1) { 954 | results[r] = [1, 0, 0, 0, 0]; 955 | } else if (value === 2) { 956 | results[r] = [0, 1, 0, 0, 0]; 957 | } else if (value === 3) { 958 | results[r] = [0, 0, 1, 0, 0]; 959 | } else if (value === 4) { 960 | results[r] = [0, 0, 0, 1, 0]; 961 | } else if (value === 5) { 962 | results[r] = [1, 0, 1, 0, 0]; 963 | } else if (value === 6) { 964 | results[r] = [1, 0, 0, 1, 0]; 965 | } else if (value === 7) { 966 | results[r] = [0, 1, 1, 0, 0]; 967 | } else if (value === 8) { 968 | results[r] = [0, 1, 0, 1, 0]; 969 | } else if (value === 9) { 970 | results[r] = [0, 0, 0, 0, 1]; 971 | } else if (value === 10) { 972 | results[r] = [1, 1, 0, 0, 0]; 973 | } else if (value === 11) { 974 | results[r] = [0, 0, 1, 1, 0]; 975 | } else { 976 | results[r] = [0, 0, 0, 0, 0]; 977 | } 978 | } 979 | 980 | resolve(results); 981 | } else { 982 | throw new SensorError('get_sensor error: Invalid sensor data'); 983 | } 984 | } else { 985 | throw new IOError('get_sensor error: No SPI response'); 986 | } 987 | }).catch((err) => { 988 | reject(err); 989 | }); 990 | } else { 991 | throw new IOError('get_sensor error: Sensor not configured or not supported.'); 992 | } 993 | }); 994 | }; 995 | 996 | // noinspection JSUnusedGlobalSymbols 997 | /** 998 | * Set the motor power in percent 999 | * 1000 | * @param {number} port The Motor port(s). PORT_A, PORT_B, PORT_C, and/or PORT_D. 1001 | * @param {number} power The power from -100 to 100, or -128 for float 1002 | * @return {Promise} 1003 | */ 1004 | this.set_motor_power = (port, power) => { 1005 | return new Promise((resolve, reject) => { 1006 | this.spi_transfer_array([this.SPI_Address, this.BPSPI_MESSAGE_TYPE.SET_MOTOR_POWER, parseInt(port), parseInt(power)]).then(() => { 1007 | resolve(); 1008 | }).catch((err) => { 1009 | reject(err); 1010 | }); 1011 | }); 1012 | }; 1013 | 1014 | // noinspection JSUnusedGlobalSymbols 1015 | /** 1016 | * Set the motor target position in degrees 1017 | * 1018 | * @param {number} port The Motor port(s). PORT_A, PORT_B, PORT_C, and/or PORT_D. 1019 | * @param {number} position The target position 1020 | * @return {Promise} 1021 | */ 1022 | this.set_motor_position = (port, position) => { 1023 | return new Promise((resolve, reject) => { 1024 | this.spi_transfer_array([this.SPI_Address, this.BPSPI_MESSAGE_TYPE.SET_MOTOR_POSITION, parseInt(port), ((position >> 24) & 0xFF), ((position >> 16) & 0xFF), ((position >> 8) & 0xFF), (position & 0xFF)]).then(() => { 1025 | resolve(); 1026 | }).catch((err) => { 1027 | reject(err); 1028 | }); 1029 | }); 1030 | }; 1031 | 1032 | // noinspection JSUnusedGlobalSymbols 1033 | /** 1034 | * Set the motor target position KP constant 1035 | * 1036 | * @param {number} port The Motor port(s). PORT_A, PORT_B, PORT_C, and/or PORT_D. 1037 | * @param {number} kp The KP constant (default 25) 1038 | * @return {Promise} 1039 | */ 1040 | this.set_motor_position_kp = (port, kp = 25) => { 1041 | return new Promise((resolve, reject) => { 1042 | this.spi_transfer_array([this.SPI_Address, this.BPSPI_MESSAGE_TYPE.SET_MOTOR_POSITION_KP, parseInt(port), parseInt(kp)]).then(() => { 1043 | resolve(); 1044 | }).catch((err) => { 1045 | reject(err); 1046 | }); 1047 | }); 1048 | }; 1049 | 1050 | // noinspection JSUnusedGlobalSymbols 1051 | /** 1052 | * Set the motor target position KD constant 1053 | * 1054 | * @param {number} port The Motor port(s). PORT_A, PORT_B, PORT_C, and/or PORT_D. 1055 | * @param {number} kd The KD constant (default 70) 1056 | * @return {Promise} 1057 | */ 1058 | this.set_motor_position_kd = (port, kd = 70) => { 1059 | return new Promise((resolve, reject) => { 1060 | this.spi_transfer_array([this.SPI_Address, this.BPSPI_MESSAGE_TYPE.SET_MOTOR_POSITION_KD, parseInt(port), parseInt(kd)]).then(() => { 1061 | resolve(); 1062 | }).catch((err) => { 1063 | reject(err); 1064 | }); 1065 | }); 1066 | }; 1067 | 1068 | // noinspection JSUnusedGlobalSymbols 1069 | /** 1070 | * Set the motor target speed in degrees per second 1071 | * 1072 | * @param {number} port The Motor port(s). PORT_A, PORT_B, PORT_C, and/or PORT_D. 1073 | * @param {number} dps The target speed in degrees per second 1074 | * @return {Promise} 1075 | */ 1076 | this.set_motor_dps = (port, dps) => { 1077 | return new Promise((resolve, reject) => { 1078 | this.spi_transfer_array([this.SPI_Address, this.BPSPI_MESSAGE_TYPE.SET_MOTOR_DPS, parseInt(port), ((dps >> 8) & 0xFF), (dps & 0xFF)]).then(() => { 1079 | resolve(); 1080 | }).catch((err) => { 1081 | reject(err); 1082 | }); 1083 | }); 1084 | }; 1085 | 1086 | // noinspection JSUnusedGlobalSymbols 1087 | /** 1088 | * Set the motor speed limit 1089 | * 1090 | * @param {number} port The Motor port(s). PORT_A, PORT_B, PORT_C, and/or PORT_D. 1091 | * @param {number} power The power limit in percent (0 to 100), with 0 being no limit (100) 1092 | * @param {number} dps The speed limit in degrees per second, with 0 being no limit 1093 | * @return {Promise} 1094 | */ 1095 | this.set_motor_limits = (port, power = 0, dps = 0) => { 1096 | return new Promise((resolve, reject) => { 1097 | this.spi_transfer_array([this.SPI_Address, this.BPSPI_MESSAGE_TYPE.SET_MOTOR_LIMITS, parseInt(port), parseInt(power), ((dps >> 8) & 0xFF), (dps & 0xFF)]).then(() => { 1098 | resolve(); 1099 | }).catch((err) => { 1100 | reject(err); 1101 | }); 1102 | }); 1103 | }; 1104 | 1105 | // noinspection JSUnusedGlobalSymbols 1106 | /** 1107 | * Read a motor status 1108 | * 1109 | * Returns a list: 1110 | * flags -- 8-bits of bit-flags that indicate motor status: 1111 | * bit 0 -- LOW_VOLTAGE_FLOAT - The motors are automatically disabled because the battery voltage is too low 1112 | * bit 1 -- OVERLOADED - The motors aren't close to the target (applies to position control and dps speed control). 1113 | * power -- the raw PWM power in percent (-100 to 100) 1114 | * encoder -- The encoder position 1115 | * dps -- The current speed in Degrees Per Second 1116 | * 1117 | * @param {number} port The Motor port(s). PORT_A, PORT_B, PORT_C, and/or PORT_D. 1118 | * @return {Promise.} 1119 | */ 1120 | this.get_motor_status = (port) => { 1121 | return new Promise((resolve, reject) => { 1122 | let message_type; 1123 | if (port === this.PORT_A) { 1124 | message_type = this.BPSPI_MESSAGE_TYPE.GET_MOTOR_A_STATUS; 1125 | } else if (port === this.PORT_B) { 1126 | message_type = this.BPSPI_MESSAGE_TYPE.GET_MOTOR_B_STATUS; 1127 | } else if (port === this.PORT_C) { 1128 | message_type = this.BPSPI_MESSAGE_TYPE.GET_MOTOR_C_STATUS; 1129 | } else if (port === this.PORT_D) { 1130 | message_type = this.BPSPI_MESSAGE_TYPE.GET_MOTOR_D_STATUS; 1131 | } else { 1132 | throw new IOError('get_motor_status error. Must be one motor port at a time. PORT_A, PORT_B, PORT_C, or PORT_D.'); 1133 | } 1134 | 1135 | this.spi_transfer_array([this.SPI_Address, message_type, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).then((reply) => { 1136 | if (reply[3] === 0xA5) { 1137 | let speed = parseInt(reply[5]); 1138 | let encoder = parseInt((reply[6] << 24) | (reply[7] << 16) | (reply[8] << 8) | reply[9]); 1139 | let dps = parseInt((reply[10] << 8) | reply[11]); 1140 | 1141 | resolve([reply[4], speed, encoder, dps]); 1142 | } else { 1143 | throw new IOError('No SPI response'); 1144 | } 1145 | }).catch((err) => { 1146 | reject(err); 1147 | }); 1148 | }); 1149 | }; 1150 | 1151 | // noinspection JSUnusedGlobalSymbols 1152 | /** 1153 | * Offset a motor encoder 1154 | * Zero the encoder by offsetting it by the current position 1155 | * 1156 | * @param {number} port The Motor port(s). PORT_A, PORT_B, PORT_C, and/or PORT_D. 1157 | * @param {number} position The encoder offset 1158 | * @return {Promise} 1159 | */ 1160 | this.offset_motor_encoder = (port, position) => { 1161 | return new Promise((resolve, reject) => { 1162 | this.spi_transfer_array([this.SPI_Address, this.BPSPI_MESSAGE_TYPE.OFFSET_MOTOR_ENCODER, parseInt(port), ((position >> 24) & 0xFF), ((position >> 16) & 0xFF), ((position >> 8) & 0xFF), (position & 0xFF)]).then(() => { 1163 | resolve(); 1164 | }).catch((err) => { 1165 | reject(err); 1166 | }); 1167 | }); 1168 | }; 1169 | 1170 | // noinspection JSUnusedGlobalSymbols 1171 | /** 1172 | * Read a motor encoder in degrees 1173 | * 1174 | * Returns the encoder position in degrees 1175 | * 1176 | * @param {number} port The Motor port(s). PORT_A, PORT_B, PORT_C, and/or PORT_D. 1177 | * @return {Promise.} 1178 | */ 1179 | this.get_motor_encoder = (port) => { 1180 | return new Promise((resolve, reject) => { 1181 | let message_type; 1182 | if (port === this.PORT_A) { 1183 | message_type = this.BPSPI_MESSAGE_TYPE.GET_MOTOR_A_ENCODER; 1184 | } else if (port === this.PORT_B) { 1185 | message_type = this.BPSPI_MESSAGE_TYPE.GET_MOTOR_B_ENCODER; 1186 | } else if (port === this.PORT_C) { 1187 | message_type = this.BPSPI_MESSAGE_TYPE.GET_MOTOR_C_ENCODER; 1188 | } else if (port === this.PORT_D) { 1189 | message_type = this.BPSPI_MESSAGE_TYPE.GET_MOTOR_D_ENCODER; 1190 | } else { 1191 | throw new IOError('get_motor_encoder error. Must be one motor port at a time. PORT_A, PORT_B, PORT_C, or PORT_D.'); 1192 | } 1193 | 1194 | this.spi_read_32(message_type).then((value) => { 1195 | resolve(value); 1196 | }).catch((err) => { 1197 | reject(err); 1198 | }); 1199 | }); 1200 | }; 1201 | 1202 | // noinspection JSUnusedGlobalSymbols 1203 | /** 1204 | * Reset the BrickPi. Set all the sensors' type to NONE, set the motors to float, and motors' limits and constants to default, and return control of the LED to the firmware. 1205 | */ 1206 | this.reset_all = () => { 1207 | return new Promise((resolve, reject) => { 1208 | this.set_sensor_type(this.PORT_1 + this.PORT_2 + this.PORT_3 + this.PORT_4, this.SENSOR_TYPE.NONE).then(() => { 1209 | return this.set_motor_power(this.PORT_A + this.PORT_B + this.PORT_C + this.PORT_D, this.MOTOR_FLOAT); 1210 | }).then(() => { 1211 | return this.set_motor_limits(this.PORT_A + this.PORT_B + this.PORT_C + this.PORT_D); 1212 | }).then(() => { 1213 | return this.set_motor_position_kp(this.PORT_A + this.PORT_B + this.PORT_C + this.PORT_D); 1214 | }).then(() => { 1215 | return this.set_motor_position_kd(this.PORT_A + this.PORT_B + this.PORT_C + this.PORT_D); 1216 | }).then(() => { 1217 | return this.set_led(-1); 1218 | }).then(() => { 1219 | resolve(); 1220 | }).catch((err) => { 1221 | reject(err); 1222 | }); 1223 | }); 1224 | }; 1225 | } 1226 | 1227 | module.exports = { 1228 | set_address: set_address, 1229 | BrickPi3: BrickPi3, 1230 | utils: require('./utils') 1231 | }; 1232 | --------------------------------------------------------------------------------