├── .gitignore ├── .gitmodules ├── .travis.yml ├── README.md ├── autogen-config.json ├── examples ├── README.md ├── battery-metadata.js ├── fade-leds.js ├── led-from-motor-speed.js ├── raw-device-events.js ├── run-specific-motor.js ├── test-motor-sensor.js └── touch-sensor-motor-control.js ├── gruntfile.js ├── lib ├── bluebird.d.ts └── node.d.ts ├── package.json ├── src ├── extras.ts ├── index.ts ├── io.ts ├── motors.ts └── sensors.ts ├── templates ├── autogen-header.liquid ├── connect-super-call.liquid ├── def-string-literal-types.liquid ├── export-string-literal-types.liquid ├── generic-class-description.liquid ├── generic-get-set.liquid ├── led-platform-class.liquid ├── property-value-constants.liquid └── sensor-helper-classes.liquid ├── test ├── device-tests.js ├── motor-tests.js ├── sensor-tests.js └── test-utils.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # project-specific 30 | bin/ 31 | project/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs"] 2 | path = docs 3 | url = https://github.com/wasabifan/ev3dev-lang-js 4 | [submodule "ev3dev-lang"] 5 | path = ev3dev-lang 6 | url = https://github.com/ev3dev/ev3dev-lang 7 | [submodule "test/fake-sys"] 8 | path = test/fake-sys 9 | url = https://github.com/ddemidov/ev3dev-lang-fake-sys.git 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.1" 4 | - "0.10" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Node.js Language Binding for ev3dev 2 | ============= 3 | 4 | This is a Node.js module that exposes the features of the [ev3dev](http://github.com/ev3dev/ev3dev) API in an easy-to-use structure. It is part of the "unified" bindings project for ev3dev, which means it implements our abstract API specification. This specification is implemented in multiple languages so that one can easily carry the same code concepts from one language to another, and all the core ev3dev APIs are supported universally. 5 | 6 | ## WARNING 7 | 8 | Due to the fact that Node.js has dropped support for the processor in the EV3, this library will no longer be supported nor operable starting with the upcoming ev3dev-stretch release. I suggest [looking into Python](https://github.com/ev3dev/ev3dev-lang-python) as an alternative language choice. 9 | 10 | **Current supported kernel version:** `*-11-ev3dev-*` 11 | 12 | ## Quickstart 13 | 14 | Install the module from `npm`: 15 | 16 | ``` 17 | $ npm install ev3dev-lang 18 | ``` 19 | 20 | Now add a `require` statement to your `.js` file: 21 | 22 | ``` 23 | var ev3dev = require('ev3dev-lang'); 24 | ``` 25 | 26 | Now check out the **[online documentation](http://wasabifan.github.io/ev3dev-lang-js/)** to see what you can do. Note that all the classes listed in the docs are available in the `ev3dev` object that you imported above. 27 | 28 | ## Getting the Module 29 | 30 | ### Installing the latest release from npm 31 | 32 | The easiest way to get the module is to install it through `npm`: 33 | 34 | ``` 35 | $ npm install ev3dev-lang 36 | ``` 37 | 38 | And then `require()` it for use in your code. 39 | 40 | ### Downloading the source code and compiling yourself 41 | You can also download the source from GitHub directly, either from the releases page or via git. If you do it this way, you will need to follow the building instructions below to make it usable from Node. 42 | 43 | This module is written in TypeScript, which means it cannot be directly used from JavaScript or Node.js. If you would like to make changes to the module or use a copy of the module from GitHub, you will need to follow these steps to build the module from source. The below steps should work on any modern OS, including Linux, OSX and Windows. 44 | 45 | First, you will need to install some tools. Both building and running the module will require Node.js and `npm`, so make sure that you have both installed. Then install grunt, the task runner that we use to build the library: 46 | ``` 47 | $ npm install -g grunt-cli 48 | ``` 49 | 50 | Once you have done this, run `grunt --version` to make sure that everything was installed correctly (you may have to restart your terminal window first). Next you'll need to get the source code. You can `git clone` it to get the most recent changes, or download a release from the releases page. The following commands will need to be executed from the root directory of the source tree so `cd` in to that directory before continuing. 51 | 52 | Now we will install the last few tools that we need. The list of dependencies for the module is pre-defined in the `package.json` file, so all we need to do is to tell `npm` to install them for us: 53 | ``` 54 | $ npm install 55 | ``` 56 | 57 | The final step is to run the build job. We can invoke the task runner that we installed earlier to do this: 58 | ``` 59 | $ grunt tsc 60 | ``` 61 | 62 | The build job should have put the generated JavaScript in the `bin` folder. 63 | 64 | ## Getting started with the API 65 | We recommend that you start by running the files in the `examples/` subdirectory of the repo so that you can make sure that your system is set up correctly. Assuming you don't get any errors, you can create your own `js` file and `require` the `ev3dev-lang` module to start writing your own code. For reference, you can take a look at the example scripts or check out the [online documentation](http://wasabifan.github.io/ev3dev-lang-js/). 66 | 67 | ## Executing your Node.js scripts 68 | The simplest way is to run your code from the command line with the `node` command. This can be done over an SSH session or directly on the brick. To run a `.js` file, execute: 69 | ```bash 70 | $ node path/to/file.js 71 | ``` 72 | 73 | If you want to be able to execute your scripts from brickman's file browser, you can add a [shebang](https://en.wikipedia.org/wiki/Shebang_%28Unix%29) and make it executable. You first must add the following code to the top of your `.js` file: 74 | ``` 75 | #!/usr/bin/env node 76 | ``` 77 | 78 | You can then make it executable from the command line: 79 | ```bash 80 | $ chmod +x path/to/file.js 81 | ``` 82 | 83 | You should now be able to execute it directly from brickman. 84 | 85 | ## Use cases for JavaScript on the EV3 86 | JavaScript is asynchronous by nature. There is practically no way to "sleep" your code for a certain amount of time, or wait for the operation to finish. This is by design, and both restricts the use cases for Node and JS as well as opens up new scenarios to explore. 87 | 88 | Situations to use JavaScript: 89 | - Servers 90 | - Programming a web interface, where you need to serve files 91 | - Responding to commands sent by an external controller (maybe a PC and browser) 92 | - Continuously taking input 93 | - Running a job on a timer 94 | - Running any code that only occasionally "wakes up" 95 | 96 | 97 | Situations in which you should use other languages: 98 | - Sequential actions that must run in a specific order 99 | - Precise timing and delay 100 | - Coordinating multiple motors, sensors, or other hardware devices 101 | -------------------------------------------------------------------------------- /autogen-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "src/extras.ts", 4 | "src/motors.ts", 5 | "src/sensors.ts", 6 | "src/index.ts" 7 | ], 8 | "templateDir": "templates/" 9 | } -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ## Node.js ev3dev API examples 2 | 3 | The scripts in this folder should help you test out the functionality of this library as well as understand the access patterns for sensors, motors, and LEDs. The scripts prefixed with `test-` are meant to simply exercise the functionality of the EV3 without performing any useful functions; if you're looking for cool things to try out, look at the other samples. 4 | -------------------------------------------------------------------------------- /examples/battery-metadata.js: -------------------------------------------------------------------------------- 1 | var ev3dev = require('../bin/index.js'); 2 | 3 | function printBatteryInfo(label, battery) { 4 | console.log(label + " --------------"); 5 | 6 | if(battery.connected) { 7 | console.log(' Technology: ' + battery.technology); 8 | console.log(' Type: ' + battery.type); 9 | 10 | console.log(' Current (microamps): ' + battery.measuredCurrent); 11 | console.log(' Current (amps): ' + battery.currentAmps); 12 | 13 | console.log(' Voltage (microvolts): ' + battery.measuredVoltage); 14 | console.log(' Voltage (volts): ' + battery.voltageVolts); 15 | 16 | console.log(' Max voltage (microvolts): ' + battery.maxVoltage); 17 | console.log(' Min voltage (microvolts): ' + battery.minVoltage); 18 | } 19 | else 20 | console.log(" Battery not connected!"); 21 | } 22 | 23 | var defaultBattery = new ev3dev.PowerSupply(); 24 | printBatteryInfo("Default battery", defaultBattery); -------------------------------------------------------------------------------- /examples/fade-leds.js: -------------------------------------------------------------------------------- 1 | var ev3dev = require('../bin/index.js'); 2 | 3 | console.log('fading LEDs from green to red...'); 4 | 5 | for (var pct = 0; pct < 100; pct += 1) { 6 | var brightnessVal = (pct / 100); 7 | var invertedBrightnessVal = 1 - brightnessVal; 8 | 9 | ev3dev.Ev3Leds.left.setColor([ brightnessVal, invertedBrightnessVal ]); 10 | ev3dev.Ev3Leds.right.setColor([ brightnessVal, invertedBrightnessVal ]); 11 | 12 | if(pct % 10 == 0) 13 | console.log(pct + '%'); 14 | 15 | { //Hack to sleep for time 16 | // SHOULD NOT BE USED IN PRODUCTION CODE 17 | var start = new Date().getTime(); 18 | while(new Date().getTime() < start + 100) { 19 | ; 20 | } 21 | } 22 | } 23 | 24 | console.log('done'); 25 | 26 | ev3dev.Ev3Leds.left.allOff(); 27 | ev3dev.Ev3Leds.right.allOff(); -------------------------------------------------------------------------------- /examples/led-from-motor-speed.js: -------------------------------------------------------------------------------- 1 | var ev3dev = require('../bin/index.js'); 2 | var stoppedBlinkInterval = 200; 3 | 4 | if(!ev3dev.Ev3Leds.isConnected) { 5 | console.error("This sample can only run on the EV3 brick. Other platforms are not supported by this script."); 6 | process.exit(1); 7 | } 8 | 9 | var motor = new ev3dev.Motor(); 10 | 11 | if(!motor.connected) { 12 | console.error("No valid motor was found. Please connect a tacho motor and try again."); 13 | process.exit(1); 14 | } 15 | 16 | console.log("Connected to motor " + motor.address); 17 | motor.stopAction = motor.stopActionValues.coast; 18 | 19 | console.log("Timer running... Rotate the motor and watch the on-board LEDs."); 20 | 21 | setInterval(function() { 22 | 23 | if(motor.speed > 1) { 24 | var rpsSpeed = Math.min(Math.abs(motor.speed) / motor.countPerRot, 1); 25 | var ledColor = [rpsSpeed, 1 - rpsSpeed]; 26 | ev3dev.Ev3Leds.left.setColor(ledColor); 27 | ev3dev.Ev3Leds.right.setColor(ledColor); 28 | } 29 | else { 30 | var blinkOn = (new Date()).getTime() % stoppedBlinkInterval > (stoppedBlinkInterval / 2); 31 | ev3dev.Ev3Leds.left.setColor([0, blinkOn? 0 : 1]); 32 | ev3dev.Ev3Leds.right.setColor([0, 1]); 33 | } 34 | 35 | }, 80); 36 | -------------------------------------------------------------------------------- /examples/raw-device-events.js: -------------------------------------------------------------------------------- 1 | var ev3dev = require('../bin/index.js'); 2 | 3 | var greenANSI = "\033[42m"; 4 | var redANSI = "\033[41m"; 5 | var resetANSI = "\033[0m"; 6 | 7 | var touchSensor = new ev3dev.TouchSensor(); 8 | if (!touchSensor.connected) { 9 | console.error("No touch sensor could be found! Please verify that a touch sensor is plugged in and try again."); 10 | process.exit(1); 11 | } 12 | 13 | touchSensor.registerEventCallback(function(error, touchInfo) { 14 | if(error) throw error; 15 | console.log("Sensor is " + (touchInfo.lastPressed ? greenANSI + "PRESSED" : redANSI + "RELEASED") + resetANSI); 16 | }, 17 | function(userData) { 18 | var isPressed = touchSensor.isPressed; 19 | var changed = isPressed != userData.lastPressed; 20 | 21 | userData.lastPressed = isPressed; 22 | return changed; 23 | }, false, { lastPressed: false }); 24 | 25 | console.log("Press the touch sensor to trigger the press event."); -------------------------------------------------------------------------------- /examples/run-specific-motor.js: -------------------------------------------------------------------------------- 1 | var ev3dev = require('../bin/index.js'); 2 | 3 | var motor = new ev3dev.Motor(ev3dev.OUTPUT_A); 4 | if(!motor.connected) { 5 | console.error("No motor was found on port A. Please connect a tacho motor to port A and try again."); 6 | process.exit(1); 7 | } 8 | 9 | motor.runForDistance(360 * 10, 500, motor.stopActionValues.brake); 10 | 11 | console.log("Running the motor for 180 tacho counts..."); 12 | 13 | // Prevent Node from exiting until motor is done 14 | var cancellationToken = setInterval(function() { 15 | if(!motor.isRunning) 16 | clearInterval(cancellationToken); 17 | }, 200); -------------------------------------------------------------------------------- /examples/test-motor-sensor.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/node 2 | 3 | var ev3dev = require('../bin/index.js'); 4 | 5 | // Run motor 6 | console.log('Motor --------------'); 7 | // Pick the first connected motor 8 | var motor = new ev3dev.Motor(); 9 | 10 | if (!motor.connected) 11 | console.log("No motor could be found. Are you sure that one is connected?"); 12 | 13 | console.log(' Port: ' + motor.address); 14 | console.log(' Driver: ' + motor.driverName); 15 | console.log(' Available commands: ' + motor.commands); 16 | 17 | console.log('Sending motor command...'); 18 | 19 | motor.rampUpSp = 100; 20 | motor.rampDownSp = 100; 21 | motor.runForTime(1000, motor.maxSpeed / 2, motor.stopActionValues.brake); 22 | 23 | do { 24 | console.log("Motor speed: " + motor.speed); 25 | 26 | { //Hack to sleep for time 27 | // SHOULD NOT BE USED IN PRODUCTION CODE 28 | var start = new Date().getTime(); 29 | while (new Date().getTime() < start + 80) { 30 | ; 31 | } 32 | } 33 | } while(motor.speed > 10); 34 | 35 | console.log('--------------------'); 36 | 37 | //Read sensor 38 | console.log('Sensor -------------'); 39 | // Pick the first connected sensor 40 | var sensor = new ev3dev.Sensor(); 41 | 42 | if (!sensor.connected) 43 | console.log("No sensor could be found. Are you sure that one is connected?"); 44 | 45 | console.log(' Port: ' + sensor.address); 46 | console.log(' Driver: ' + sensor.driverName); 47 | 48 | console.log('Reading all sensor values...'); 49 | for(var i = 0; i < sensor.numValues; i++) { 50 | console.log(' Value ' + i + ': ' + sensor.getValue(i) + ', ' + sensor.getFloatValue(i)); 51 | } 52 | console.log('--------------------') 53 | console.log("Core motor and sensor test complete"); 54 | -------------------------------------------------------------------------------- /examples/touch-sensor-motor-control.js: -------------------------------------------------------------------------------- 1 | var ev3dev = require('../bin/index.js'); 2 | 3 | var touchSensor = new ev3dev.TouchSensor(); 4 | if(!touchSensor.connected) { 5 | console.error("No touch sensor could be found! Please verify that a touch sensor is plugged in and try again."); 6 | process.exit(1); 7 | } 8 | 9 | var motor = new ev3dev.Motor(); 10 | if(!motor.connected) { 11 | console.error("No valid motor was found. Please connect a tacho motor and try again."); 12 | process.exit(1); 13 | } 14 | 15 | console.log("Connected to touch sensor at address " + touchSensor.address + " and tacho motor at address " + motor.address); 16 | console.log("Press the touch sensor to spin the motor."); 17 | 18 | setInterval(function() { 19 | motor.start(motor.maxSpeed * touchSensor.getValue(0), motor.stopActionValues.hold); 20 | }, 10); 21 | -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.loadNpmTasks('grunt-ts'); 3 | grunt.loadNpmTasks('grunt-typedoc'); 4 | 5 | grunt.initConfig({ 6 | pkg: grunt.file.readJSON('package.json'), 7 | ts: { 8 | main: { 9 | src: "src/index.ts", 10 | dest: 'bin/', 11 | options: { 12 | target: 'es5', 13 | sourceMap: true, 14 | declaration: true, 15 | module: 'commonjs' 16 | } 17 | } 18 | }, 19 | typedoc: { 20 | doc: { 21 | src: "src/index.ts", 22 | options: { 23 | out: './docs', 24 | name: 'ev3dev-lang for Node.js', 25 | target: 'es5', 26 | disableOutputCheck: '', 27 | module: 'commonjs' 28 | } 29 | } 30 | } 31 | }); 32 | 33 | grunt.registerTask('default', ['ts', 'typedoc']); 34 | grunt.registerTask('tsc', ['ts']); 35 | grunt.registerTask('doc', ['ts', 'typedoc']); 36 | } 37 | -------------------------------------------------------------------------------- /lib/bluebird.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for bluebird 2.0.0 2 | // Project: https://github.com/petkaantonov/bluebird 3 | // Definitions by: Bart van der Schoor , falsandtru 4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 5 | 6 | // ES6 model with generics overload was sourced and trans-multiplied from es6-promises.d.ts 7 | // By: Campredon 8 | 9 | // Warning: recommended to use `tsc > v0.9.7` (critical bugs in earlier generic code): 10 | // - https://github.com/DefinitelyTyped/DefinitelyTyped/issues/1563 11 | 12 | // Note: replicate changes to all overloads in both definition and test file 13 | // Note: keep both static and instance members inline (so similar) 14 | 15 | // TODO fix remaining TODO annotations in both definition and test 16 | 17 | // TODO verify support to have no return statement in handlers to get a Promise (more overloads?) 18 | 19 | declare var Promise: PromiseConstructor; 20 | 21 | interface PromiseConstructor { 22 | /** 23 | * Create a new promise. The passed in function will receive functions `resolve` and `reject` as its arguments which can be called to seal the fate of the created promise. 24 | */ 25 | new (callback: (resolve: (thenableOrResult?: T | PromiseLike) => void, reject: (error: any) => void) => void): Promise; 26 | 27 | config(options: { 28 | warnings?: boolean | {wForgottenReturn?: boolean}; 29 | longStackTraces?: boolean; 30 | cancellation?: boolean; 31 | monitoring?: boolean; 32 | }): void; 33 | 34 | // Ideally, we'd define e.g. "export class RangeError extends Error {}", 35 | // but as Error is defined as an interface (not a class), TypeScript doesn't 36 | // allow extending Error, only implementing it. 37 | // However, if we want to catch() only a specific error type, we need to pass 38 | // a constructor function to it. So, as a workaround, we define them here as such. 39 | RangeError(): RangeError; 40 | CancellationError(): Promise.CancellationError; 41 | TimeoutError(): Promise.TimeoutError; 42 | TypeError(): Promise.TypeError; 43 | RejectionError(): Promise.RejectionError; 44 | OperationalError(): Promise.OperationalError; 45 | 46 | /** 47 | * Changes how bluebird schedules calls a-synchronously. 48 | * 49 | * @param scheduler Should be a function that asynchronously schedules 50 | * the calling of the passed in function 51 | */ 52 | setScheduler(scheduler: (callback: (...args: any[]) => void) => void): void; 53 | 54 | /** 55 | * Start the chain of promises with `Promise.try`. Any synchronous exceptions will be turned into rejections on the returned promise. 56 | * 57 | * Note about second argument: if it's specifically a true array, its values become respective arguments for the function call. Otherwise it is passed as is as the first argument for the function call. 58 | * 59 | * Alias for `attempt();` for compatibility with earlier ECMAScript version. 60 | */ 61 | try(fn: () => T | PromiseLike, args?: any[], ctx?: any): Promise; 62 | 63 | attempt(fn: () => T | PromiseLike, args?: any[], ctx?: any): Promise; 64 | 65 | /** 66 | * Returns a new function that wraps the given function `fn`. The new function will always return a promise that is fulfilled with the original functions return values or rejected with thrown exceptions from the original function. 67 | * This method is convenient when a function can sometimes return synchronously or throw synchronously. 68 | */ 69 | method(fn: Function): Function; 70 | 71 | /** 72 | * Create a promise that is resolved with the given `value`. If `value` is a thenable or promise, the returned promise will assume its state. 73 | */ 74 | resolve(value: T | PromiseLike): Promise; 75 | resolve(): Promise; 76 | 77 | /** 78 | * Create a promise that is rejected with the given `reason`. 79 | */ 80 | reject(reason: any): Promise; 81 | reject(reason: any): Promise; 82 | 83 | /** 84 | * Create a promise with undecided fate and return a `PromiseResolver` to control it. See resolution?: Promise(#promise-resolution). 85 | */ 86 | defer(): Promise.Resolver; 87 | 88 | /** 89 | * Cast the given `value` to a trusted promise. If `value` is already a trusted `Promise`, it is returned as is. If `value` is not a thenable, a fulfilled is: Promise returned with `value` as its fulfillment value. If `value` is a thenable (Promise-like object, like those returned by jQuery's `$.ajax`), returns a trusted that: Promise assimilates the state of the thenable. 90 | */ 91 | cast(value: T | PromiseLike): Promise; 92 | 93 | /** 94 | * Sugar for `Promise.resolve(undefined).bind(thisArg);`. See `.bind()`. 95 | */ 96 | bind(thisArg: any): Promise; 97 | 98 | /** 99 | * See if `value` is a trusted Promise. 100 | */ 101 | is(value: any): boolean; 102 | 103 | /** 104 | * Call this right after the library is loaded to enabled long stack traces. Long stack traces cannot be disabled after being enabled, and cannot be enabled after promises have alread been created. Long stack traces imply a substantial performance penalty, around 4-5x for throughput and 0.5x for latency. 105 | */ 106 | longStackTraces(): void; 107 | 108 | /** 109 | * Returns a promise that will be fulfilled with `value` (or `undefined`) after given `ms` milliseconds. If `value` is a promise, the delay will start counting down when it is fulfilled and the returned promise will be fulfilled with the fulfillment value of the `value` promise. 110 | */ 111 | // TODO enable more overloads 112 | delay(ms: number, value: T | PromiseLike): Promise; 113 | delay(ms: number): Promise; 114 | 115 | /** 116 | * Returns a function that will wrap the given `nodeFunction`. Instead of taking a callback, the returned function will return a promise whose fate is decided by the callback behavior of the given node function. The node function should conform to node.js convention of accepting a callback as last argument and calling that callback with error as the first argument and success value on the second argument. 117 | * 118 | * If the `nodeFunction` calls its callback with multiple success values, the fulfillment value will be an array of them. 119 | * 120 | * If you pass a `receiver`, the `nodeFunction` will be called as a method on the `receiver`. 121 | */ 122 | promisify(func: (callback: (err: any, result: T) => void) => void, receiver?: any): () => Promise; 123 | promisify(func: (arg1: A1, callback: (err: any, result: T) => void) => void, receiver?: any): (arg1: A1) => Promise; 124 | promisify(func: (arg1: A1, arg2: A2, callback: (err: any, result: T) => void) => void, receiver?: any): (arg1: A1, arg2: A2) => Promise; 125 | promisify(func: (arg1: A1, arg2: A2, arg3: A3, callback: (err: any, result: T) => void) => void, receiver?: any): (arg1: A1, arg2: A2, arg3: A3) => Promise; 126 | promisify(func: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, callback: (err: any, result: T) => void) => void, receiver?: any): (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => Promise; 127 | promisify(func: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, callback: (err: any, result: T) => void) => void, receiver?: any): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => Promise; 128 | promisify(nodeFunction: Function, receiver?: any): Function; 129 | 130 | /** 131 | * Promisifies the entire object by going through the object's properties and creating an async equivalent of each function on the object and its prototype chain. The promisified method name will be the original method name postfixed with `Async`. Returns the input object. 132 | * 133 | * Note that the original methods on the object are not overwritten but new methods are created with the `Async`-postfix. For example, if you `promisifyAll()` the node.js `fs` object use `fs.statAsync()` to call the promisified `stat` method. 134 | */ 135 | // TODO how to model promisifyAll? 136 | promisifyAll(target: Object, options?: Promise.PromisifyAllOptions): any; 137 | 138 | 139 | /** 140 | * Returns a promise that is resolved by a node style callback function. 141 | */ 142 | fromNode(resolver: (callback: (err: any, result?: any) => void) => void, options? : {multiArgs? : boolean}): Promise; 143 | fromCallback(resolver: (callback: (err: any, result?: any) => void) => void, options? : {multiArgs? : boolean}): Promise; 144 | 145 | /** 146 | * Returns a function that can use `yield` to run asynchronous code synchronously. This feature requires the support of generators which are drafted in the next version of the language. Node version greater than `0.11.2` is required and needs to be executed with the `--harmony-generators` (or `--harmony`) command-line switch. 147 | */ 148 | // TODO fix coroutine GeneratorFunction 149 | coroutine(generatorFunction: Function): Function; 150 | 151 | /** 152 | * Spawn a coroutine which may yield promises to run asynchronous code synchronously. This feature requires the support of generators which are drafted in the next version of the language. Node version greater than `0.11.2` is required and needs to be executed with the `--harmony-generators` (or `--harmony`) command-line switch. 153 | */ 154 | // TODO fix spawn GeneratorFunction 155 | spawn(generatorFunction: Function): Promise; 156 | 157 | /** 158 | * This is relevant to browser environments with no module loader. 159 | * 160 | * Release control of the `Promise` namespace to whatever it was before this library was loaded. Returns a reference to the library namespace so you can attach it to something else. 161 | */ 162 | noConflict(): typeof Promise; 163 | 164 | /** 165 | * Add `handler` as the handler to call when there is a possibly unhandled rejection. The default handler logs the error stack to stderr or `console.error` in browsers. 166 | * 167 | * Passing no value or a non-function will have the effect of removing any kind of handling for possibly unhandled rejections. 168 | */ 169 | onPossiblyUnhandledRejection(handler: (reason: any) => any): void; 170 | 171 | /** 172 | * Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled when all the items in the array are fulfilled. The promise's fulfillment value is an array with fulfillment values at respective positions to the original array. If any promise in the array rejects, the returned promise is rejected with the rejection reason. 173 | */ 174 | // TODO enable more overloads 175 | // promise of array with promises of value 176 | all(values: PromiseLike[]>): Promise; 177 | // promise of array with values 178 | all(values: PromiseLike): Promise; 179 | // array with promises of value 180 | all(values: PromiseLike[]): Promise; 181 | // array with promises of different types 182 | all(values: [PromiseLike, PromiseLike, PromiseLike, PromiseLike, PromiseLike]): Promise<[T1, T2, T3, T4, T5]>; 183 | all(values: [PromiseLike, PromiseLike, PromiseLike, PromiseLike]): Promise<[T1, T2, T3, T4]>; 184 | all(values: [PromiseLike, PromiseLike, PromiseLike]): Promise<[T1, T2, T3]>; 185 | all(values: [PromiseLike, PromiseLike]): Promise<[T1, T2]>; 186 | // array with values 187 | all(values: T[]): Promise; 188 | 189 | /** 190 | * Like ``Promise.all`` but for object properties instead of array items. Returns a promise that is fulfilled when all the properties of the object are fulfilled. The promise's fulfillment value is an object with fulfillment values at respective keys to the original object. If any promise in the object rejects, the returned promise is rejected with the rejection reason. 191 | * 192 | * If `object` is a trusted `Promise`, then it will be treated as a promise for object rather than for its properties. All other objects are treated for their properties as is returned by `Object.keys` - the object's own enumerable properties. 193 | * 194 | * *The original object is not modified.* 195 | */ 196 | // TODO verify this is correct 197 | // trusted promise for object 198 | props(object: Promise): Promise; 199 | // object 200 | props(object: Object): Promise; 201 | 202 | /** 203 | * Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled when all the items in the array are either fulfilled or rejected. The fulfillment value is an array of ``PromiseInspection`` instances at respective positions in relation to the input array. 204 | * 205 | * *original: The array is not modified. The input array sparsity is retained in the resulting array.* 206 | */ 207 | // promise of array with promises of value 208 | settle(values: PromiseLike[]>): Promise[]>; 209 | // promise of array with values 210 | settle(values: PromiseLike): Promise[]>; 211 | // array with promises of value 212 | settle(values: PromiseLike[]): Promise[]>; 213 | // array with values 214 | settle(values: T[]): Promise[]>; 215 | 216 | /** 217 | * Like `Promise.some()`, with 1 as `count`. However, if the promise fulfills, the fulfillment value is not an array of 1 but the value directly. 218 | */ 219 | // promise of array with promises of value 220 | any(values: PromiseLike[]>): Promise; 221 | // promise of array with values 222 | any(values: PromiseLike): Promise; 223 | // array with promises of value 224 | any(values: PromiseLike[]): Promise; 225 | // array with values 226 | any(values: T[]): Promise; 227 | 228 | /** 229 | * Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled or rejected as soon as a promise in the array is fulfilled or rejected with the respective rejection reason or fulfillment value. 230 | * 231 | * **Note** If you pass empty array or a sparse array with no values, or a promise/thenable for such, it will be forever pending. 232 | */ 233 | // promise of array with promises of value 234 | race(values: PromiseLike[]>): Promise; 235 | // promise of array with values 236 | race(values: PromiseLike): Promise; 237 | // array with promises of value 238 | race(values: PromiseLike[]): Promise; 239 | // array with values 240 | race(values: T[]): Promise; 241 | 242 | /** 243 | * Initiate a competetive race between multiple promises or values (values will become immediately fulfilled promises). When `count` amount of promises have been fulfilled, the returned promise is fulfilled with an array that contains the fulfillment values of the winners in order of resolution. 244 | * 245 | * If too many promises are rejected so that the promise can never become fulfilled, it will be immediately rejected with an array of rejection reasons in the order they were thrown in. 246 | * 247 | * *The original array is not modified.* 248 | */ 249 | // promise of array with promises of value 250 | some(values: PromiseLike[]>, count: number): Promise; 251 | // promise of array with values 252 | some(values: PromiseLike, count: number): Promise; 253 | // array with promises of value 254 | some(values: PromiseLike[], count: number): Promise; 255 | // array with values 256 | some(values: T[], count: number): Promise; 257 | 258 | /** 259 | * Like `Promise.all()` but instead of having to pass an array, the array is generated from the passed variadic arguments. 260 | */ 261 | // variadic array with promises of value 262 | join(...values: PromiseLike[]): Promise; 263 | // variadic array with values 264 | join(...values: T[]): Promise; 265 | 266 | /** 267 | * Map an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `mapper` function with the signature `(item, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well. 268 | * 269 | * If the `mapper` function returns promises or thenables, the returned promise will wait for all the mapped results to be resolved as well. 270 | * 271 | * *The original array is not modified.* 272 | */ 273 | // promise of array with promises of value 274 | map(values: PromiseLike[]>, mapper: (item: T, index: number, arrayLength: number) => U | PromiseLike, options?: Promise.ConcurrencyOption): Promise; 275 | 276 | // promise of array with values 277 | map(values: PromiseLike, mapper: (item: T, index: number, arrayLength: number) => U | PromiseLike, options?: Promise.ConcurrencyOption): Promise; 278 | 279 | // array with promises of value 280 | map(values: PromiseLike[], mapper: (item: T, index: number, arrayLength: number) => U | PromiseLike, options?: Promise.ConcurrencyOption): Promise; 281 | 282 | // array with values 283 | map(values: T[], mapper: (item: T, index: number, arrayLength: number) => U | PromiseLike, options?: Promise.ConcurrencyOption): Promise; 284 | 285 | /** 286 | * Similar to `map` with concurrency set to 1 but guaranteed to execute in sequential order 287 | * 288 | * If the `mapper` function returns promises or thenables, the returned promise will wait for all the mapped results to be resolved as well. 289 | * 290 | * *The original array is not modified.* 291 | */ 292 | // promise of array with promises of value 293 | mapSeries(values: PromiseLike[]>, mapper: (item: R, index: number, arrayLength: number) => U | PromiseLike): Promise; 294 | 295 | // promise of array with values 296 | mapSeries(values: PromiseLike, mapper: (item: R, index: number, arrayLength: number) => U | PromiseLike): Promise; 297 | 298 | // array with promises of value 299 | mapSeries(values: PromiseLike[], mapper: (item: R, index: number, arrayLength: number) => U | PromiseLike): Promise; 300 | 301 | // array with values 302 | mapSeries(values: R[], mapper: (item: R, index: number, arrayLength: number) => U | PromiseLike): Promise; 303 | 304 | 305 | /** 306 | * Reduce an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `reducer` function with the signature `(total, current, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well. 307 | * 308 | * If the reducer function returns a promise or a thenable, the result for the promise is awaited for before continuing with next iteration. 309 | * 310 | * *The original array is not modified. If no `intialValue` is given and the array doesn't contain at least 2 items, the callback will not be called and `undefined` is returned. If `initialValue` is given and the array doesn't have at least 1 item, `initialValue` is returned.* 311 | */ 312 | // promise of array with promises of value 313 | reduce(values: PromiseLike[]>, reducer: (total: U, current: T, index: number, arrayLength: number) => U | PromiseLike, initialValue?: U): Promise; 314 | 315 | // promise of array with values 316 | reduce(values: PromiseLike, reducer: (total: U, current: T, index: number, arrayLength: number) => U | PromiseLike, initialValue?: U): Promise; 317 | 318 | // array with promises of value 319 | reduce(values: PromiseLike[], reducer: (total: U, current: T, index: number, arrayLength: number) => U | PromiseLike, initialValue?: U): Promise; 320 | 321 | // array with values 322 | reduce(values: T[], reducer: (total: U, current: T, index: number, arrayLength: number) => U | PromiseLike, initialValue?: U): Promise; 323 | 324 | /** 325 | * Filter an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `filterer` function with the signature `(item, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well. 326 | * 327 | * The return values from the filtered functions are coerced to booleans, with the exception of promises and thenables which are awaited for their eventual result. 328 | * 329 | * *The original array is not modified. 330 | */ 331 | // promise of array with promises of value 332 | filter(values: PromiseLike[]>, filterer: (item: T, index: number, arrayLength: number) => boolean | PromiseLike, option?: Promise.ConcurrencyOption): Promise; 333 | 334 | // promise of array with values 335 | filter(values: PromiseLike, filterer: (item: T, index: number, arrayLength: number) => boolean | PromiseLike, option?: Promise.ConcurrencyOption): Promise; 336 | 337 | // array with promises of value 338 | filter(values: PromiseLike[], filterer: (item: T, index: number, arrayLength: number) => boolean | PromiseLike, option?: Promise.ConcurrencyOption): Promise; 339 | 340 | // array with values 341 | filter(values: T[], filterer: (item: T, index: number, arrayLength: number) => boolean | PromiseLike, option?: Promise.ConcurrencyOption): Promise; 342 | 343 | /** 344 | * Iterate over an array, or a promise of an array, which contains promises (or a mix of promises and values) with the given iterator function with the signature (item, index, value) where item is the resolved value of a respective promise in the input array. Iteration happens serially. If any promise in the input array is rejected the returned promise is rejected as well. 345 | * 346 | * Resolves to the original array unmodified, this method is meant to be used for side effects. If the iterator function returns a promise or a thenable, the result for the promise is awaited for before continuing with next iteration. 347 | */ 348 | // promise of array with promises of value 349 | each(values: PromiseLike[]>, iterator: (item: T, index: number, arrayLength: number) => U | PromiseLike): Promise; 350 | // array with promises of value 351 | each(values: PromiseLike[], iterator: (item: T, index: number, arrayLength: number) => U | PromiseLike): Promise; 352 | // array with values OR promise of array with values 353 | each(values: T[] | PromiseLike, iterator: (item: T, index: number, arrayLength: number) => U | PromiseLike): Promise; 354 | } 355 | 356 | interface Promise extends PromiseLike, Promise.Inspection { 357 | /** 358 | * Promises/A+ `.then()` with progress handler. Returns a new promise chained from this promise. The new promise will be rejected or resolved dedefer on the passed `fulfilledHandler`, `rejectedHandler` and the state of this promise. 359 | */ 360 | then(onFulfill: (value: T) => U | PromiseLike, onReject?: (error: any) => U | PromiseLike, onProgress?: (note: any) => any): Promise; 361 | then(onFulfill: (value: T) => U | PromiseLike, onReject?: (error: any) => void | PromiseLike, onProgress?: (note: any) => any): Promise; 362 | 363 | /** 364 | * This is a catch-all exception handler, shortcut for calling `.then(null, handler)` on this promise. Any exception happening in a `.then`-chain will propagate to nearest `.catch` handler. 365 | * 366 | * Alias `.caught();` for compatibility with earlier ECMAScript version. 367 | */ 368 | catch(onReject?: (error: any) => T | PromiseLike | void | PromiseLike): Promise; 369 | caught(onReject?: (error: any) => T | PromiseLike | void | PromiseLike): Promise; 370 | 371 | catch(onReject?: (error: any) => U | PromiseLike): Promise; 372 | caught(onReject?: (error: any) => U | PromiseLike): Promise; 373 | 374 | /** 375 | * This extends `.catch` to work more like catch-clauses in languages like Java or C#. Instead of manually checking `instanceof` or `.name === "SomeError"`, you may specify a number of error constructors which are eligible for this catch handler. The catch handler that is first met that has eligible constructors specified, is the one that will be called. 376 | * 377 | * This method also supports predicate-based filters. If you pass a predicate function instead of an error constructor, the predicate will receive the error as an argument. The return result of the predicate will be used determine whether the error handler should be called. 378 | * 379 | * Alias `.caught();` for compatibility with earlier ECMAScript version. 380 | */ 381 | catch(predicate: (error: any) => boolean, onReject: (error: any) => T | PromiseLike | void | PromiseLike): Promise; 382 | caught(predicate: (error: any) => boolean, onReject: (error: any) => T | PromiseLike | void | PromiseLike): Promise; 383 | 384 | catch(predicate: (error: any) => boolean, onReject: (error: any) => U | PromiseLike): Promise; 385 | caught(predicate: (error: any) => boolean, onReject: (error: any) => U | PromiseLike): Promise; 386 | 387 | catch(ErrorClass: Function, onReject: (error: any) => T | PromiseLike | void | PromiseLike): Promise; 388 | caught(ErrorClass: Function, onReject: (error: any) => T | PromiseLike | void | PromiseLike): Promise; 389 | 390 | catch(ErrorClass: Function, onReject: (error: any) => U | PromiseLike): Promise; 391 | caught(ErrorClass: Function, onReject: (error: any) => U | PromiseLike): Promise; 392 | 393 | 394 | /** 395 | * Like `.catch` but instead of catching all types of exceptions, it only catches those that don't originate from thrown errors but rather from explicit rejections. 396 | */ 397 | error(onReject: (reason: any) => PromiseLike): Promise; 398 | error(onReject: (reason: any) => U): Promise; 399 | 400 | /** 401 | * Pass a handler that will be called regardless of this promise's fate. Returns a new promise chained from this promise. There are special semantics for `.finally()` in that the final value cannot be modified from the handler. 402 | * 403 | * Alias `.lastly();` for compatibility with earlier ECMAScript version. 404 | */ 405 | finally(handler: () => U | PromiseLike): Promise; 406 | 407 | lastly(handler: () => U | PromiseLike): Promise; 408 | 409 | /** 410 | * Create a promise that follows this promise, but is bound to the given `thisArg` value. A bound promise will call its handlers with the bound value set to `this`. Additionally promises derived from a bound promise will also be bound promises with the same `thisArg` binding as the original promise. 411 | */ 412 | bind(thisArg: any): Promise; 413 | 414 | /** 415 | * Like `.then()`, but any unhandled rejection that ends up here will be thrown as an error. 416 | */ 417 | done(onFulfilled?: (value: T) => PromiseLike, onRejected?: (error: any) => U | PromiseLike, onProgress?: (note: any) => any): void; 418 | done(onFulfilled?: (value: T) => U, onRejected?: (error: any) => U | PromiseLike, onProgress?: (note: any) => any): void; 419 | 420 | /** 421 | * Like `.finally()`, but not called for rejections. 422 | */ 423 | tap(onFulFill: (value: T) => U | PromiseLike): Promise; 424 | 425 | /** 426 | * Shorthand for `.then(null, null, handler);`. Attach a progress handler that will be called if this promise is progressed. Returns a new promise chained from this promise. 427 | */ 428 | progressed(handler: (note: any) => any): Promise; 429 | 430 | /** 431 | * Same as calling `Promise.delay(this, ms)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. 432 | */ 433 | delay(ms: number): Promise; 434 | 435 | /** 436 | * Returns a promise that will be fulfilled with this promise's fulfillment value or rejection reason. However, if this promise is not fulfilled or rejected within `ms` milliseconds, the returned promise is rejected with a `Promise.TimeoutError` instance. 437 | * 438 | * You may specify a custom error message with the `message` parameter. 439 | */ 440 | timeout(ms: number, message?: string): Promise; 441 | 442 | /** 443 | * Register a node-style callback on this promise. When this promise is is either fulfilled or rejected, the node callback will be called back with the node.js convention where error reason is the first argument and success value is the second argument. The error argument will be `null` in case of success. 444 | * Returns back this promise instead of creating a new one. If the `callback` argument is not a function, this method does not do anything. 445 | */ 446 | nodeify(callback: (err: any, value?: T) => void, options?: Promise.SpreadOption): Promise; 447 | nodeify(...sink: any[]): Promise; 448 | 449 | /** 450 | * Marks this promise as cancellable. Promises by default are not cancellable after v0.11 and must be marked as such for `.cancel()` to have any effect. Marking a promise as cancellable is infectious and you don't need to remark any descendant promise. 451 | */ 452 | cancellable(): Promise; 453 | 454 | /** 455 | * Cancel this promise. The cancellation will propagate to farthest cancellable ancestor promise which is still pending. 456 | * 457 | * That ancestor will then be rejected with a `CancellationError` (get a reference from `Promise.CancellationError`) object as the rejection reason. 458 | * 459 | * In a promise rejection handler you may check for a cancellation by seeing if the reason object has `.name === "Cancel"`. 460 | * 461 | * Promises are by default not cancellable. Use `.cancellable()` to mark a promise as cancellable. 462 | */ 463 | // TODO what to do with this? 464 | cancel(reason?: any): Promise; 465 | 466 | /** 467 | * Like `.then()`, but cancellation of the the returned promise or any of its descendant will not propagate cancellation to this promise or this promise's ancestors. 468 | */ 469 | fork(onFulfilled?: (value: T) => U | PromiseLike, onRejected?: (error: any) => U | PromiseLike, onProgress?: (note: any) => any): Promise; 470 | 471 | /** 472 | * Create an uncancellable promise based on this promise. 473 | */ 474 | uncancellable(): Promise; 475 | 476 | /** 477 | * See if this promise can be cancelled. 478 | */ 479 | isCancellable(): boolean; 480 | 481 | /** 482 | * See if this `promise` has been fulfilled. 483 | */ 484 | isFulfilled(): boolean; 485 | 486 | /** 487 | * See if this `promise` has been rejected. 488 | */ 489 | isRejected(): boolean; 490 | 491 | /** 492 | * See if this `promise` is still defer. 493 | */ 494 | isPending(): boolean; 495 | 496 | /** 497 | * See if this `promise` is resolved -> either fulfilled or rejected. 498 | */ 499 | isResolved(): boolean; 500 | 501 | /** 502 | * Get the fulfillment value of the underlying promise. Throws if the promise isn't fulfilled yet. 503 | * 504 | * throws `TypeError` 505 | */ 506 | value(): T; 507 | 508 | /** 509 | * Get the rejection reason for the underlying promise. Throws if the promise isn't rejected yet. 510 | * 511 | * throws `TypeError` 512 | */ 513 | reason(): any; 514 | 515 | /** 516 | * Synchronously inspect the state of this `promise`. The `PromiseInspection` will represent the state of the promise as snapshotted at the time of calling `.inspect()`. 517 | */ 518 | inspect(): Promise.Inspection; 519 | 520 | /** 521 | * This is a convenience method for doing: 522 | * 523 | * 524 | * promise.then(function(obj){ 525 | * return obj[propertyName].call(obj, arg...); 526 | * }); 527 | * 528 | */ 529 | call(propertyName: string, ...args: any[]): Promise; 530 | 531 | /** 532 | * This is a convenience method for doing: 533 | * 534 | * 535 | * promise.then(function(obj){ 536 | * return obj[propertyName]; 537 | * }); 538 | * 539 | */ 540 | // TODO find way to fix get() 541 | // get(propertyName: string): Promise; 542 | 543 | /** 544 | * Convenience method for: 545 | * 546 | * 547 | * .then(function() { 548 | * return value; 549 | * }); 550 | * 551 | * 552 | * in the case where `value` doesn't change its value. That means `value` is bound at the time of calling `.return()` 553 | * 554 | * Alias `.thenReturn();` for compatibility with earlier ECMAScript version. 555 | */ 556 | return(): Promise; 557 | thenReturn(): Promise; 558 | return(value: U): Promise; 559 | thenReturn(value: U): Promise; 560 | 561 | /** 562 | * Convenience method for: 563 | * 564 | * 565 | * .then(function() { 566 | * throw reason; 567 | * }); 568 | * 569 | * Same limitations apply as with `.return()`. 570 | * 571 | * Alias `.thenThrow();` for compatibility with earlier ECMAScript version. 572 | */ 573 | throw(reason: Error): Promise; 574 | thenThrow(reason: Error): Promise; 575 | 576 | /** 577 | * Convert to String. 578 | */ 579 | toString(): string; 580 | 581 | /** 582 | * This is implicitly called by `JSON.stringify` when serializing the object. Returns a serialized representation of the `Promise`. 583 | */ 584 | toJSON(): Object; 585 | 586 | /** 587 | * Like calling `.then`, but the fulfillment value or rejection reason is assumed to be an array, which is flattened to the formal parameters of the handlers. 588 | */ 589 | // TODO how to model instance.spread()? like Q? 590 | spread(onFulfill: Function, onReject?: (reason: any) => U | PromiseLike): Promise; 591 | /* 592 | // TODO or something like this? 593 | spread(onFulfill: (...values: W[]) => PromiseLike, onReject?: (reason: any) => PromiseLike): Promise; 594 | spread(onFulfill: (...values: W[]) => PromiseLike, onReject?: (reason: any) => U): Promise; 595 | spread(onFulfill: (...values: W[]) => U, onReject?: (reason: any) => PromiseLike): Promise; 596 | spread(onFulfill: (...values: W[]) => U, onReject?: (reason: any) => U): Promise; 597 | */ 598 | /** 599 | * Same as calling `Promise.all(thisPromise)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. 600 | */ 601 | // TODO type inference from array-resolving promise? 602 | all(): Promise; 603 | 604 | /** 605 | * Same as calling `Promise.props(thisPromise)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. 606 | */ 607 | // TODO how to model instance.props()? 608 | props(): Promise; 609 | 610 | /** 611 | * Same as calling `Promise.settle(thisPromise)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. 612 | */ 613 | // TODO type inference from array-resolving promise? 614 | settle(): Promise[]>; 615 | 616 | /** 617 | * Same as calling `Promise.any(thisPromise)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. 618 | */ 619 | // TODO type inference from array-resolving promise? 620 | any(): Promise; 621 | 622 | /** 623 | * Same as calling `Promise.some(thisPromise)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. 624 | */ 625 | // TODO type inference from array-resolving promise? 626 | some(count: number): Promise; 627 | 628 | /** 629 | * Same as calling `Promise.race(thisPromise, count)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. 630 | */ 631 | // TODO type inference from array-resolving promise? 632 | race(): Promise; 633 | 634 | /** 635 | * Same as calling `Promise.map(thisPromise, mapper)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. 636 | */ 637 | // TODO type inference from array-resolving promise? 638 | map(mapper: (item: Q, index: number, arrayLength: number) => U | PromiseLike, options?: Promise.ConcurrencyOption): Promise; 639 | 640 | /** 641 | * Same as `Promise.mapSeries(thisPromise, mapper)`. 642 | */ 643 | // TODO type inference from array-resolving promise? 644 | mapSeries(mapper: (item: Q, index: number, arrayLength: number) => U | PromiseLike): Promise; 645 | 646 | /** 647 | * Same as calling `Promise.reduce(thisPromise, Function reducer, initialValue)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. 648 | */ 649 | // TODO type inference from array-resolving promise? 650 | reduce(reducer: (memo: U, item: Q, index: number, arrayLength: number) => U | PromiseLike, initialValue?: U): Promise; 651 | 652 | /** 653 | * Same as calling ``Promise.filter(thisPromise, filterer)``. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. 654 | */ 655 | // TODO type inference from array-resolving promise? 656 | filter(filterer: (item: U, index: number, arrayLength: number) => boolean | PromiseLike, options?: Promise.ConcurrencyOption): Promise; 657 | 658 | /** 659 | * Same as calling ``Promise.each(thisPromise, iterator)``. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. 660 | */ 661 | each(iterator: (item: T, index: number, arrayLength: number) => U | PromiseLike): Promise; 662 | } 663 | 664 | /** 665 | * Don't use variable namespace such as variables, functions, and classes. 666 | * If you use this namespace, it will conflict in es6. 667 | */ 668 | declare namespace Promise { 669 | export interface RangeError extends Error { 670 | } 671 | export interface CancellationError extends Error { 672 | } 673 | export interface TimeoutError extends Error { 674 | } 675 | export interface TypeError extends Error { 676 | } 677 | export interface RejectionError extends Error { 678 | } 679 | export interface OperationalError extends Error { 680 | } 681 | 682 | export interface ConcurrencyOption { 683 | concurrency: number; 684 | } 685 | export interface SpreadOption { 686 | spread: boolean; 687 | } 688 | export interface PromisifyAllOptions { 689 | suffix?: string; 690 | filter?: (name: string, func: Function, target?: any, passesDefaultFilter?: boolean) => boolean; 691 | // The promisifier gets a reference to the original method and should return a function which returns a promise 692 | promisifier?: (originalMethod: Function) => () => PromiseLike; 693 | } 694 | 695 | export interface Resolver { 696 | /** 697 | * Returns a reference to the controlled promise that can be passed to clients. 698 | */ 699 | promise: Promise; 700 | 701 | /** 702 | * Resolve the underlying promise with `value` as the resolution value. If `value` is a thenable or a promise, the underlying promise will assume its state. 703 | */ 704 | resolve(value: T): void; 705 | resolve(): void; 706 | 707 | /** 708 | * Reject the underlying promise with `reason` as the rejection reason. 709 | */ 710 | reject(reason: any): void; 711 | 712 | /** 713 | * Progress the underlying promise with `value` as the progression value. 714 | */ 715 | progress(value: any): void; 716 | 717 | /** 718 | * Gives you a callback representation of the `PromiseResolver`. Note that this is not a method but a property. The callback accepts error object in first argument and success values on the 2nd parameter and the rest, I.E. node js conventions. 719 | * 720 | * If the the callback is called with multiple success values, the resolver fullfills its promise with an array of the values. 721 | */ 722 | // TODO specify resolver callback 723 | callback: (err: any, value: T, ...values: T[]) => void; 724 | } 725 | 726 | export interface Inspection { 727 | /** 728 | * See if the underlying promise was fulfilled at the creation time of this inspection object. 729 | */ 730 | isFulfilled(): boolean; 731 | 732 | /** 733 | * See if the underlying promise was rejected at the creation time of this inspection object. 734 | */ 735 | isRejected(): boolean; 736 | 737 | /** 738 | * See if the underlying promise was defer at the creation time of this inspection object. 739 | */ 740 | isPending(): boolean; 741 | 742 | /** 743 | * Get the fulfillment value of the underlying promise. Throws if the promise wasn't fulfilled at the creation time of this inspection object. 744 | * 745 | * throws `TypeError` 746 | */ 747 | value(): T; 748 | 749 | /** 750 | * Get the rejection reason for the underlying promise. Throws if the promise wasn't rejected at the creation time of this inspection object. 751 | * 752 | * throws `TypeError` 753 | */ 754 | reason(): any; 755 | } 756 | } 757 | 758 | declare module 'bluebird' { 759 | export = Promise; 760 | } 761 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ev3dev-lang", 3 | "version": "1.0.0", 4 | "description": "An interface to control an EV3 or other supported platform running ev3dev from JavaScript.", 5 | "keywords": [ 6 | "ev3dev", 7 | "ev3", 8 | "lego", 9 | "robotics" 10 | ], 11 | "homepage": "http://github.com/wasabifan/ev3dev-lang-js", 12 | "bugs": { 13 | "url": "http://github.com/wasabifan/ev3dev-lang-js/issues" 14 | }, 15 | "author": { 16 | "name": "WasabiFan", 17 | "email": "WasabiFan@outlook.com" 18 | }, 19 | "devDependencies": { 20 | "grunt": "^0.4.5", 21 | "grunt-cli": "^0.1.13", 22 | "grunt-shell": "^1.0.1", 23 | "grunt-typedoc": "0.2.3", 24 | "grunt-ts": "5.5.0-beta.2", 25 | "typescript": ">=1.8.0", 26 | "mocha": "2.3.4", 27 | "python-shell": "*" 28 | }, 29 | "scripts": { 30 | "test": "grunt tsc && ./node_modules/mocha/bin/mocha" 31 | }, 32 | "files": [ 33 | "bin/" 34 | ], 35 | "main": "bin/index.js", 36 | "repository": { 37 | "type": "git", 38 | "url": "http://github.com/wasabifan/ev3dev-lang-js.git" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/extras.ts: -------------------------------------------------------------------------------- 1 | //~autogen autogen-header 2 | // Sections of the following code were auto-generated based on spec v1.2.0. 3 | 4 | //~autogen 5 | 6 | import IO = require('./io'); 7 | import Device = IO.Device; 8 | 9 | //~autogen generic-class-description classes.powerSupply>currentClass 10 | /** 11 | * A generic interface to read data from the system's power_supply class. 12 | * Uses the built-in legoev3-battery if none is specified. 13 | */ 14 | //~autogen 15 | export class PowerSupply extends Device { 16 | public deviceName; 17 | 18 | constructor(deviceName: string) { 19 | super(); 20 | 21 | var deviceConstraints = { }; 22 | if (deviceName == undefined) 23 | deviceConstraints["scope"] = "System"; 24 | else 25 | this.deviceName = deviceName; 26 | 27 | this.connect('power_supply', deviceName, deviceConstraints); 28 | } 29 | 30 | //PROPERTIES 31 | //~autogen generic-get-set classes.powerSupply>currentClass 32 | /** 33 | * The measured current that the battery is supplying (in microamps) 34 | */ 35 | get measuredCurrent(): number { 36 | return this.readNumber("current_now"); 37 | } 38 | 39 | /** 40 | * The measured voltage that the battery is supplying (in microvolts) 41 | */ 42 | get measuredVoltage(): number { 43 | return this.readNumber("voltage_now"); 44 | } 45 | 46 | /** 47 | */ 48 | get maxVoltage(): number { 49 | return this.readNumber("voltage_max_design"); 50 | } 51 | 52 | /** 53 | */ 54 | get minVoltage(): number { 55 | return this.readNumber("voltage_min_design"); 56 | } 57 | 58 | /** 59 | */ 60 | get technology(): string { 61 | return this.readString("technology"); 62 | } 63 | 64 | /** 65 | */ 66 | get type(): string { 67 | return this.readString("type"); 68 | } 69 | 70 | 71 | //~autogen 72 | 73 | get voltageVolts(): number { 74 | return this.measuredVoltage / 1000000; 75 | } 76 | 77 | get currentAmps(): number { 78 | return this.measuredCurrent / 1000000; 79 | } 80 | 81 | } 82 | 83 | //~autogen generic-class-description classes.led>currentClass 84 | /** 85 | * Any device controlled by the generic LED driver. 86 | * See https://www.kernel.org/doc/Documentation/leds/leds-class.txt 87 | * for more details. 88 | */ 89 | //~autogen 90 | export class LED extends Device { 91 | public deviceName: string; 92 | 93 | constructor(deviceName: string) { 94 | super(); 95 | 96 | this.deviceName = deviceName; 97 | 98 | this.connect('leds', deviceName); 99 | } 100 | 101 | //PROPERTIES 102 | 103 | //~autogen generic-get-set classes.led>currentClass 104 | /** 105 | * Returns the maximum allowable brightness value. 106 | */ 107 | get maxBrightness(): number { 108 | return this.readNumber("max_brightness"); 109 | } 110 | 111 | /** 112 | * Sets the brightness level. Possible values are from 0 to `max_brightness`. 113 | */ 114 | get brightness(): number { 115 | return this.readNumber("brightness"); 116 | } 117 | /** 118 | * Sets the brightness level. Possible values are from 0 to `max_brightness`. 119 | */ 120 | set brightness(value: number) { 121 | this.setNumber("brightness", value); 122 | } 123 | 124 | /** 125 | * Returns a list of available triggers. 126 | */ 127 | get triggers(): string[] { 128 | return this.readStringArray("trigger"); 129 | } 130 | 131 | /** 132 | * Sets the led trigger. A trigger 133 | * is a kernel based source of led events. Triggers can either be simple or 134 | * complex. A simple trigger isn't configurable and is designed to slot into 135 | * existing subsystems with minimal additional code. Examples are the `ide-disk` and 136 | * `nand-disk` triggers. 137 | * 138 | * Complex triggers whilst available to all LEDs have LED specific 139 | * parameters and work on a per LED basis. The `timer` trigger is an example. 140 | * The `timer` trigger will periodically change the LED brightness between 141 | * 0 and the current brightness setting. The `on` and `off` time can 142 | * be specified via `delay_{on,off}` attributes in milliseconds. 143 | * You can change the brightness value of a LED independently of the timer 144 | * trigger. However, if you set the brightness value to 0 it will 145 | * also disable the `timer` trigger. 146 | */ 147 | get trigger(): string { 148 | return this.readStringSelector("trigger"); 149 | } 150 | /** 151 | * Sets the led trigger. A trigger 152 | * is a kernel based source of led events. Triggers can either be simple or 153 | * complex. A simple trigger isn't configurable and is designed to slot into 154 | * existing subsystems with minimal additional code. Examples are the `ide-disk` and 155 | * `nand-disk` triggers. 156 | * 157 | * Complex triggers whilst available to all LEDs have LED specific 158 | * parameters and work on a per LED basis. The `timer` trigger is an example. 159 | * The `timer` trigger will periodically change the LED brightness between 160 | * 0 and the current brightness setting. The `on` and `off` time can 161 | * be specified via `delay_{on,off}` attributes in milliseconds. 162 | * You can change the brightness value of a LED independently of the timer 163 | * trigger. However, if you set the brightness value to 0 it will 164 | * also disable the `timer` trigger. 165 | */ 166 | set trigger(value: string) { 167 | this.setString("trigger", value); 168 | } 169 | 170 | /** 171 | * The `timer` trigger will periodically change the LED brightness between 172 | * 0 and the current brightness setting. The `on` time can 173 | * be specified via `delay_on` attribute in milliseconds. 174 | */ 175 | get delayOn(): number { 176 | return this.readNumber("delay_on"); 177 | } 178 | /** 179 | * The `timer` trigger will periodically change the LED brightness between 180 | * 0 and the current brightness setting. The `on` time can 181 | * be specified via `delay_on` attribute in milliseconds. 182 | */ 183 | set delayOn(value: number) { 184 | this.setNumber("delay_on", value); 185 | } 186 | 187 | /** 188 | * The `timer` trigger will periodically change the LED brightness between 189 | * 0 and the current brightness setting. The `off` time can 190 | * be specified via `delay_off` attribute in milliseconds. 191 | */ 192 | get delayOff(): number { 193 | return this.readNumber("delay_off"); 194 | } 195 | /** 196 | * The `timer` trigger will periodically change the LED brightness between 197 | * 0 and the current brightness setting. The `off` time can 198 | * be specified via `delay_off` attribute in milliseconds. 199 | */ 200 | set delayOff(value: number) { 201 | this.setNumber("delay_off", value); 202 | } 203 | 204 | 205 | //~autogen 206 | 207 | /** 208 | * Sets the LED's brightness to the given percent (0-1) of the max value. 209 | */ 210 | public get brightnessPct(): number{ 211 | return this.brightness / this.maxBrightness; 212 | } 213 | 214 | public set brightnessPct(brightnessPct: number) { 215 | this.brightness = Math.round(this.maxBrightness * brightnessPct); 216 | } 217 | 218 | /** 219 | * Sets brightness to maximum value, turning the LED on 220 | */ 221 | public on() { 222 | this.brightness = this.maxBrightness; 223 | } 224 | 225 | /** 226 | * Sets brightness to 0, turning the LED off 227 | */ 228 | public off() { 229 | this.brightness = 0; 230 | } 231 | 232 | /** 233 | * Flashes the LED on a timer using the given intervals. 234 | */ 235 | public flash(onInterval: number, offInterval: number) { 236 | this.delayOn = onInterval; 237 | this.delayOff = offInterval; 238 | this.trigger = "timer"; 239 | } 240 | } 241 | 242 | export class LEDGroup { 243 | private leds: LED[]; 244 | 245 | public constructor(...leds: (string | LED)[]) { 246 | this.leds = []; 247 | for(var ledObj of leds) { 248 | if(typeof ledObj == "string") { 249 | var newLed = new LED(ledObj); 250 | this.leds.push(newLed); 251 | } 252 | else { 253 | this.leds.push(ledObj); 254 | } 255 | } 256 | } 257 | 258 | public get isConnected(): boolean { 259 | return this.leds.every(function(led: LED, index: number, wholeArray: LED[]) { 260 | return led.connected; 261 | }); 262 | } 263 | 264 | /** 265 | * Sets the brightness percentages for each LED in the group to the given percentages, 266 | * scaling each according to the given percent power scale if provided. 267 | * 268 | * @param colorCombination The percent powers to use for each LED, applied to the corresponding index in the LED group. 269 | * @param pctPower The scale factor to multiply each value by. Leave undefined or null to default to `1`. 270 | */ 271 | public setColor(colorCombination: number[], pctPower: number) { 272 | if(colorCombination.length !== this.leds.length) { 273 | throw new Error("The given color values had either too few or too many numbers for this LED group." 274 | + " Expected length: " + this.leds.length + "; Given length: " + colorCombination.length); 275 | } 276 | 277 | if(pctPower == undefined || pctPower == null) { 278 | pctPower = 1; 279 | } 280 | 281 | for(var ledIndex = 0; ledIndex < this.leds.length; ledIndex++) { 282 | this.leds[ledIndex].brightnessPct = pctPower * colorCombination[ledIndex]; 283 | } 284 | } 285 | 286 | /** 287 | * Sets the given property names to the corresponding values on each LED in the group. 288 | * 289 | * If the requested property does not exist on the LED object, the property is skipped. 290 | * 291 | * @param props A hash containing the key-value pairs of properties to set. 292 | */ 293 | public setProps(props: { [propName: string]: any}) { 294 | for(var led of this.leds) { 295 | for(var prop in Object.keys(props)) { 296 | if(Object.keys(led).indexOf(prop) != -1) { 297 | led[prop] = props[prop]; 298 | } 299 | } 300 | } 301 | } 302 | 303 | public allOn() { 304 | for(var led of this.leds) { 305 | led.on(); 306 | } 307 | } 308 | 309 | public allOff() { 310 | for(var led of this.leds) { 311 | led.off(); 312 | } 313 | } 314 | } 315 | 316 | //~autogen generic-class-description classes.legoPort>currentClass 317 | /** 318 | * The `lego-port` class provides an interface for working with input and 319 | * output ports that are compatible with LEGO MINDSTORMS RCX/NXT/EV3, LEGO 320 | * WeDo and LEGO Power Functions sensors and motors. Supported devices include 321 | * the LEGO MINDSTORMS EV3 Intelligent Brick, the LEGO WeDo USB hub and 322 | * various sensor multiplexers from 3rd party manufacturers. 323 | * 324 | * Some types of ports may have multiple modes of operation. For example, the 325 | * input ports on the EV3 brick can communicate with sensors using UART, I2C 326 | * or analog validate signals - but not all at the same time. Therefore there 327 | * are multiple modes available to connect to the different types of sensors. 328 | * 329 | * In most cases, ports are able to automatically detect what type of sensor 330 | * or motor is connected. In some cases though, this must be manually specified 331 | * using the `mode` and `set_device` attributes. The `mode` attribute affects 332 | * how the port communicates with the connected device. For example the input 333 | * ports on the EV3 brick can communicate using UART, I2C or analog voltages, 334 | * but not all at the same time, so the mode must be set to the one that is 335 | * appropriate for the connected sensor. The `set_device` attribute is used to 336 | * specify the exact type of sensor that is connected. Note: the mode must be 337 | * correctly set before setting the sensor type. 338 | * 339 | * Ports can be found at `/sys/class/lego-port/port` where `` is 340 | * incremented each time a new port is registered. Note: The number is not 341 | * related to the actual port at all - use the `address` attribute to find 342 | * a specific port. 343 | */ 344 | //~autogen 345 | export class LegoPort extends Device { 346 | protected _deviceIndex: number = -1; 347 | get deviceIndex(): number { 348 | return this._deviceIndex; 349 | } 350 | 351 | constructor(port: string) { 352 | super(); 353 | 354 | this.connect('lego-port', 'port(\d*)', { 355 | port_name: port 356 | }); 357 | } 358 | 359 | //PROPERTIES 360 | //~autogen generic-get-set classes.legoPort>currentClass 361 | /** 362 | * Returns the name of the port. See individual driver documentation for 363 | * the name that will be returned. 364 | */ 365 | get address(): string { 366 | return this.readString("address"); 367 | } 368 | 369 | /** 370 | * Returns the name of the driver that loaded this device. You can find the 371 | * complete list of drivers in the [list of port drivers]. 372 | */ 373 | get driverName(): string { 374 | return this.readString("driver_name"); 375 | } 376 | 377 | /** 378 | * Returns a list of the available modes of the port. 379 | */ 380 | get modes(): string[] { 381 | return this.readStringArray("modes"); 382 | } 383 | 384 | /** 385 | * Reading returns the currently selected mode. Writing sets the mode. 386 | * Generally speaking when the mode changes any sensor or motor devices 387 | * associated with the port will be removed new ones loaded, however this 388 | * this will depend on the individual driver implementing this class. 389 | */ 390 | get mode(): string { 391 | return this.readString("mode"); 392 | } 393 | /** 394 | * Reading returns the currently selected mode. Writing sets the mode. 395 | * Generally speaking when the mode changes any sensor or motor devices 396 | * associated with the port will be removed new ones loaded, however this 397 | * this will depend on the individual driver implementing this class. 398 | */ 399 | set mode(value: string) { 400 | this.setString("mode", value); 401 | } 402 | 403 | /** 404 | * For modes that support it, writing the name of a driver will cause a new 405 | * device to be registered for that driver and attached to this port. For 406 | * example, since NXT/Analog sensors cannot be auto-detected, you must use 407 | * this attribute to load the correct driver. Returns -EOPNOTSUPP if setting a 408 | * device is not supported. 409 | */ 410 | set setDevice(value: string) { 411 | this.setString("set_device", value); 412 | } 413 | 414 | /** 415 | * In most cases, reading status will return the same value as `mode`. In 416 | * cases where there is an `auto` mode additional values may be returned, 417 | * such as `no-device` or `error`. See individual port driver documentation 418 | * for the full list of possible values. 419 | */ 420 | get status(): string { 421 | return this.readString("status"); 422 | } 423 | 424 | 425 | //~autogen 426 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a language binding for the ev3dev device APIs. More info at: https://github.com/ev3dev/ev3dev-lang 3 | * This library complies with spec v0.9.3. 4 | */ 5 | 6 | import io = require("./io"); 7 | import motors = require("./motors"); 8 | import sensors = require("./sensors"); 9 | import extras = require("./extras"); 10 | 11 | // Constants 12 | export var INPUT_AUTO = undefined; 13 | export var OUTPUT_AUTO = undefined; 14 | 15 | export var INPUT_1 = "in1"; 16 | export var INPUT_2 = "in2"; 17 | export var INPUT_3 = "in3"; 18 | export var INPUT_4 = "in4"; 19 | 20 | export var OUTPUT_A = "outA"; 21 | export var OUTPUT_B = "outB"; 22 | export var OUTPUT_C = "outC"; 23 | export var OUTPUT_D = "outD"; 24 | 25 | // IO 26 | export var Device = io.Device; 27 | export var IndexedDevice = io.IndexedDevice; 28 | 29 | // Motors 30 | export var Motor = motors.Motor; 31 | export var DcMotor = motors.DcMotor; 32 | export var LargeMotor = motors.LargeMotor; 33 | export var MediumMotor = motors.MediumMotor; 34 | export var ServoMotor = motors.ServoMotor; 35 | 36 | //~autogen export-string-literal-types classes.motor>currentClass "motors">module 37 | export module Motor { 38 | 39 | export type CommandValue = motors.Motor.CommandValue; 40 | export type EncoderPolarityValue = motors.Motor.EncoderPolarityValue; 41 | export type PolarityValue = motors.Motor.PolarityValue; 42 | export type StateValue = motors.Motor.StateValue; 43 | export type StopActionValue = motors.Motor.StopActionValue; 44 | } 45 | //~autogen 46 | 47 | //~autogen export-string-literal-types classes.servoMotor>currentClass "motors">module 48 | export module ServoMotor { 49 | 50 | export type CommandValue = motors.ServoMotor.CommandValue; 51 | export type PolarityValue = motors.ServoMotor.PolarityValue; 52 | } 53 | //~autogen 54 | 55 | //~autogen export-string-literal-types classes.dcMotor>currentClass "motors">module 56 | export module DcMotor { 57 | 58 | export type CommandValue = motors.DcMotor.CommandValue; 59 | export type PolarityValue = motors.DcMotor.PolarityValue; 60 | export type StopActionValue = motors.DcMotor.StopActionValue; 61 | } 62 | //~autogen 63 | 64 | // Sensors 65 | export var Sensor = sensors.Sensor; 66 | export var I2CSensor = sensors.I2CSensor; 67 | export var TouchSensor = sensors.TouchSensor; 68 | export var ColorSensor = sensors.ColorSensor; 69 | export var UltrasonicSensor = sensors.UltrasonicSensor; 70 | export var GyroSensor = sensors.GyroSensor; 71 | export var InfraredSensor = sensors.InfraredSensor; 72 | export var SoundSensor = sensors.SoundSensor; 73 | export var LightSensor = sensors.LightSensor; 74 | 75 | // Extras 76 | export var PowerSupply = extras.PowerSupply; 77 | export var LED = extras.LED; 78 | export var LEDGroup = extras.LEDGroup; 79 | export var LegoPort = extras.LegoPort; 80 | 81 | //~autogen led-platform-class platforms.ev3>currentPlatform "Ev3Leds">currentPlatformClassName 82 | export class Ev3Leds { 83 | 84 | public static redLeft = new extras.LED("ev3:left:red:ev3dev"); 85 | public static redRight = new extras.LED("ev3:right:red:ev3dev"); 86 | public static greenLeft = new extras.LED("ev3:left:green:ev3dev"); 87 | public static greenRight = new extras.LED("ev3:right:green:ev3dev"); 88 | 89 | public static left = new extras.LEDGroup(Ev3Leds.redLeft, Ev3Leds.greenLeft); 90 | public static right = new extras.LEDGroup(Ev3Leds.redRight, Ev3Leds.greenRight); 91 | 92 | public static blackColor = [0, 0]; 93 | public static redColor = [1, 0]; 94 | public static greenColor = [0, 1]; 95 | public static amberColor = [1, 1]; 96 | public static orangeColor = [1, 0.5]; 97 | public static yellowColor = [0.1, 1]; 98 | 99 | public static get isConnected(): boolean { 100 | return Ev3Leds.redLeft.connected && Ev3Leds.redRight.connected && Ev3Leds.greenLeft.connected && Ev3Leds.greenRight.connected; 101 | } 102 | } 103 | //~autogen 104 | 105 | //~autogen led-platform-class platforms.brickpi>currentPlatform "BrickpiLeds">currentPlatformClassName 106 | export class BrickpiLeds { 107 | 108 | public static blueLed1 = new extras.LED("brickpi:led1:blue:ev3dev"); 109 | public static blueLed2 = new extras.LED("brickpi:led2:blue:ev3dev"); 110 | 111 | public static led1 = new extras.LEDGroup(BrickpiLeds.blueLed1); 112 | public static led2 = new extras.LEDGroup(BrickpiLeds.blueLed2); 113 | 114 | public static blackColor = [0]; 115 | public static blueColor = [1]; 116 | 117 | public static get isConnected(): boolean { 118 | return BrickpiLeds.blueLed1.connected && BrickpiLeds.blueLed2.connected; 119 | } 120 | } 121 | //~autogen -------------------------------------------------------------------------------- /src/io.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import fs = require('fs'); 5 | import path = require('path'); 6 | var Promise: PromiseConstructor = null; 7 | try { 8 | Promise = require('bluebird'); 9 | } 10 | catch( e ) { 11 | // Promises are not available 12 | } 13 | 14 | 15 | class XError { 16 | public message: string; 17 | 18 | constructor(...tsargs) { 19 | Error.apply(this, arguments); 20 | return new Error(); 21 | } 22 | } 23 | 24 | XError['prototype'] = new Error(); 25 | 26 | export class TraceError { 27 | public innerError: any; 28 | public message: string; 29 | 30 | constructor(message?: string, innerError?: any) { 31 | this.message = message; 32 | this.innerError = innerError; 33 | } 34 | 35 | public toString() { 36 | var str = this.message.trim() + '\r\nInner error:\r\n'; 37 | 38 | var innerLines = this.innerError.toString().split('\r\n'); 39 | for (var i in innerLines) { 40 | innerLines[i] = ' ' + innerLines[i]; 41 | } 42 | return str + innerLines.join('\r\n'); 43 | } 44 | } 45 | 46 | class EventNotificationRequest { 47 | private callbackFunction: (err?: Error) => void; 48 | private eventPredicate: (userData?: any) => boolean; 49 | private userData: any; 50 | private firstTriggerOnly: boolean; 51 | 52 | constructor(callbackFunction: (err?: Error) => void, eventPredicate: (userData?: any) => boolean, firstTriggerOnly: boolean = true, userData?: any) { 53 | this.callbackFunction = callbackFunction; 54 | this.eventPredicate = eventPredicate; 55 | this.userData = userData; 56 | 57 | this.firstTriggerOnly = firstTriggerOnly; 58 | } 59 | 60 | /** 61 | * Calls this event's predicate and invokes its callback if the 62 | * predicate signals for the event to fire. Returns a boolean 63 | * indicating whether the event should continue to be updated. 64 | */ 65 | public handleUpdate(): boolean { 66 | 67 | var predicateResult: boolean; 68 | 69 | try { 70 | predicateResult = this.eventPredicate(this.userData) 71 | } 72 | catch (e) { 73 | this.callbackFunction(e); 74 | return false; 75 | } 76 | 77 | if (predicateResult) { 78 | this.callbackFunction(); 79 | 80 | if (this.firstTriggerOnly) 81 | return false; 82 | } 83 | 84 | return true; 85 | } 86 | } 87 | 88 | export class Device { 89 | public static overrideSysClassDir: string = null; 90 | 91 | private static eventTimerInterval = 50; 92 | 93 | public deviceRoot: string; 94 | public deviceDirName: string; 95 | public connected: boolean = false; 96 | 97 | private sysClassDir: string = '/sys/class'; 98 | 99 | private pendingEventRequests: EventNotificationRequest[] = []; 100 | private eventTimerCancellationToken: NodeJS.Timer = null; 101 | 102 | public connect(driverName: string, nameConvention?: string, propertyConstraints?: { [propertyName: string]: any }) { 103 | var nameRegex = nameConvention? new RegExp(nameConvention) : undefined; 104 | 105 | var deviceSearchDir = path.join(Device.overrideSysClassDir || this.sysClassDir, driverName); 106 | 107 | var availableDevices: string[]; 108 | try { 109 | availableDevices = fs.readdirSync(deviceSearchDir); 110 | } 111 | catch (error) { 112 | return; 113 | } 114 | 115 | for (var deviceDirIndex in availableDevices) { 116 | var currentDeviceDirName = availableDevices[deviceDirIndex]; 117 | 118 | if (nameRegex != undefined && !nameRegex.test(currentDeviceDirName)) { 119 | continue; 120 | } 121 | 122 | var currentDeviceDir = path.join(deviceSearchDir, currentDeviceDirName); 123 | 124 | var satisfiesConstraints: boolean = true; 125 | if (propertyConstraints != undefined) { 126 | for (var propName in propertyConstraints) { 127 | var propertyValue = this.readProperty(propName, currentDeviceDir); 128 | var constraintValue = propertyConstraints[propName]; 129 | 130 | if (constraintValue instanceof Array) { 131 | if (constraintValue.indexOf(propertyValue) === -1) { 132 | satisfiesConstraints = false; 133 | } 134 | } 135 | else if (propertyValue != constraintValue) { 136 | satisfiesConstraints = false; 137 | } 138 | } 139 | } 140 | 141 | if (!satisfiesConstraints) 142 | continue; 143 | 144 | this.deviceRoot = currentDeviceDir; 145 | this.deviceDirName = currentDeviceDirName; 146 | this.connected = true; 147 | } 148 | } 149 | 150 | protected constructPropertyPath(property: string, deviceRoot?: string) { 151 | return path.join(deviceRoot || this.deviceRoot, property); 152 | } 153 | 154 | public readNumber(property: string, deviceRoot?: string): number { 155 | var value = this.readProperty(property, deviceRoot); 156 | 157 | if (typeof value !== 'number') 158 | return NaN; 159 | 160 | return value; 161 | } 162 | 163 | public readString(property: string, deviceRoot?: string): string { 164 | var value = this.readProperty(property, deviceRoot); 165 | return String(value); 166 | } 167 | 168 | public readStringAsType(property: string, deviceRoot?: string): T { 169 | return this.readString(property, deviceRoot) as T; 170 | } 171 | 172 | public readStringArray(property: string, deviceRoot?: string): string[] { 173 | return this.readString(property, deviceRoot) 174 | .split(' ') 175 | .map((value: string) => value.replace(/^\[|\]$/g, '')); 176 | } 177 | 178 | public readStringArrayAsType(property: string, deviceRoot?: string): T[] { 179 | return this.readStringArray(property, deviceRoot) as T[]; 180 | } 181 | 182 | public readStringSelector(property: string, deviceRoot?: string): string { 183 | var bracketedParts = this.readString(property, deviceRoot) 184 | .split(' ') 185 | .filter((value: string) => value.match(/^\[|\]$/g) != null); 186 | 187 | if (bracketedParts.length <= 0) 188 | return null; 189 | 190 | return bracketedParts[0].replace(/^\[|\]$/g, ''); 191 | } 192 | 193 | public readProperty(property: string, deviceRoot?: string): any { 194 | if (!deviceRoot && !this.connected) 195 | throw new Error('You must be connected to a device before you can read from it. This error probably means that the target device was not found.'); 196 | 197 | var rawValue: string; 198 | var propertyPath = this.constructPropertyPath(property, deviceRoot); 199 | 200 | try { 201 | rawValue = fs.readFileSync(propertyPath).toString(); 202 | } 203 | catch (e) { 204 | throw new TraceError('There was an error while reading from the property file "' + propertyPath + '".', e); 205 | } 206 | 207 | rawValue = rawValue.trim(); 208 | var numValue = Number(rawValue); 209 | 210 | if (isNaN(numValue)) 211 | return rawValue; 212 | else 213 | return numValue; 214 | } 215 | 216 | public setProperty(property: string, value: any): any { 217 | if (!this.connected) 218 | throw new Error('You must be connected to a device before you can write to it. This error probably means that the target device was not found.'); 219 | 220 | var propertyPath = this.constructPropertyPath(property); 221 | 222 | try { 223 | fs.writeFileSync(propertyPath, value.toString()); 224 | } 225 | catch (e) { 226 | throw new TraceError('There was an error while writing to the property file "' + propertyPath + '".', e); 227 | } 228 | } 229 | 230 | public setNumber(property: string, value: number) { 231 | this.setProperty(property, value); 232 | } 233 | 234 | public setString(property: string, value: string) { 235 | this.setProperty(property, value); 236 | } 237 | 238 | public set(propertyDefs: any) { 239 | for (var key in propertyDefs) { 240 | this.setProperty(key, propertyDefs[key]); 241 | } 242 | } 243 | 244 | private updatePendingEventRequests() { 245 | this.pendingEventRequests = this.pendingEventRequests.filter( 246 | (eventRequest, index, arr) => 247 | eventRequest.handleUpdate()); 248 | 249 | this.updateEventTimerState(); 250 | } 251 | 252 | private updateEventTimerState() { 253 | if (this.pendingEventRequests.length > 0 && this.eventTimerCancellationToken == null) { 254 | this.eventTimerCancellationToken = setInterval(() => this.updatePendingEventRequests(), Device.eventTimerInterval); 255 | } 256 | else if (this.pendingEventRequests.length <= 0 && this.eventTimerCancellationToken != null) { 257 | clearInterval(this.eventTimerCancellationToken); 258 | this.eventTimerCancellationToken = null; 259 | } 260 | } 261 | 262 | public registerEventCallback( 263 | callbackFunction: (err?: Error, userData?: any) => void, 264 | eventPredicate: (userData?: any) => boolean, 265 | firstTriggerOnly: boolean = false, 266 | userCallbackData?: any) { 267 | 268 | var newEventRequest: EventNotificationRequest = new EventNotificationRequest( 269 | (err?) => { 270 | callbackFunction(err, userCallbackData); 271 | }, eventPredicate, firstTriggerOnly, userCallbackData); 272 | 273 | this.pendingEventRequests.push(newEventRequest); 274 | this.updateEventTimerState(); 275 | } 276 | 277 | public registerEventPromise(eventPredicate: (userData?: any) => boolean, userCallbackData?: any): Promise { 278 | if(Promise == null) { 279 | throw new Error("Promises are currently unavailable. Install the 'bluebird' package or use 'registerEventCallback(...)' instead."); 280 | } 281 | 282 | return new Promise((resolve, reject) => { 283 | this.registerEventCallback((err?) => { 284 | if (err) 285 | reject(err); 286 | else 287 | resolve(userCallbackData); 288 | 289 | }, eventPredicate, true, userCallbackData); 290 | }); 291 | } 292 | } 293 | 294 | export class IndexedDevice extends Device { 295 | protected deviceIndexRegex = new RegExp("(\\d+)", "g"); 296 | 297 | protected _deviceIndex: number = -1; 298 | get deviceIndex(): number { 299 | return this._deviceIndex; 300 | } 301 | 302 | constructor(driverTypeDirName: string, nameConvention?: string, targetAddress?: string, targetDriverName?: string | string[]) { 303 | super(); 304 | 305 | var propertyConstraints: {[propertyName: string]: any} = {}; 306 | 307 | if (targetAddress != undefined) 308 | propertyConstraints['address'] = targetAddress; 309 | 310 | if (targetDriverName != undefined) 311 | propertyConstraints['driver_name'] = [].concat(targetDriverName); 312 | 313 | this.connect(driverTypeDirName, nameConvention, propertyConstraints); 314 | 315 | if (this.connected) { 316 | var matches = this.deviceIndexRegex.exec(this.deviceDirName); 317 | 318 | if (matches != null && matches[0] != undefined) { 319 | this._deviceIndex = Number(matches[1]); 320 | } 321 | } 322 | } 323 | } -------------------------------------------------------------------------------- /src/motors.ts: -------------------------------------------------------------------------------- 1 | //~autogen autogen-header 2 | // Sections of the following code were auto-generated based on spec v1.2.0. 3 | 4 | //~autogen 5 | 6 | import IO = require('./io'); 7 | import Device = IO.Device; 8 | import IndexedDevice = IO.IndexedDevice; 9 | 10 | //~autogen def-string-literal-types classes.motor>currentClass 11 | export module Motor { 12 | 13 | export type CommandValue = 'run-forever' | 'run-to-abs-pos' | 'run-to-rel-pos' | 'run-timed' | 'run-direct' | 'stop' | 'reset'; 14 | export type EncoderPolarityValue = 'normal' | 'inversed'; 15 | export type PolarityValue = 'normal' | 'inversed'; 16 | export type StateValue = 'running' | 'ramping' | 'holding' | 'overloaded' | 'stalled'; 17 | export type StopActionValue = 'coast' | 'brake' | 'hold'; 18 | } 19 | //~autogen 20 | 21 | //~autogen def-string-literal-types classes.dcMotor>currentClass 22 | export module DcMotor { 23 | 24 | export type CommandValue = 'run-forever' | 'run-timed' | 'run-direct' | 'stop'; 25 | export type PolarityValue = 'normal' | 'inversed'; 26 | export type StopActionValue = 'coast' | 'brake'; 27 | } 28 | //~autogen 29 | 30 | //~autogen def-string-literal-types classes.servoMotor>currentClass 31 | export module ServoMotor { 32 | 33 | export type CommandValue = 'run' | 'float'; 34 | export type PolarityValue = 'normal' | 'inversed'; 35 | } 36 | //~autogen 37 | 38 | export class MotorBase extends IndexedDevice { 39 | constructor(driverTypeDirName: string, nameConvention?: string, targetAddress?: string, targetDriverName?: string | string[]) { 40 | super(driverTypeDirName, nameConvention, targetAddress, targetDriverName); 41 | } 42 | } 43 | 44 | //~autogen generic-class-description classes.motor>currentClass 45 | /** 46 | * The motor class provides a uniform interface for using motors with 47 | * positional and directional feedback such as the EV3 and NXT motors. 48 | * This feedback allows for precise control of the motors. This is the 49 | * most common type of motor, so we just call it `motor`. 50 | * 51 | * The way to configure a motor is to set the '_sp' attributes when 52 | * calling a command or before. Only in 'run_direct' mode attribute 53 | * changes are processed immediately, in the other modes they only 54 | * take place when a new command is issued. 55 | */ 56 | //~autogen 57 | export class Motor extends MotorBase { 58 | 59 | public constructor(port?: string, targetDriverName?: string[] | string) { 60 | //~autogen connect-super-call classes.motor>currentClass "port,targetDriverName">extraParams "true">omitNameConvention 61 | super('tacho-motor', null, port,targetDriverName); 62 | //~autogen 63 | } 64 | 65 | //~autogen property-value-constants classes.motor>currentClass 66 | 67 | public get commandValues(): { runForever: Motor.CommandValue, runToAbsPos: Motor.CommandValue, runToRelPos: Motor.CommandValue, runTimed: Motor.CommandValue, runDirect: Motor.CommandValue, stop: Motor.CommandValue, reset: Motor.CommandValue } { 68 | return { 69 | runForever: "run-forever", 70 | runToAbsPos: "run-to-abs-pos", 71 | runToRelPos: "run-to-rel-pos", 72 | runTimed: "run-timed", 73 | runDirect: "run-direct", 74 | stop: "stop", 75 | reset: "reset" 76 | } 77 | } 78 | 79 | public get encoderPolarityValues(): { normal: Motor.EncoderPolarityValue, inversed: Motor.EncoderPolarityValue } { 80 | return { 81 | normal: "normal", 82 | inversed: "inversed" 83 | } 84 | } 85 | 86 | public get polarityValues(): { normal: Motor.PolarityValue, inversed: Motor.PolarityValue } { 87 | return { 88 | normal: "normal", 89 | inversed: "inversed" 90 | } 91 | } 92 | 93 | public get stateValues(): { running: Motor.StateValue, ramping: Motor.StateValue, holding: Motor.StateValue, overloaded: Motor.StateValue, stalled: Motor.StateValue } { 94 | return { 95 | running: "running", 96 | ramping: "ramping", 97 | holding: "holding", 98 | overloaded: "overloaded", 99 | stalled: "stalled" 100 | } 101 | } 102 | 103 | public get stopActionValues(): { coast: Motor.StopActionValue, brake: Motor.StopActionValue, hold: Motor.StopActionValue } { 104 | return { 105 | coast: "coast", 106 | brake: "brake", 107 | hold: "hold" 108 | } 109 | } 110 | 111 | //~autogen 112 | 113 | public reset() { 114 | this.command = this.commandValues.reset; 115 | } 116 | 117 | public stop() { 118 | this.command = this.commandValues.stop; 119 | } 120 | 121 | //PROPERTIES 122 | //~autogen generic-get-set classes.motor>currentClass 123 | /** 124 | * Returns the name of the port that this motor is connected to. 125 | */ 126 | get address(): string { 127 | return this.readString("address"); 128 | } 129 | 130 | /** 131 | * Sends a command to the motor controller. See `commands` for a list of 132 | * possible values. 133 | */ 134 | set command(value: Motor.CommandValue) { 135 | this.setString("command", value); 136 | } 137 | 138 | /** 139 | * Returns a list of commands that are supported by the motor 140 | * controller. Possible values are `run-forever`, `run-to-abs-pos`, `run-to-rel-pos`, 141 | * `run-timed`, `run-direct`, `stop` and `reset`. Not all commands may be supported. 142 | * 143 | * - `run-forever` will cause the motor to run until another command is sent. 144 | * - `run-to-abs-pos` will run to an absolute position specified by `position_sp` 145 | * and then stop using the action specified in `stop_action`. 146 | * - `run-to-rel-pos` will run to a position relative to the current `position` value. 147 | * The new position will be current `position` + `position_sp`. When the new 148 | * position is reached, the motor will stop using the action specified by `stop_action`. 149 | * - `run-timed` will run the motor for the amount of time specified in `time_sp` 150 | * and then stop the motor using the action specified by `stop_action`. 151 | * - `run-direct` will run the motor at the duty cycle specified by `duty_cycle_sp`. 152 | * Unlike other run commands, changing `duty_cycle_sp` while running *will* 153 | * take effect immediately. 154 | * - `stop` will stop any of the run commands before they are complete using the 155 | * action specified by `stop_action`. 156 | * - `reset` will reset all of the motor parameter attributes to their default value. 157 | * This will also have the effect of stopping the motor. 158 | */ 159 | get commands(): string[] { 160 | return this.readStringArray("commands"); 161 | } 162 | 163 | /** 164 | * Returns the number of tacho counts in one rotation of the motor. Tacho counts 165 | * are used by the position and speed attributes, so you can use this value 166 | * to convert rotations or degrees to tacho counts. (rotation motors only) 167 | */ 168 | get countPerRot(): number { 169 | return this.readNumber("count_per_rot"); 170 | } 171 | 172 | /** 173 | * Returns the number of tacho counts in one meter of travel of the motor. Tacho 174 | * counts are used by the position and speed attributes, so you can use this 175 | * value to convert from distance to tacho counts. (linear motors only) 176 | */ 177 | get countPerM(): number { 178 | return this.readNumber("count_per_m"); 179 | } 180 | 181 | /** 182 | * Returns the name of the driver that provides this tacho motor device. 183 | */ 184 | get driverName(): string { 185 | return this.readString("driver_name"); 186 | } 187 | 188 | /** 189 | * Returns the current duty cycle of the motor. Units are percent. Values 190 | * are -100 to 100. 191 | */ 192 | get dutyCycle(): number { 193 | return this.readNumber("duty_cycle"); 194 | } 195 | 196 | /** 197 | * Writing sets the duty cycle setpoint. Reading returns the current value. 198 | * Units are in percent. Valid values are -100 to 100. A negative value causes 199 | * the motor to rotate in reverse. 200 | */ 201 | get dutyCycleSp(): number { 202 | return this.readNumber("duty_cycle_sp"); 203 | } 204 | /** 205 | * Writing sets the duty cycle setpoint. Reading returns the current value. 206 | * Units are in percent. Valid values are -100 to 100. A negative value causes 207 | * the motor to rotate in reverse. 208 | */ 209 | set dutyCycleSp(value: number) { 210 | this.setNumber("duty_cycle_sp", value); 211 | } 212 | 213 | /** 214 | * Returns the number of tacho counts in the full travel of the motor. When 215 | * combined with the `count_per_m` atribute, you can use this value to 216 | * calculate the maximum travel distance of the motor. (linear motors only) 217 | */ 218 | get fullTravelCount(): number { 219 | return this.readNumber("full_travel_count"); 220 | } 221 | 222 | /** 223 | * Sets the polarity of the motor. With `normal` polarity, a positive duty 224 | * cycle will cause the motor to rotate clockwise. With `inversed` polarity, 225 | * a positive duty cycle will cause the motor to rotate counter-clockwise. 226 | * Valid values are `normal` and `inversed`. 227 | */ 228 | get polarity(): Motor.PolarityValue { 229 | return this.readStringAsType("polarity"); 230 | } 231 | /** 232 | * Sets the polarity of the motor. With `normal` polarity, a positive duty 233 | * cycle will cause the motor to rotate clockwise. With `inversed` polarity, 234 | * a positive duty cycle will cause the motor to rotate counter-clockwise. 235 | * Valid values are `normal` and `inversed`. 236 | */ 237 | set polarity(value: Motor.PolarityValue) { 238 | this.setString("polarity", value); 239 | } 240 | 241 | /** 242 | * Returns the current position of the motor in pulses of the rotary 243 | * encoder. When the motor rotates clockwise, the position will increase. 244 | * Likewise, rotating counter-clockwise causes the position to decrease. 245 | * Writing will set the position to that value. 246 | */ 247 | get position(): number { 248 | return this.readNumber("position"); 249 | } 250 | /** 251 | * Returns the current position of the motor in pulses of the rotary 252 | * encoder. When the motor rotates clockwise, the position will increase. 253 | * Likewise, rotating counter-clockwise causes the position to decrease. 254 | * Writing will set the position to that value. 255 | */ 256 | set position(value: number) { 257 | this.setNumber("position", value); 258 | } 259 | 260 | /** 261 | * The proportional constant for the position PID. 262 | */ 263 | get positionP(): number { 264 | return this.readNumber("hold_pid/Kp"); 265 | } 266 | /** 267 | * The proportional constant for the position PID. 268 | */ 269 | set positionP(value: number) { 270 | this.setNumber("hold_pid/Kp", value); 271 | } 272 | 273 | /** 274 | * The integral constant for the position PID. 275 | */ 276 | get positionI(): number { 277 | return this.readNumber("hold_pid/Ki"); 278 | } 279 | /** 280 | * The integral constant for the position PID. 281 | */ 282 | set positionI(value: number) { 283 | this.setNumber("hold_pid/Ki", value); 284 | } 285 | 286 | /** 287 | * The derivative constant for the position PID. 288 | */ 289 | get positionD(): number { 290 | return this.readNumber("hold_pid/Kd"); 291 | } 292 | /** 293 | * The derivative constant for the position PID. 294 | */ 295 | set positionD(value: number) { 296 | this.setNumber("hold_pid/Kd", value); 297 | } 298 | 299 | /** 300 | * Writing specifies the target position for the `run-to-abs-pos` and `run-to-rel-pos` 301 | * commands. Reading returns the current value. Units are in tacho counts. You 302 | * can use the value returned by `counts_per_rot` to convert tacho counts to/from 303 | * rotations or degrees. 304 | */ 305 | get positionSp(): number { 306 | return this.readNumber("position_sp"); 307 | } 308 | /** 309 | * Writing specifies the target position for the `run-to-abs-pos` and `run-to-rel-pos` 310 | * commands. Reading returns the current value. Units are in tacho counts. You 311 | * can use the value returned by `counts_per_rot` to convert tacho counts to/from 312 | * rotations or degrees. 313 | */ 314 | set positionSp(value: number) { 315 | this.setNumber("position_sp", value); 316 | } 317 | 318 | /** 319 | * Returns the maximum value that is accepted by the `speed_sp` attribute. This 320 | * may be slightly different than the maximum speed that a particular motor can 321 | * reach - it's the maximum theoretical speed. 322 | */ 323 | get maxSpeed(): number { 324 | return this.readNumber("max_speed"); 325 | } 326 | 327 | /** 328 | * Returns the current motor speed in tacho counts per second. Note, this is 329 | * not necessarily degrees (although it is for LEGO motors). Use the `count_per_rot` 330 | * attribute to convert this value to RPM or deg/sec. 331 | */ 332 | get speed(): number { 333 | return this.readNumber("speed"); 334 | } 335 | 336 | /** 337 | * Writing sets the target speed in tacho counts per second used for all `run-*` 338 | * commands except `run-direct`. Reading returns the current value. A negative 339 | * value causes the motor to rotate in reverse with the exception of `run-to-*-pos` 340 | * commands where the sign is ignored. Use the `count_per_rot` attribute to convert 341 | * RPM or deg/sec to tacho counts per second. Use the `count_per_m` attribute to 342 | * convert m/s to tacho counts per second. 343 | */ 344 | get speedSp(): number { 345 | return this.readNumber("speed_sp"); 346 | } 347 | /** 348 | * Writing sets the target speed in tacho counts per second used for all `run-*` 349 | * commands except `run-direct`. Reading returns the current value. A negative 350 | * value causes the motor to rotate in reverse with the exception of `run-to-*-pos` 351 | * commands where the sign is ignored. Use the `count_per_rot` attribute to convert 352 | * RPM or deg/sec to tacho counts per second. Use the `count_per_m` attribute to 353 | * convert m/s to tacho counts per second. 354 | */ 355 | set speedSp(value: number) { 356 | this.setNumber("speed_sp", value); 357 | } 358 | 359 | /** 360 | * Writing sets the ramp up setpoint. Reading returns the current value. Units 361 | * are in milliseconds and must be positive. When set to a non-zero value, the 362 | * motor speed will increase from 0 to 100% of `max_speed` over the span of this 363 | * setpoint. The actual ramp time is the ratio of the difference between the 364 | * `speed_sp` and the current `speed` and max_speed multiplied by `ramp_up_sp`. 365 | */ 366 | get rampUpSp(): number { 367 | return this.readNumber("ramp_up_sp"); 368 | } 369 | /** 370 | * Writing sets the ramp up setpoint. Reading returns the current value. Units 371 | * are in milliseconds and must be positive. When set to a non-zero value, the 372 | * motor speed will increase from 0 to 100% of `max_speed` over the span of this 373 | * setpoint. The actual ramp time is the ratio of the difference between the 374 | * `speed_sp` and the current `speed` and max_speed multiplied by `ramp_up_sp`. 375 | */ 376 | set rampUpSp(value: number) { 377 | this.setNumber("ramp_up_sp", value); 378 | } 379 | 380 | /** 381 | * Writing sets the ramp down setpoint. Reading returns the current value. Units 382 | * are in milliseconds and must be positive. When set to a non-zero value, the 383 | * motor speed will decrease from 0 to 100% of `max_speed` over the span of this 384 | * setpoint. The actual ramp time is the ratio of the difference between the 385 | * `speed_sp` and the current `speed` and max_speed multiplied by `ramp_down_sp`. 386 | */ 387 | get rampDownSp(): number { 388 | return this.readNumber("ramp_down_sp"); 389 | } 390 | /** 391 | * Writing sets the ramp down setpoint. Reading returns the current value. Units 392 | * are in milliseconds and must be positive. When set to a non-zero value, the 393 | * motor speed will decrease from 0 to 100% of `max_speed` over the span of this 394 | * setpoint. The actual ramp time is the ratio of the difference between the 395 | * `speed_sp` and the current `speed` and max_speed multiplied by `ramp_down_sp`. 396 | */ 397 | set rampDownSp(value: number) { 398 | this.setNumber("ramp_down_sp", value); 399 | } 400 | 401 | /** 402 | * The proportional constant for the speed regulation PID. 403 | */ 404 | get speedP(): number { 405 | return this.readNumber("speed_pid/Kp"); 406 | } 407 | /** 408 | * The proportional constant for the speed regulation PID. 409 | */ 410 | set speedP(value: number) { 411 | this.setNumber("speed_pid/Kp", value); 412 | } 413 | 414 | /** 415 | * The integral constant for the speed regulation PID. 416 | */ 417 | get speedI(): number { 418 | return this.readNumber("speed_pid/Ki"); 419 | } 420 | /** 421 | * The integral constant for the speed regulation PID. 422 | */ 423 | set speedI(value: number) { 424 | this.setNumber("speed_pid/Ki", value); 425 | } 426 | 427 | /** 428 | * The derivative constant for the speed regulation PID. 429 | */ 430 | get speedD(): number { 431 | return this.readNumber("speed_pid/Kd"); 432 | } 433 | /** 434 | * The derivative constant for the speed regulation PID. 435 | */ 436 | set speedD(value: number) { 437 | this.setNumber("speed_pid/Kd", value); 438 | } 439 | 440 | /** 441 | * Reading returns a list of state flags. Possible flags are 442 | * `running`, `ramping`, `holding`, `overloaded` and `stalled`. 443 | */ 444 | get state(): Motor.StateValue[] { 445 | return this.readStringArrayAsType("state"); 446 | } 447 | 448 | /** 449 | * Reading returns the current stop action. Writing sets the stop action. 450 | * The value determines the motors behavior when `command` is set to `stop`. 451 | * Also, it determines the motors behavior when a run command completes. See 452 | * `stop_actions` for a list of possible values. 453 | */ 454 | get stopAction(): Motor.StopActionValue { 455 | return this.readStringAsType("stop_action"); 456 | } 457 | /** 458 | * Reading returns the current stop action. Writing sets the stop action. 459 | * The value determines the motors behavior when `command` is set to `stop`. 460 | * Also, it determines the motors behavior when a run command completes. See 461 | * `stop_actions` for a list of possible values. 462 | */ 463 | set stopAction(value: Motor.StopActionValue) { 464 | this.setString("stop_action", value); 465 | } 466 | 467 | /** 468 | * Returns a list of stop actions supported by the motor controller. 469 | * Possible values are `coast`, `brake` and `hold`. `coast` means that power will 470 | * be removed from the motor and it will freely coast to a stop. `brake` means 471 | * that power will be removed from the motor and a passive electrical load will 472 | * be placed on the motor. This is usually done by shorting the motor terminals 473 | * together. This load will absorb the energy from the rotation of the motors and 474 | * cause the motor to stop more quickly than coasting. `hold` does not remove 475 | * power from the motor. Instead it actively tries to hold the motor at the current 476 | * position. If an external force tries to turn the motor, the motor will 'push 477 | * back' to maintain its position. 478 | */ 479 | get stopActions(): string[] { 480 | return this.readStringArray("stop_actions"); 481 | } 482 | 483 | /** 484 | * Writing specifies the amount of time the motor will run when using the 485 | * `run-timed` command. Reading returns the current value. Units are in 486 | * milliseconds. 487 | */ 488 | get timeSp(): number { 489 | return this.readNumber("time_sp"); 490 | } 491 | /** 492 | * Writing specifies the amount of time the motor will run when using the 493 | * `run-timed` command. Reading returns the current value. Units are in 494 | * milliseconds. 495 | */ 496 | set timeSp(value: number) { 497 | this.setNumber("time_sp", value); 498 | } 499 | 500 | 501 | //~autogen 502 | 503 | public sendCommand(commandName: Motor.CommandValue) { 504 | 505 | if (this.commands.indexOf(commandName) < 0) 506 | throw new Error('The command ' + commandName + ' is not supported by the device.'); 507 | 508 | this.command = commandName; 509 | } 510 | 511 | public setStopAction(stopAction: Motor.StopActionValue) { 512 | 513 | if (this.stopActions.indexOf(stopAction) < 0) 514 | throw new Error('The stop command ' + stopAction + ' is not supported by the device.'); 515 | 516 | this.stopAction = stopAction; 517 | } 518 | 519 | public runForever(sp?: number, stopAction?: Motor.StopActionValue) { 520 | if (sp != undefined) 521 | this.speedSp = sp; 522 | 523 | if(stopAction != undefined) 524 | this.setStopAction(stopAction); 525 | 526 | this.sendCommand(this.commandValues.runForever); 527 | } 528 | 529 | public start(sp?: number, stopAction?: Motor.StopActionValue) { 530 | this.runForever(sp, stopAction); 531 | } 532 | 533 | public runToPosition(position?: number, speedSp?: number, stopAction?: Motor.StopActionValue) { 534 | this.runToAbsolutePosition(position, speedSp, stopAction); 535 | } 536 | 537 | public runToAbsolutePosition(position?: number, speedSp?: number, stopAction?: Motor.StopActionValue) { 538 | if (speedSp != undefined) 539 | this.speedSp = speedSp; 540 | 541 | if (position != undefined) 542 | this.positionSp = position; 543 | 544 | if(stopAction != undefined) 545 | this.setStopAction(stopAction); 546 | 547 | this.sendCommand(this.commandValues.runToAbsPos); 548 | } 549 | 550 | public runForDistance(distance?: number, speedSp?: number, stopAction?: Motor.StopActionValue) { 551 | this.runToRelativePosition(distance, speedSp, stopAction); 552 | } 553 | 554 | public runToRelativePosition(relPos?: number, speedSp?: number, stopAction?: Motor.StopActionValue) { 555 | if (speedSp != undefined) 556 | this.speedSp = speedSp; 557 | 558 | if (relPos != undefined) 559 | this.positionSp = relPos; 560 | 561 | if(stopAction != undefined) 562 | this.setStopAction(stopAction); 563 | 564 | this.sendCommand(this.commandValues.runToRelPos); 565 | } 566 | 567 | public runForTime(timeMs: number, speedSp?: number, stopAction?: Motor.StopActionValue) { 568 | if (speedSp != undefined) 569 | this.speedSp = speedSp; 570 | 571 | if (timeMs != undefined) 572 | this.timeSp = timeMs; 573 | 574 | if(stopAction != undefined) 575 | this.setStopAction(stopAction); 576 | 577 | this.sendCommand(this.commandValues.runTimed); 578 | } 579 | 580 | public hasState(stateValue: Motor.StateValue): boolean { 581 | return this.state.indexOf(stateValue) >= 0; 582 | } 583 | 584 | public get isRunning(): boolean { 585 | return this.hasState(this.stateValues.running); 586 | } 587 | 588 | public get isRamping(): boolean { 589 | return this.hasState(this.stateValues.ramping); 590 | } 591 | 592 | public get isHolding(): boolean { 593 | return this.hasState(this.stateValues.holding); 594 | } 595 | 596 | public get isOverloaded(): boolean { 597 | return this.hasState(this.stateValues.overloaded); 598 | } 599 | 600 | public get isStalled(): boolean { 601 | return this.hasState(this.stateValues.stalled); 602 | } 603 | } 604 | 605 | //~autogen generic-class-description classes.largeMotor>currentClass 606 | /** 607 | * EV3 large servo motor 608 | */ 609 | //~autogen 610 | export class LargeMotor extends Motor { 611 | constructor(port?: string) { 612 | super(port, 'lego-ev3-l-motor'); 613 | } 614 | } 615 | 616 | //~autogen generic-class-description classes.mediumMotor>currentClass 617 | /** 618 | * EV3 medium servo motor 619 | */ 620 | //~autogen 621 | export class MediumMotor extends Motor { 622 | constructor(port?: string) { 623 | super(port, 'lego-ev3-m-motor'); 624 | } 625 | } 626 | 627 | //~autogen generic-class-description classes.dcMotor>currentClass 628 | /** 629 | * The DC motor class provides a uniform interface for using regular DC motors 630 | * with no fancy controls or feedback. This includes LEGO MINDSTORMS RCX motors 631 | * and LEGO Power Functions motors. 632 | */ 633 | //~autogen 634 | export class DcMotor extends MotorBase { 635 | 636 | constructor(port: string) { 637 | //~autogen connect-super-call classes.dcMotor>currentClass "port">extraParams "true">omitNameConvention 638 | super('dc-motor', null, port); 639 | //~autogen 640 | } 641 | 642 | //~autogen property-value-constants classes.dcMotor>currentClass 643 | 644 | public get commandValues(): { runForever: DcMotor.CommandValue, runTimed: DcMotor.CommandValue, runDirect: DcMotor.CommandValue, stop: DcMotor.CommandValue } { 645 | return { 646 | runForever: "run-forever", 647 | runTimed: "run-timed", 648 | runDirect: "run-direct", 649 | stop: "stop" 650 | } 651 | } 652 | 653 | public get polarityValues(): { normal: DcMotor.PolarityValue, inversed: DcMotor.PolarityValue } { 654 | return { 655 | normal: "normal", 656 | inversed: "inversed" 657 | } 658 | } 659 | 660 | public get stopActionValues(): { coast: DcMotor.StopActionValue, brake: DcMotor.StopActionValue } { 661 | return { 662 | coast: "coast", 663 | brake: "brake" 664 | } 665 | } 666 | 667 | //~autogen 668 | 669 | //PROPERTIES 670 | 671 | //~autogen generic-get-set classes.dcMotor>currentClass 672 | /** 673 | * Returns the name of the port that this motor is connected to. 674 | */ 675 | get address(): string { 676 | return this.readString("address"); 677 | } 678 | 679 | /** 680 | * Sets the command for the motor. Possible values are `run-forever`, `run-timed` and 681 | * `stop`. Not all commands may be supported, so be sure to check the contents 682 | * of the `commands` attribute. 683 | */ 684 | set command(value: DcMotor.CommandValue) { 685 | this.setString("command", value); 686 | } 687 | 688 | /** 689 | * Returns a list of commands supported by the motor 690 | * controller. 691 | */ 692 | get commands(): string[] { 693 | return this.readStringArray("commands"); 694 | } 695 | 696 | /** 697 | * Returns the name of the motor driver that loaded this device. See the list 698 | * of [supported devices] for a list of drivers. 699 | */ 700 | get driverName(): string { 701 | return this.readString("driver_name"); 702 | } 703 | 704 | /** 705 | * Shows the current duty cycle of the PWM signal sent to the motor. Values 706 | * are -100 to 100 (-100% to 100%). 707 | */ 708 | get dutyCycle(): number { 709 | return this.readNumber("duty_cycle"); 710 | } 711 | 712 | /** 713 | * Writing sets the duty cycle setpoint of the PWM signal sent to the motor. 714 | * Valid values are -100 to 100 (-100% to 100%). Reading returns the current 715 | * setpoint. 716 | */ 717 | get dutyCycleSp(): number { 718 | return this.readNumber("duty_cycle_sp"); 719 | } 720 | /** 721 | * Writing sets the duty cycle setpoint of the PWM signal sent to the motor. 722 | * Valid values are -100 to 100 (-100% to 100%). Reading returns the current 723 | * setpoint. 724 | */ 725 | set dutyCycleSp(value: number) { 726 | this.setNumber("duty_cycle_sp", value); 727 | } 728 | 729 | /** 730 | * Sets the polarity of the motor. Valid values are `normal` and `inversed`. 731 | */ 732 | get polarity(): DcMotor.PolarityValue { 733 | return this.readStringAsType("polarity"); 734 | } 735 | /** 736 | * Sets the polarity of the motor. Valid values are `normal` and `inversed`. 737 | */ 738 | set polarity(value: DcMotor.PolarityValue) { 739 | this.setString("polarity", value); 740 | } 741 | 742 | /** 743 | * Sets the time in milliseconds that it take the motor to ramp down from 100% 744 | * to 0%. Valid values are 0 to 10000 (10 seconds). Default is 0. 745 | */ 746 | get rampDownSp(): number { 747 | return this.readNumber("ramp_down_sp"); 748 | } 749 | /** 750 | * Sets the time in milliseconds that it take the motor to ramp down from 100% 751 | * to 0%. Valid values are 0 to 10000 (10 seconds). Default is 0. 752 | */ 753 | set rampDownSp(value: number) { 754 | this.setNumber("ramp_down_sp", value); 755 | } 756 | 757 | /** 758 | * Sets the time in milliseconds that it take the motor to up ramp from 0% to 759 | * 100%. Valid values are 0 to 10000 (10 seconds). Default is 0. 760 | */ 761 | get rampUpSp(): number { 762 | return this.readNumber("ramp_up_sp"); 763 | } 764 | /** 765 | * Sets the time in milliseconds that it take the motor to up ramp from 0% to 766 | * 100%. Valid values are 0 to 10000 (10 seconds). Default is 0. 767 | */ 768 | set rampUpSp(value: number) { 769 | this.setNumber("ramp_up_sp", value); 770 | } 771 | 772 | /** 773 | * Gets a list of flags indicating the motor status. Possible 774 | * flags are `running` and `ramping`. `running` indicates that the motor is 775 | * powered. `ramping` indicates that the motor has not yet reached the 776 | * `duty_cycle_sp`. 777 | */ 778 | get state(): string[] { 779 | return this.readStringArray("state"); 780 | } 781 | 782 | /** 783 | * Sets the stop action that will be used when the motor stops. Read 784 | * `stop_actions` to get the list of valid values. 785 | */ 786 | set stopAction(value: DcMotor.StopActionValue) { 787 | this.setString("stop_action", value); 788 | } 789 | 790 | /** 791 | * Gets a list of stop actions. Valid values are `coast` 792 | * and `brake`. 793 | */ 794 | get stopActions(): string[] { 795 | return this.readStringArray("stop_actions"); 796 | } 797 | 798 | /** 799 | * Writing specifies the amount of time the motor will run when using the 800 | * `run-timed` command. Reading returns the current value. Units are in 801 | * milliseconds. 802 | */ 803 | get timeSp(): number { 804 | return this.readNumber("time_sp"); 805 | } 806 | /** 807 | * Writing specifies the amount of time the motor will run when using the 808 | * `run-timed` command. Reading returns the current value. Units are in 809 | * milliseconds. 810 | */ 811 | set timeSp(value: number) { 812 | this.setNumber("time_sp", value); 813 | } 814 | 815 | 816 | //~autogen 817 | } 818 | 819 | //~autogen generic-class-description classes.servoMotor>currentClass 820 | /** 821 | * The servo motor class provides a uniform interface for using hobby type 822 | * servo motors. 823 | */ 824 | //~autogen 825 | export class ServoMotor extends MotorBase { 826 | 827 | constructor(port: string) { 828 | //~autogen connect-super-call classes.servoMotor>currentClass "port">extraParams "true">omitNameConvention 829 | super('servo-motor', null, port); 830 | //~autogen 831 | } 832 | 833 | //~autogen property-value-constants classes.servoMotor>currentClass 834 | 835 | public get commandValues(): { run: ServoMotor.CommandValue, float: ServoMotor.CommandValue } { 836 | return { 837 | run: "run", 838 | float: "float" 839 | } 840 | } 841 | 842 | public get polarityValues(): { normal: ServoMotor.PolarityValue, inversed: ServoMotor.PolarityValue } { 843 | return { 844 | normal: "normal", 845 | inversed: "inversed" 846 | } 847 | } 848 | 849 | //~autogen 850 | 851 | //PROPERTIES 852 | 853 | //~autogen generic-get-set classes.servoMotor>currentClass 854 | /** 855 | * Returns the name of the port that this motor is connected to. 856 | */ 857 | get address(): string { 858 | return this.readString("address"); 859 | } 860 | 861 | /** 862 | * Sets the command for the servo. Valid values are `run` and `float`. Setting 863 | * to `run` will cause the servo to be driven to the position_sp set in the 864 | * `position_sp` attribute. Setting to `float` will remove power from the motor. 865 | */ 866 | set command(value: ServoMotor.CommandValue) { 867 | this.setString("command", value); 868 | } 869 | 870 | /** 871 | * Returns the name of the motor driver that loaded this device. See the list 872 | * of [supported devices] for a list of drivers. 873 | */ 874 | get driverName(): string { 875 | return this.readString("driver_name"); 876 | } 877 | 878 | /** 879 | * Used to set the pulse size in milliseconds for the signal that tells the 880 | * servo to drive to the maximum (clockwise) position_sp. Default value is 2400. 881 | * Valid values are 2300 to 2700. You must write to the position_sp attribute for 882 | * changes to this attribute to take effect. 883 | */ 884 | get maxPulseSp(): number { 885 | return this.readNumber("max_pulse_sp"); 886 | } 887 | /** 888 | * Used to set the pulse size in milliseconds for the signal that tells the 889 | * servo to drive to the maximum (clockwise) position_sp. Default value is 2400. 890 | * Valid values are 2300 to 2700. You must write to the position_sp attribute for 891 | * changes to this attribute to take effect. 892 | */ 893 | set maxPulseSp(value: number) { 894 | this.setNumber("max_pulse_sp", value); 895 | } 896 | 897 | /** 898 | * Used to set the pulse size in milliseconds for the signal that tells the 899 | * servo to drive to the mid position_sp. Default value is 1500. Valid 900 | * values are 1300 to 1700. For example, on a 180 degree servo, this would be 901 | * 90 degrees. On continuous rotation servo, this is the 'neutral' position_sp 902 | * where the motor does not turn. You must write to the position_sp attribute for 903 | * changes to this attribute to take effect. 904 | */ 905 | get midPulseSp(): number { 906 | return this.readNumber("mid_pulse_sp"); 907 | } 908 | /** 909 | * Used to set the pulse size in milliseconds for the signal that tells the 910 | * servo to drive to the mid position_sp. Default value is 1500. Valid 911 | * values are 1300 to 1700. For example, on a 180 degree servo, this would be 912 | * 90 degrees. On continuous rotation servo, this is the 'neutral' position_sp 913 | * where the motor does not turn. You must write to the position_sp attribute for 914 | * changes to this attribute to take effect. 915 | */ 916 | set midPulseSp(value: number) { 917 | this.setNumber("mid_pulse_sp", value); 918 | } 919 | 920 | /** 921 | * Used to set the pulse size in milliseconds for the signal that tells the 922 | * servo to drive to the miniumum (counter-clockwise) position_sp. Default value 923 | * is 600. Valid values are 300 to 700. You must write to the position_sp 924 | * attribute for changes to this attribute to take effect. 925 | */ 926 | get minPulseSp(): number { 927 | return this.readNumber("min_pulse_sp"); 928 | } 929 | /** 930 | * Used to set the pulse size in milliseconds for the signal that tells the 931 | * servo to drive to the miniumum (counter-clockwise) position_sp. Default value 932 | * is 600. Valid values are 300 to 700. You must write to the position_sp 933 | * attribute for changes to this attribute to take effect. 934 | */ 935 | set minPulseSp(value: number) { 936 | this.setNumber("min_pulse_sp", value); 937 | } 938 | 939 | /** 940 | * Sets the polarity of the servo. Valid values are `normal` and `inversed`. 941 | * Setting the value to `inversed` will cause the position_sp value to be 942 | * inversed. i.e `-100` will correspond to `max_pulse_sp`, and `100` will 943 | * correspond to `min_pulse_sp`. 944 | */ 945 | get polarity(): ServoMotor.PolarityValue { 946 | return this.readStringAsType("polarity"); 947 | } 948 | /** 949 | * Sets the polarity of the servo. Valid values are `normal` and `inversed`. 950 | * Setting the value to `inversed` will cause the position_sp value to be 951 | * inversed. i.e `-100` will correspond to `max_pulse_sp`, and `100` will 952 | * correspond to `min_pulse_sp`. 953 | */ 954 | set polarity(value: ServoMotor.PolarityValue) { 955 | this.setString("polarity", value); 956 | } 957 | 958 | /** 959 | * Reading returns the current position_sp of the servo. Writing instructs the 960 | * servo to move to the specified position_sp. Units are percent. Valid values 961 | * are -100 to 100 (-100% to 100%) where `-100` corresponds to `min_pulse_sp`, 962 | * `0` corresponds to `mid_pulse_sp` and `100` corresponds to `max_pulse_sp`. 963 | */ 964 | get positionSp(): number { 965 | return this.readNumber("position_sp"); 966 | } 967 | /** 968 | * Reading returns the current position_sp of the servo. Writing instructs the 969 | * servo to move to the specified position_sp. Units are percent. Valid values 970 | * are -100 to 100 (-100% to 100%) where `-100` corresponds to `min_pulse_sp`, 971 | * `0` corresponds to `mid_pulse_sp` and `100` corresponds to `max_pulse_sp`. 972 | */ 973 | set positionSp(value: number) { 974 | this.setNumber("position_sp", value); 975 | } 976 | 977 | /** 978 | * Sets the rate_sp at which the servo travels from 0 to 100.0% (half of the full 979 | * range of the servo). Units are in milliseconds. Example: Setting the rate_sp 980 | * to 1000 means that it will take a 180 degree servo 2 second to move from 0 981 | * to 180 degrees. Note: Some servo controllers may not support this in which 982 | * case reading and writing will fail with `-EOPNOTSUPP`. In continuous rotation 983 | * servos, this value will affect the rate_sp at which the speed ramps up or down. 984 | */ 985 | get rateSp(): number { 986 | return this.readNumber("rate_sp"); 987 | } 988 | /** 989 | * Sets the rate_sp at which the servo travels from 0 to 100.0% (half of the full 990 | * range of the servo). Units are in milliseconds. Example: Setting the rate_sp 991 | * to 1000 means that it will take a 180 degree servo 2 second to move from 0 992 | * to 180 degrees. Note: Some servo controllers may not support this in which 993 | * case reading and writing will fail with `-EOPNOTSUPP`. In continuous rotation 994 | * servos, this value will affect the rate_sp at which the speed ramps up or down. 995 | */ 996 | set rateSp(value: number) { 997 | this.setNumber("rate_sp", value); 998 | } 999 | 1000 | /** 1001 | * Returns a list of flags indicating the state of the servo. 1002 | * Possible values are: 1003 | * * `running`: Indicates that the motor is powered. 1004 | */ 1005 | get state(): string[] { 1006 | return this.readStringArray("state"); 1007 | } 1008 | 1009 | 1010 | //~autogen 1011 | } -------------------------------------------------------------------------------- /src/sensors.ts: -------------------------------------------------------------------------------- 1 | import IO = require('./io'); 2 | import Device = IO.Device; 3 | import IndexedDevice = IO.IndexedDevice; 4 | 5 | export class SensorBase extends IndexedDevice { 6 | constructor(driverTypeDirName: string, nameConvention?: string, targetAddress?: string, targetDriverName?: string | string[]) { 7 | super(driverTypeDirName, nameConvention, targetAddress, targetDriverName); 8 | } 9 | } 10 | 11 | //~autogen generic-class-description classes.sensor>currentClass 12 | /** 13 | * The sensor class provides a uniform interface for using most of the 14 | * sensors available for the EV3. The various underlying device drivers will 15 | * create a `lego-sensor` device for interacting with the sensors. 16 | * 17 | * Sensors are primarily controlled by setting the `mode` and monitored by 18 | * reading the `value` attributes. Values can be converted to floating point 19 | * if needed by `value` / 10.0 ^ `decimals`. 20 | * 21 | * Since the name of the `sensor` device node does not correspond to the port 22 | * that a sensor is plugged in to, you must look at the `address` attribute if 23 | * you need to know which port a sensor is plugged in to. However, if you don't 24 | * have more than one sensor of each type, you can just look for a matching 25 | * `driver_name`. Then it will not matter which port a sensor is plugged in to - your 26 | * program will still work. 27 | */ 28 | //~autogen 29 | export class Sensor extends SensorBase { 30 | 31 | constructor(port?: string, driverNames?: string[]| string) { 32 | //~autogen connect-super-call classes.sensor>currentClass "port,driverNames">extraParams 33 | super('lego-sensor', 'sensor(\\d*)', port,driverNames); 34 | //~autogen 35 | 36 | } 37 | 38 | public getValue(valueIndex: number): number { 39 | return this.readNumber("value" + valueIndex); 40 | } 41 | 42 | public getFloatValue(valueIndex: number): number { 43 | return this.getValue(valueIndex) / Math.pow(10, this.decimals); 44 | } 45 | 46 | //PROPERTIES 47 | //~autogen generic-get-set classes.sensor>currentClass 48 | /** 49 | * Returns the name of the port that the sensor is connected to, e.g. `ev3:in1`. 50 | * I2C sensors also include the I2C address (decimal), e.g. `ev3:in1:i2c8`. 51 | */ 52 | get address(): string { 53 | return this.readString("address"); 54 | } 55 | 56 | /** 57 | * Sends a command to the sensor. 58 | */ 59 | set command(value: string) { 60 | this.setString("command", value); 61 | } 62 | 63 | /** 64 | * Returns a list of the valid commands for the sensor. 65 | * Returns -EOPNOTSUPP if no commands are supported. 66 | */ 67 | get commands(): string[] { 68 | return this.readStringArray("commands"); 69 | } 70 | 71 | /** 72 | * Returns the number of decimal places for the values in the `value` 73 | * attributes of the current mode. 74 | */ 75 | get decimals(): number { 76 | return this.readNumber("decimals"); 77 | } 78 | 79 | /** 80 | * Returns the name of the sensor device/driver. See the list of [supported 81 | * sensors] for a complete list of drivers. 82 | */ 83 | get driverName(): string { 84 | return this.readString("driver_name"); 85 | } 86 | 87 | /** 88 | * Returns the current mode. Writing one of the values returned by `modes` 89 | * sets the sensor to that mode. 90 | */ 91 | get mode(): string { 92 | return this.readString("mode"); 93 | } 94 | /** 95 | * Returns the current mode. Writing one of the values returned by `modes` 96 | * sets the sensor to that mode. 97 | */ 98 | set mode(value: string) { 99 | this.setString("mode", value); 100 | } 101 | 102 | /** 103 | * Returns a list of the valid modes for the sensor. 104 | */ 105 | get modes(): string[] { 106 | return this.readStringArray("modes"); 107 | } 108 | 109 | /** 110 | * Returns the number of `value` attributes that will return a valid value 111 | * for the current mode. 112 | */ 113 | get numValues(): number { 114 | return this.readNumber("num_values"); 115 | } 116 | 117 | /** 118 | * Returns the units of the measured value for the current mode. May return 119 | * empty string 120 | */ 121 | get units(): string { 122 | return this.readString("units"); 123 | } 124 | 125 | 126 | //~autogen 127 | } 128 | 129 | //~autogen sensor-helper-classes 130 | 131 | /** 132 | * Touch Sensor 133 | */ 134 | export class TouchSensor extends Sensor { 135 | constructor(port?: string) { 136 | super(port, ["lego-ev3-touch","lego-nxt-touch"]); 137 | } 138 | 139 | 140 | /** 141 | * A boolean indicating whether the current touch sensor is being 142 | * pressed. 143 | */ 144 | get isPressed(): boolean { 145 | this.mode = 'TOUCH'; 146 | return Boolean(this.getFloatValue(0)); 147 | } 148 | 149 | } 150 | 151 | /** 152 | * LEGO EV3 color sensor. 153 | */ 154 | export class ColorSensor extends Sensor { 155 | constructor(port?: string) { 156 | super(port, ["lego-ev3-color"]); 157 | } 158 | 159 | 160 | /** 161 | * Reflected light intensity as a percentage. Light on sensor is red. 162 | */ 163 | get reflectedLightIntensity(): number { 164 | this.mode = 'COL-REFLECT'; 165 | return Number(this.getFloatValue(0)); 166 | } 167 | 168 | /** 169 | * Ambient light intensity. Light on sensor is dimly lit blue. 170 | */ 171 | get ambientLightIntensity(): number { 172 | this.mode = 'COL-AMBIENT'; 173 | return Number(this.getFloatValue(0)); 174 | } 175 | 176 | /** 177 | * Color detected by the sensor, categorized by overall value. 178 | * - 0: No color 179 | * - 1: Black 180 | * - 2: Blue 181 | * - 3: Green 182 | * - 4: Yellow 183 | * - 5: Red 184 | * - 6: White 185 | * - 7: Brown 186 | */ 187 | get color(): number { 188 | this.mode = 'COL-COLOR'; 189 | return Number(this.getFloatValue(0)); 190 | } 191 | 192 | /** 193 | * Red component of the detected color, in the range 0-1020. 194 | */ 195 | get red(): number { 196 | this.mode = 'RGB-RAW'; 197 | return Number(this.getFloatValue(0)); 198 | } 199 | 200 | /** 201 | * Green component of the detected color, in the range 0-1020. 202 | */ 203 | get green(): number { 204 | this.mode = 'RGB-RAW'; 205 | return Number(this.getFloatValue(1)); 206 | } 207 | 208 | /** 209 | * Blue component of the detected color, in the range 0-1020. 210 | */ 211 | get blue(): number { 212 | this.mode = 'RGB-RAW'; 213 | return Number(this.getFloatValue(2)); 214 | } 215 | 216 | } 217 | 218 | /** 219 | * LEGO EV3 ultrasonic sensor. 220 | */ 221 | export class UltrasonicSensor extends Sensor { 222 | constructor(port?: string) { 223 | super(port, ["lego-ev3-us","lego-nxt-us"]); 224 | } 225 | 226 | 227 | /** 228 | * Measurement of the distance detected by the sensor, 229 | * in centimeters. 230 | */ 231 | get distanceCentimeters(): number { 232 | this.mode = 'US-DIST-CM'; 233 | return Number(this.getFloatValue(0)); 234 | } 235 | 236 | /** 237 | * Measurement of the distance detected by the sensor, 238 | * in inches. 239 | */ 240 | get distanceInches(): number { 241 | this.mode = 'US-DIST-IN'; 242 | return Number(this.getFloatValue(0)); 243 | } 244 | 245 | /** 246 | * Value indicating whether another ultrasonic sensor could 247 | * be heard nearby. 248 | */ 249 | get otherSensorPresent(): boolean { 250 | this.mode = 'US-LISTEN'; 251 | return Boolean(this.getFloatValue(0)); 252 | } 253 | 254 | } 255 | 256 | /** 257 | * LEGO EV3 gyro sensor. 258 | */ 259 | export class GyroSensor extends Sensor { 260 | constructor(port?: string) { 261 | super(port, ["lego-ev3-gyro"]); 262 | } 263 | 264 | 265 | /** 266 | * The number of degrees that the sensor has been rotated 267 | * since it was put into this mode. 268 | */ 269 | get angle(): number { 270 | this.mode = 'GYRO-ANG'; 271 | return Number(this.getFloatValue(0)); 272 | } 273 | 274 | /** 275 | * The rate at which the sensor is rotating, in degrees/second. 276 | */ 277 | get rate(): number { 278 | this.mode = 'GYRO-RATE'; 279 | return Number(this.getFloatValue(0)); 280 | } 281 | 282 | } 283 | 284 | /** 285 | * LEGO EV3 infrared sensor. 286 | */ 287 | export class InfraredSensor extends Sensor { 288 | constructor(port?: string) { 289 | super(port, ["lego-ev3-ir"]); 290 | } 291 | 292 | 293 | /** 294 | * A measurement of the distance between the sensor and the remote, 295 | * as a percentage. 100% is approximately 70cm/27in. 296 | */ 297 | get proximity(): number { 298 | this.mode = 'IR-PROX'; 299 | return Number(this.getFloatValue(0)); 300 | } 301 | 302 | } 303 | 304 | /** 305 | * LEGO NXT Sound Sensor 306 | */ 307 | export class SoundSensor extends Sensor { 308 | constructor(port?: string) { 309 | super(port, ["lego-nxt-sound"]); 310 | } 311 | 312 | 313 | /** 314 | * A measurement of the measured sound pressure level, as a 315 | * percent. Uses a flat weighting. 316 | */ 317 | get soundPressure(): number { 318 | this.mode = 'DB'; 319 | return Number(this.getFloatValue(0)); 320 | } 321 | 322 | /** 323 | * A measurement of the measured sound pressure level, as a 324 | * percent. Uses A-weighting, which focuses on levels up to 55 dB. 325 | */ 326 | get soundPressureLow(): number { 327 | this.mode = 'DBA'; 328 | return Number(this.getFloatValue(0)); 329 | } 330 | 331 | } 332 | 333 | /** 334 | * LEGO NXT Light Sensor 335 | */ 336 | export class LightSensor extends Sensor { 337 | constructor(port?: string) { 338 | super(port, ["lego-nxt-light"]); 339 | } 340 | 341 | 342 | /** 343 | * A measurement of the reflected light intensity, as a percentage. 344 | */ 345 | get reflectedLightIntensity(): number { 346 | this.mode = 'REFLECT'; 347 | return Number(this.getFloatValue(0)); 348 | } 349 | 350 | /** 351 | * A measurement of the ambient light intensity, as a percentage. 352 | */ 353 | get ambientLightIntensity(): number { 354 | this.mode = 'AMBIENT'; 355 | return Number(this.getFloatValue(0)); 356 | } 357 | 358 | } 359 | 360 | //~autogen 361 | 362 | //~autogen generic-class-description classes.i2cSensor>currentClass 363 | /** 364 | * A generic interface to control I2C-type EV3 sensors. 365 | */ 366 | //~autogen 367 | export class I2CSensor extends Sensor { 368 | constructor(port?: string, driverNames?: string[]) { 369 | super(port, driverNames); 370 | } 371 | 372 | //~autogen generic-get-set classes.i2cSensor>currentClass 373 | /** 374 | * Returns the firmware version of the sensor if available. Currently only 375 | * I2C/NXT sensors support this. 376 | */ 377 | get fwVersion(): string { 378 | return this.readString("fw_version"); 379 | } 380 | 381 | /** 382 | * Returns the polling period of the sensor in milliseconds. Writing sets the 383 | * polling period. Setting to 0 disables polling. Minimum value is hard 384 | * coded as 50 msec. Returns -EOPNOTSUPP if changing polling is not supported. 385 | * Currently only I2C/NXT sensors support changing the polling period. 386 | */ 387 | get pollMs(): number { 388 | return this.readNumber("poll_ms"); 389 | } 390 | /** 391 | * Returns the polling period of the sensor in milliseconds. Writing sets the 392 | * polling period. Setting to 0 disables polling. Minimum value is hard 393 | * coded as 50 msec. Returns -EOPNOTSUPP if changing polling is not supported. 394 | * Currently only I2C/NXT sensors support changing the polling period. 395 | */ 396 | set pollMs(value: number) { 397 | this.setNumber("poll_ms", value); 398 | } 399 | 400 | 401 | //~autogen 402 | } -------------------------------------------------------------------------------- /templates/autogen-header.liquid: -------------------------------------------------------------------------------- 1 | // Sections of the following code were auto-generated based on spec v{{ meta.version }}{% if meta.specRevision %}, rev {{meta.specRevision}}{% endif %}. 2 | -------------------------------------------------------------------------------- /templates/connect-super-call.liquid: -------------------------------------------------------------------------------- 1 | {% assign conventionRegex = currentClass.systemDeviceNameConvention | replace: '\{\d\}', '(\\d*)' 2 | %}{% assign paramLength = extraParams | size 3 | %} super('{{currentClass.systemClassName}}', {% if omitNameConvention %}null{% else %}'{{conventionRegex}}'{% endif %}{% if paramLength > 0 %}, {{extraParams}}{% endif %}); -------------------------------------------------------------------------------- /templates/def-string-literal-types.liquid: -------------------------------------------------------------------------------- 1 | export module {{ currentClass.friendlyName | camel_case | capitalize }} { 2 | {% for propertyVals in currentClass.propertyValues %}{% 3 | assign type = '' %}{% 4 | for propVal in propertyVals.values %}{% 5 | assign type = type | append: "'" %}{% 6 | assign type = type | append: propVal.name %}{% 7 | assign type = type | append: "' " %}{% 8 | endfor %}{% 9 | assign type = type | trim | replace: ' ', ' | ' %} 10 | export type {{ propertyVals.propertyName | camel_case | capitalize }}Value = {{ type }};{% 11 | endfor %} 12 | } -------------------------------------------------------------------------------- /templates/export-string-literal-types.liquid: -------------------------------------------------------------------------------- 1 | export module {{ currentClass.friendlyName | camel_case | capitalize }} { 2 | {% for propertyVals in currentClass.propertyValues %} 3 | export type {{ propertyVals.propertyName | camel_case | capitalize }}Value = {{ module }}.{{ currentClass.friendlyName | camel_case | capitalize }}.{{ propertyVals.propertyName | camel_case | capitalize }}Value;{% 4 | endfor %} 5 | } -------------------------------------------------------------------------------- /templates/generic-class-description.liquid: -------------------------------------------------------------------------------- 1 | /** {% for line in currentClass.description %} 2 | * {{ line }}{% 3 | endfor %} 4 | */ -------------------------------------------------------------------------------- /templates/generic-get-set.liquid: -------------------------------------------------------------------------------- 1 | {% for prop in currentClass.systemProperties %}{% 2 | assign specialReadCall = '' %}{% 3 | if prop.type contains 'int' or prop.type contains 'float' %}{% 4 | assign type = 'number' %}{% 5 | assign typeCaps = 'Number' %}{% 6 | else %}{% 7 | assign type = 'string' %}{% 8 | for propertyVals in currentClass.propertyValues %}{% 9 | if propertyVals.propertyName == prop.name %}{% 10 | assign typeQualifier = currentClass.friendlyName | camel_case | capitalize | append: '.' %}{% 11 | assign type = prop.name | camel_case | capitalize | append: 'Value' | prepend: typeQualifier %}{% 12 | assign specialReadCall = 'AsType<' | append: type | append: '>' %}{% 13 | endif %}{% 14 | endfor %}{% 15 | assign typeCaps = 'String' %}{% 16 | endif %}{% 17 | if prop.type contains 'array' %}{% 18 | assign array = '[]' %}{% 19 | else %}{% 20 | assign array = blank %}{% 21 | endif %}{% 22 | 23 | if prop.readAccess == true 24 | %} /**{% for line in prop.description 25 | %} 26 | * {{ line }}{% 27 | endfor %} 28 | */ 29 | get {{ prop.name | camel_case }}(): {{ type }}{{ array }} { 30 | {% if array 31 | %} return this.read{{ typeCaps }}Array{{ specialReadCall }}("{{ prop.systemName }}");{% 32 | elsif prop.type contains 'selector' 33 | %} return this.read{{ typeCaps }}Selector("{{ prop.systemName }}");{% 34 | else 35 | %} return this.read{{ typeCaps }}{{ specialReadCall }}("{{ prop.systemName }}");{% 36 | endif %} 37 | } 38 | {% 39 | endif %}{% 40 | if prop.writeAccess == true 41 | %} /**{% for line in prop.description 42 | %} 43 | * {{ line }}{% 44 | endfor %} 45 | */ 46 | set {{ prop.name | camel_case }}(value: {{type}}) { 47 | this.set{{ typeCaps }}("{{ prop.systemName }}", value); 48 | } 49 | {% endif %} 50 | {% endfor %} -------------------------------------------------------------------------------- /templates/led-platform-class.liquid: -------------------------------------------------------------------------------- 1 | export class {{ currentPlatformClassName }} { 2 | {% for instanceInfo in currentPlatform.led.instances %} 3 | public static {{ instanceInfo.name | camel_case }} = new extras.LED("{{instanceInfo.systemName}}");{% 4 | endfor %} 5 | {% 6 | for groupInfo in currentPlatform.led.groups %} 7 | public static {{ groupInfo.name | camel_case }} = new extras.LEDGroup({{ groupInfo.entries | camel_case | prepend_all: '.' | prepend_all: currentPlatformClassName | join: ', ' }});{% 8 | endfor %} 9 | {% 10 | for colorInfo in currentPlatform.led.colors %} 11 | public static {{ colorInfo.name | camel_case }}Color = [{{ colorInfo.value | join: ', ' }}];{% 12 | endfor %} 13 | 14 | public static get isConnected(): boolean { 15 | return {{ currentPlatform.led.instances | select: 'name' | camel_case | prepend_all: '.' | prepend_all: currentPlatformClassName | append_all: '.connected' | join: ' && ' }}; 16 | } 17 | } -------------------------------------------------------------------------------- /templates/property-value-constants.liquid: -------------------------------------------------------------------------------- 1 | {% for propertyVals in currentClass.propertyValues %}{% 2 | assign type = '' %}{% 3 | for propVal in propertyVals.values %}{% 4 | assign valName = propVal.name | camel_case %}{% 5 | assign valNameCaps = valName | capitalize %}{% 6 | assign className = currentClass.friendlyName | camel_case | capitalize %}{% 7 | assign propName = propertyVals.propertyName | camel_case | capitalize %}{% 8 | 9 | assign type = type | append: valName %}{% 10 | assign type = type | append: ": " %}{% 11 | 12 | assign type = type | append: className %}{% 13 | assign type = type | append: "." %}{% 14 | assign type = type | append: propName %}{% 15 | assign type = type | append: "Value" %}{% 16 | 17 | unless forloop.last %}{% 18 | assign type = type | append: ", " %}{% 19 | endunless %}{% 20 | endfor %} 21 | public get {{ propertyVals.propertyName | camel_case }}Values(): { {{ type }} } { 22 | return { {% 23 | for propVal in propertyVals.values %} 24 | {{ propVal.name | camel_case }}: "{{ propVal.name }}"{% unless forloop.last %},{% endunless %}{% 25 | endfor %} 26 | } 27 | } 28 | {% 29 | endfor %} -------------------------------------------------------------------------------- /templates/sensor-helper-classes.liquid: -------------------------------------------------------------------------------- 1 | {% for currentClassMetadata in specialSensorTypes %}{% 2 | assign currentClass = currentClassMetadata[1] 3 | %} 4 | /** {% for line in currentClass.description %} 5 | * {{ line }}{% 6 | endfor %} 7 | */ 8 | export class {{ currentClass.friendlyName | camel_case | capitalize }} extends Sensor { 9 | constructor(port?: string) { 10 | super(port, {{ currentClass.driverName | json_stringify }}); 11 | } 12 | 13 | {% for valueMapping in currentClass.sensorValueMappings %}{% 14 | if valueMapping.type contains 'int' or valueMapping.type contains 'float' %}{% 15 | assign type = 'number' %}{% 16 | elsif valueMapping.type contains 'boolean' or valueMapping.type contains 'bool' %}{% 17 | assign type = 'boolean' %}{% 18 | else %}{% 19 | assign type = 'string' %}{% 20 | endif %} 21 | /**{% for line in valueMapping.description 22 | %} 23 | * {{ line }}{% 24 | endfor %} 25 | */ 26 | get {{ valueMapping.name | camel_case }}(): {{ type }} { 27 | this.mode = '{{ valueMapping.requiredMode }}'; 28 | return {{ type | capitalize }}(this.getFloatValue({{ valueMapping.sourceValue }})); 29 | } 30 | {% endfor %} 31 | } 32 | {% endfor %} -------------------------------------------------------------------------------- /test/device-tests.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var ev3dev = require('../bin/index.js'); 3 | var utils = require('./test-utils.js'); 4 | var path = require('path'); 5 | 6 | var touchSensorData = { 7 | name: "touch_sensor", 8 | index: 0 9 | } 10 | 11 | describe('Device', function () { 12 | this.timeout(4000); 13 | 14 | before(function () { 15 | ev3dev.Device.overrideSysClassDir = path.join(__dirname, "fake-sys", "arena"); 16 | }); 17 | 18 | it('should connect to any device if no criteria are provided', function (done) { 19 | utils.populateArena({ 20 | "in1": touchSensorData 21 | }, function (pathMapping) { 22 | 23 | var currentDevice = new ev3dev.Device(); 24 | currentDevice.connect("lego-sensor", "sensor(\\d*)"); 25 | 26 | utils.assertDeviceConnected(currentDevice, pathMapping["in1"].path); 27 | 28 | done(); 29 | }); 30 | }); 31 | 32 | it('should connect to a specific device if a single criterion is specified', function (done) { 33 | utils.populateArena({ 34 | "in3": touchSensorData 35 | }, function (pathMapping) { 36 | 37 | var currentDevice = new ev3dev.Device(); 38 | currentDevice.connect("lego-sensor", "sensor(\\d*)", { 39 | address: "in3" 40 | }); 41 | 42 | utils.assertDeviceConnected(currentDevice, pathMapping["in3"].path); 43 | 44 | done(); 45 | }); 46 | }); 47 | 48 | it('should not connect to any device if no devices matching criteria are found', function (done) { 49 | utils.populateArena({ 50 | "in3": touchSensorData 51 | }, function (pathMapping) { 52 | 53 | var currentDevice = new ev3dev.Device(); 54 | currentDevice.connect("lego-sensor", "sensor(\\d*)", { 55 | address: "thisDoesntExist" 56 | }); 57 | 58 | assert.equal(currentDevice.connected, false); 59 | 60 | done(); 61 | }); 62 | }); 63 | }); -------------------------------------------------------------------------------- /test/motor-tests.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var ev3dev = require('../bin/index.js'); 3 | var utils = require('./test-utils.js'); 4 | var path = require('path'); 5 | 6 | var motorData = { 7 | name: "medium_motor", 8 | index: 0 9 | } 10 | 11 | describe('Motor', function () { 12 | this.timeout(4000); 13 | 14 | before(function () { 15 | ev3dev.Device.overrideSysClassDir = path.join(__dirname, "fake-sys", "arena"); 16 | }); 17 | 18 | it('should connect to a specified port', function (done) { 19 | utils.populateArena({ 20 | "outA": motorData 21 | }, function (pathMapping) { 22 | 23 | var currentMotor = new ev3dev.Motor("outA"); 24 | utils.assertDeviceConnected(currentMotor, pathMapping["outA"].path); 25 | 26 | done(); 27 | }); 28 | }); 29 | 30 | it('should properly set properties', function (done) { 31 | utils.populateArena({ 32 | "outA": motorData 33 | }, function (pathMapping) { 34 | 35 | var currentMotor = new ev3dev.Motor("outA"); 36 | utils.assertDeviceConnected(currentMotor, pathMapping["outA"].path); 37 | 38 | currentMotor.speedSp = 400; 39 | assert.equal(utils.getDeviceProperty(currentMotor, "speed_sp"), 400); 40 | 41 | currentMotor.command = "run-forever"; 42 | assert.equal(utils.getDeviceProperty(currentMotor, "command"), "run-forever"); 43 | 44 | currentMotor.rampDownSp = 150; 45 | assert.equal(utils.getDeviceProperty(currentMotor, "ramp_down_sp"), 150); 46 | 47 | done(); 48 | }); 49 | }); 50 | 51 | it('should properly read properties', function (done) { 52 | utils.populateArena({ 53 | "outA": motorData 54 | }, function (pathMapping) { 55 | 56 | var currentMotor = new ev3dev.Motor("outA"); 57 | utils.assertDeviceConnected(currentMotor, pathMapping["outA"].path); 58 | 59 | utils.setDeviceProperty(currentMotor, "speed", 300); 60 | assert.equal(currentMotor.speed, 300); 61 | 62 | utils.setDeviceProperty(currentMotor, "polarity", "inversed"); 63 | assert.equal(currentMotor.polarity, "inversed"); 64 | 65 | utils.setDeviceProperty(currentMotor, "commands", "run-forever run-timed run-direct"); 66 | assert.deepEqual(currentMotor.commands, ["run-forever", "run-timed", "run-direct"]); 67 | 68 | done(); 69 | }); 70 | }); 71 | 72 | it('should properly start motor via helper functions', function (done) { 73 | utils.populateArena({ 74 | "outA": motorData 75 | }, function (pathMapping) { 76 | 77 | var currentMotor = new ev3dev.Motor("outA"); 78 | utils.assertDeviceConnected(currentMotor, pathMapping["outA"].path); 79 | 80 | currentMotor.start(700, currentMotor.stopActionValues.hold); 81 | assert.equal(utils.getDeviceProperty(currentMotor, "speed_sp"), 700); 82 | assert.equal(utils.getDeviceProperty(currentMotor, "command"), "run-forever"); 83 | assert.equal(utils.getDeviceProperty(currentMotor, "stop_action"), "hold"); 84 | 85 | currentMotor.runForDistance(720, 800, currentMotor.stopActionValues.brake); 86 | assert.equal(utils.getDeviceProperty(currentMotor, "position_sp"), 720); 87 | assert.equal(utils.getDeviceProperty(currentMotor, "speed_sp"), 800); 88 | assert.equal(utils.getDeviceProperty(currentMotor, "stop_action"), "brake"); 89 | assert.equal(utils.getDeviceProperty(currentMotor, "command"), "run-to-rel-pos"); 90 | 91 | currentMotor.runToPosition(150, 900, currentMotor.stopActionValues.coast); 92 | assert.equal(utils.getDeviceProperty(currentMotor, "position_sp"), 150); 93 | assert.equal(utils.getDeviceProperty(currentMotor, "speed_sp"), 900); 94 | assert.equal(utils.getDeviceProperty(currentMotor, "stop_action"), "coast"); 95 | assert.equal(utils.getDeviceProperty(currentMotor, "command"), "run-to-abs-pos"); 96 | 97 | currentMotor.runForTime(2000, 1000, currentMotor.stopActionValues.hold); 98 | assert.equal(utils.getDeviceProperty(currentMotor, "time_sp"), 2000); 99 | assert.equal(utils.getDeviceProperty(currentMotor, "speed_sp"), 1000); 100 | assert.equal(utils.getDeviceProperty(currentMotor, "stop_action"), "hold"); 101 | assert.equal(utils.getDeviceProperty(currentMotor, "command"), "run-timed"); 102 | 103 | done(); 104 | }); 105 | }); 106 | 107 | it('should expose constants for string properties', function (done) { 108 | utils.populateArena({ 109 | "outA": motorData 110 | }, function (pathMapping) { 111 | 112 | var currentMotor = new ev3dev.Motor("outA"); 113 | utils.assertDeviceConnected(currentMotor, pathMapping["outA"].path); 114 | 115 | // Sampling of values; if one of them is broken, they all probably are. 116 | assert.equal(currentMotor.commandValues.runForever, "run-forever"); 117 | assert.equal(currentMotor.encoderPolarityValues.inversed, "inversed"); 118 | 119 | done(); 120 | }); 121 | }); 122 | }); -------------------------------------------------------------------------------- /test/sensor-tests.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var ev3dev = require('../bin/index.js'); 3 | var utils = require('./test-utils.js'); 4 | var path = require('path'); 5 | 6 | var touchSensorData = { 7 | name: "touch_sensor", 8 | index: 0 9 | } 10 | 11 | describe('TouchSensor', function () { 12 | this.timeout(4000); 13 | 14 | before(function () { 15 | ev3dev.Device.overrideSysClassDir = path.join(__dirname, "fake-sys", "arena"); 16 | }); 17 | 18 | it('should connect to a specified port', function (done) { 19 | utils.populateArena({ 20 | "in1": touchSensorData 21 | }, function (pathMapping) { 22 | 23 | var currentSensor = new ev3dev.TouchSensor("in1"); 24 | utils.assertDeviceConnected(currentSensor, pathMapping["in1"].path); 25 | 26 | done(); 27 | }); 28 | }); 29 | 30 | it('should properly read touch state', function (done) { 31 | utils.populateArena({ 32 | "in1": touchSensorData 33 | }, function (pathMapping) { 34 | 35 | var currentSensor = new ev3dev.TouchSensor("in1"); 36 | 37 | utils.assertDeviceConnected(currentSensor, pathMapping["in1"].path); 38 | 39 | utils.setDeviceProperty(currentSensor, "value0", 0); 40 | assert.equal(currentSensor.isPressed, false, "sensor shouldn't be pressed"); 41 | 42 | utils.setDeviceProperty(currentSensor, "value0", 1); 43 | assert.equal(currentSensor.isPressed, true, "sensor should be pressed"); 44 | 45 | utils.setDeviceProperty(currentSensor, "value0", 0); 46 | assert.equal(currentSensor.isPressed, false, "sensor shouldn't be pressed"); 47 | 48 | utils.setDeviceProperty(currentSensor, "value0", 1); 49 | assert.equal(currentSensor.isPressed, true, "sensor should be pressed"); 50 | 51 | 52 | done(); 53 | }); 54 | }); 55 | }); -------------------------------------------------------------------------------- /test/test-utils.js: -------------------------------------------------------------------------------- 1 | var PythonShell = require('python-shell'); 2 | var assert = require('assert'); 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | 6 | var fakeSysRootDir = path.relative(".", path.join(__dirname, "fake-sys/")); 7 | 8 | module.exports.cleanArena = function (callback) { 9 | PythonShell.run(path.join(fakeSysRootDir, "/clean_arena.py"), function (err) { 10 | if (err) throw err; 11 | 12 | callback(); 13 | }); 14 | } 15 | 16 | module.exports.populateArena = function (deviceConfiguration, callback) { 17 | module.exports.cleanArena(function () { 18 | var deviceParams = []; 19 | for (var addressKey in deviceConfiguration) { 20 | deviceParams.push( 21 | deviceConfiguration[addressKey].name 22 | + ":" + deviceConfiguration[addressKey].index 23 | + "@" + addressKey); 24 | } 25 | 26 | var shellOptions = { 27 | args: deviceParams 28 | }; 29 | var populateShell = new PythonShell(path.join(fakeSysRootDir, "/populate_arena.py"), shellOptions); 30 | 31 | var pathMapping = {}; 32 | populateShell.on("message", function (message) { 33 | if (message.indexOf("\t") === 0) { 34 | var splitMessage = message.trim().split("\t"); 35 | 36 | assert.equal(splitMessage.length, 3); 37 | 38 | pathMapping[splitMessage[1]] = { 39 | index: Number(splitMessage[0]), 40 | path: splitMessage[2] 41 | } 42 | } 43 | else { 44 | throw new Error("Populate script returned unexpected data (probably an error message): " + message); 45 | } 46 | }); 47 | 48 | populateShell.end(function (err) { 49 | if (err) throw err; 50 | 51 | callback(pathMapping); 52 | }) 53 | }) 54 | } 55 | 56 | module.exports.assertDeviceConnected = function (device, targetPath, targetIndex, targetAddress) { 57 | assert.equal(device.connected, true); 58 | 59 | if (targetIndex != undefined && targetIndex != null) { 60 | assert.equal(path.normalize(device.deviceRoot), path.normalize(targetPath)); 61 | } 62 | 63 | if (targetIndex != undefined && targetIndex != null) { 64 | if (device.hasOwnProperty("deviceIndex")) { 65 | assert.equal(device.deviceIndex, targetIndex); 66 | } 67 | else { 68 | assert.fail(undefined, undefined, "Cannot assert that device is connected at a given index if this device doesn't have an index property."); 69 | } 70 | } 71 | 72 | if (targetAddress != undefined && targetAddress != null) { 73 | if (device.hasOwnProperty("address")) { 74 | assert.equal(device.address, targetAddress); 75 | } 76 | else { 77 | assert.fail(undefined, undefined, "Cannot assert that device is connected at a given address if this device doesn't have an address property."); 78 | } 79 | } 80 | } 81 | 82 | module.exports.setDeviceProperty = function(device, key, value) { 83 | assert.doesNotThrow(function() { 84 | var propFilePath = path.normalize(path.join(device.deviceRoot, key)); 85 | if(!fs.existsSync(propFilePath)) 86 | throw new Error("The requested property does not exist: " + key); 87 | 88 | fs.writeFileSync(propFilePath, value); 89 | }); 90 | } 91 | 92 | 93 | module.exports.getDeviceProperty = function(device, key) { 94 | var readResult = null; 95 | assert.doesNotThrow(function() { 96 | var propFilePath = path.normalize(path.join(device.deviceRoot, key)); 97 | if(!fs.existsSync(propFilePath)) 98 | throw new Error("The requested property does not exist: " + key); 99 | 100 | readResult = fs.readFileSync(propFilePath).toString(); 101 | }); 102 | 103 | return readResult; 104 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "src/index.ts" 4 | ], 5 | "compilerOptions": { 6 | "sourceMap": true, 7 | "declaration": true, 8 | "target": "es5", 9 | "module": "commonjs", 10 | "outDir": "bin/" 11 | } 12 | } --------------------------------------------------------------------------------