Initial hacky commit
Esse commit está contido em:
+65
@@ -0,0 +1,65 @@
|
||||
var express = require('express');
|
||||
var engines = require('consolidate');
|
||||
var nodecr = require('nodecr');
|
||||
var fs = require('fs');
|
||||
var app = express();
|
||||
|
||||
app.engine('html', engines.hogan);
|
||||
app.set('view engine', 'html');
|
||||
app.set('views', __dirname);
|
||||
|
||||
var lastPng;
|
||||
|
||||
app.get('/', function(req, res)
|
||||
{
|
||||
res.render('index');
|
||||
});
|
||||
|
||||
app.get('/image', function(req, res)
|
||||
{
|
||||
if(lastPng)
|
||||
{
|
||||
res.writeHead(200, {'Content-Type:': 'image/png' });
|
||||
res.end(lastPng);
|
||||
}
|
||||
});
|
||||
|
||||
var arDrone = require('ar-drone');
|
||||
var client = arDrone.createClient();
|
||||
|
||||
//client.takeoff();
|
||||
/*client.after(5000, function()
|
||||
{
|
||||
var pngEncoder = client.getPngStream();
|
||||
pngEncoder
|
||||
.on('error', console.log)
|
||||
.on('data', function(pngBugger)
|
||||
{
|
||||
lastPng = pngBuffer;
|
||||
});
|
||||
});
|
||||
*/
|
||||
/*client
|
||||
.after(5000, function() {
|
||||
this.clockwise(0.5);
|
||||
})
|
||||
//.after(3000, function() {
|
||||
// this.animate('flipLeft', 15);
|
||||
//})
|
||||
.after(1000, function() {
|
||||
this.stop();
|
||||
this.land();
|
||||
});
|
||||
*/
|
||||
|
||||
nodecr.process(__dirname + '/stop.png', function(err, text)
|
||||
{
|
||||
if(err)
|
||||
{
|
||||
console.error(err);
|
||||
}else{
|
||||
console.log("OCR Text: " + text);
|
||||
}
|
||||
});
|
||||
|
||||
//app.listen(8080);
|
||||
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test</h1>
|
||||
<img src="/image"/>
|
||||
</body>
|
||||
</html>
|
||||
+1
@@ -0,0 +1 @@
|
||||
../express/bin/express
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
*.swp
|
||||
*.un~
|
||||
/node_modules
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.8
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
# Contributing
|
||||
|
||||
## Running the test suite
|
||||
|
||||
```js
|
||||
$ git clone <git url>
|
||||
$ cd <clone dir>
|
||||
$ npm install
|
||||
$ npm test
|
||||
```
|
||||
|
||||
## Running an individual test
|
||||
|
||||
```js
|
||||
$ node test/unit/control/test-AtCommandCreator.js
|
||||
```
|
||||
|
||||
## TODOS
|
||||
|
||||
If you are looking for something to work on, here are a few things I'd like to
|
||||
see in this module:
|
||||
|
||||
### arDrone.createCsvRecorder()
|
||||
|
||||
This feature would allow users to easily record control, navdata and other
|
||||
flight related data for later analysis / plotting.
|
||||
|
||||
I'm not sure how this should work exactly, but here is one idea:
|
||||
|
||||
```js
|
||||
var recorder = arDrone.createCsvRecorder();
|
||||
var control = arDrone.createUdpControl({recorder: recorder});
|
||||
var navdata = arDrone.createUdpNavdataStream({recorder: recorder});
|
||||
|
||||
recorder.pipe(fs.createWritableStream(__dirname + '/recording.csv'));
|
||||
```
|
||||
|
||||
The approach could also be inversed:
|
||||
|
||||
```js
|
||||
var control = arDrone.createUdpControl();
|
||||
var navdata = arDrone.createUdpNavdataStream();
|
||||
var recorder = arDrone.createCsvRecorder();
|
||||
|
||||
recorder.add(navdata);
|
||||
recorder.add(control);
|
||||
```
|
||||
|
||||
Custom events could be tracked through an EventEmitter-like interface:
|
||||
|
||||
```js
|
||||
recorder.emit('myEvent', 'a string, object, array, buffer, number, etc.');
|
||||
```
|
||||
|
||||
So if you have ideas for this / want to work on it, let me know. My favorite
|
||||
module for generating the CSV output would be:
|
||||
[ya-csv](https://github.com/koles/ya-csv).
|
||||
|
||||
### Client API
|
||||
|
||||
The Client API is still lacking an important feature:
|
||||
|
||||
* `client.config()` - allow sending custom config values to a client. This is
|
||||
needed to configure things like the 'navdata' and 'camera' settings.
|
||||
|
||||
### Parse remaining navdata options
|
||||
|
||||
Currently a lot of navdata packages are still not implemented, so have a look
|
||||
at `lib/navdata/parseNavdata.js` if you'd like to help.
|
||||
|
||||
### Streaming to HTML5 video tag
|
||||
|
||||
This one is difficult. Right now this library can turn the drone video stream
|
||||
into a series of PNG buffers which can be rendered using an image tag. However,
|
||||
this only works well for up to ~5-10 frames per second and is very hacky.
|
||||
|
||||
So what would be really cool is playing the video inside an actual video tag.
|
||||
However, the video data received from the drone is a raw H264 stream. In order
|
||||
for it to be played inside a browser, it would have to be embedded into a
|
||||
container format browsers understand (probably quicktime/mp4). FFMPEG does not
|
||||
seem capable of doing this (the output stream will not be playable until the input
|
||||
stream has ended). There is a chance that this is because the quicktime/mp4
|
||||
format does not allow this kind of "streaming", but it could also have other
|
||||
reasons.
|
||||
|
||||
So somebody needs to figure out if ffmpeg is a dead end for this, or if there
|
||||
is some way to do this.
|
||||
|
||||
If ffmpeg turns out to be unviable, another approach would be to implement
|
||||
a container format (like quicktime/mp4) in JavaScript (I looked into this,
|
||||
it will be tons of work), or create bindings to something like
|
||||
[l-smash](http://code.google.com/p/l-smash/) (was recommended to me in #ffmpeg).
|
||||
|
||||
Another angle at tackling this problem is not use a video tag, but decode the
|
||||
h264 stream on a canvas tag instead. This may be very feasable given that there
|
||||
is already a h264 decoder for JS: [Broadway.js](https://github.com/mbebenita/Broadway)
|
||||
which could probably be modified for this.
|
||||
|
||||
So, if you're looking for a very difficult but interesting problem, let me know
|
||||
and I'll try to help you on this as much as I can.
|
||||
|
||||
### AR Drone 1.0
|
||||
|
||||
It [appears](https://github.com/felixge/node-ar-drone/issues/11#issuecomment-9402270) that this library is compatible with 1.0 drones as well,
|
||||
but feel free to submit bugs / patches if you find any problems.
|
||||
|
||||
### Fixing bugs
|
||||
|
||||
Bug fixes are always welcome. Please add a test!
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
SHELL := /bin/bash
|
||||
|
||||
test:
|
||||
npm test
|
||||
|
||||
.PHONY: test
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
* Undo stupid changes to master branch and push newClient into master
|
||||
|
||||
* Disable emergency mode
|
||||
|
||||
* Figure out a better way to record control (to include config commands)
|
||||
|
||||
* Discard / log out of order navdata
|
||||
|
||||
* Decide on name for Navdata data
|
||||
* Parse pwm and rawMeasures again
|
||||
|
||||
* Rename clockSpin/clockWise
|
||||
|
||||
* Maybe change udpControlStream interface to accept buffers
|
||||
|
||||
* Do sensor data stuff
|
||||
|
||||
* ClientControl.config
|
||||
* 'tricks' / 'leds' object
|
||||
* Convert everything to functional style
|
||||
* move createAtCommand one level up
|
||||
|
||||
* Map battery event
|
||||
* Document events and properties
|
||||
* Create client.sensors, client.commands streams
|
||||
|
||||
* Document new AR Drone libs
|
||||
* Finish AR Drone Png thingy
|
||||
* Delete old shit
|
||||
* Document what needs to be done
|
||||
* Logging !
|
||||
* Build new AR Drone client
|
||||
|
||||
# Features
|
||||
|
||||
* control.flip() (POST /control/flip)
|
||||
|
||||
# Consistency
|
||||
|
||||
* client.navdata.raw -> client.navdata.udpStream
|
||||
* message / command: number -> message.sequenceNumber
|
||||
* Rename navdata message to navdata
|
||||
|
||||
# Logging
|
||||
|
||||
* node version
|
||||
* v8 version
|
||||
* get drone config
|
||||
* get memory usage
|
||||
* get operating systme
|
||||
* raw video stream
|
||||
* raw at stream
|
||||
* raw navdata stream
|
||||
+409
@@ -0,0 +1,409 @@
|
||||
# ar-drone
|
||||
|
||||
[](http://travis-ci.org/felixge/node-ar-drone)
|
||||
|
||||
An implementation of the networking protocols used by the
|
||||
[Parrot AR Drone 2.0](http://ardrone2.parrot.com/). It appears that 1.0 drones are also [compatible](https://github.com/felixge/node-ar-drone/issues/11#issuecomment-9402270).
|
||||
|
||||
Install via Github to get the *latest* version:
|
||||
|
||||
```bash
|
||||
npm install git://github.com/felixge/node-ar-drone.git
|
||||
```
|
||||
|
||||
Or, if you're fine with missing some cutting edge stuff, go for npm:
|
||||
|
||||
```bash
|
||||
npm install ar-drone
|
||||
```
|
||||
|
||||
## Introduction
|
||||
|
||||
The AR Drone is an affordable, yet surprisingly capable quadcopter. The drone
|
||||
itself runs a proprietary firmware that can be controlled via WiFi using the official
|
||||
FreeFlight mobile app
|
||||
(available for [iOS](http://itunes.apple.com/us/app/freeflight/id373065271?mt=8) and [Android](https://play.google.com/store/apps/details?id=com.parrot.freeflight&hl=en)).
|
||||
|
||||
Unlike the firmware, the client protocol is open, and Parrot publishes an [SDK](https://projects.ardrone.org/projects/show/ardrone-api)
|
||||
(signup required to download) including a good amount of documentation and C
|
||||
code. Their target audience seems to be mobile developers who can use this
|
||||
SDK to create games and other apps for people to have more fun with their drones.
|
||||
|
||||
However, the protocol can also be used to receive video and sensor data, enabling
|
||||
developers to write autonomous programs for the upcoming robot revolution.
|
||||
|
||||
## Status
|
||||
|
||||
This module is still under [heavy development](./node-ar-drone/blob/master/CONTRIBUTING.md), so please don't be suprised if
|
||||
you find some functionality missing or undocumented.
|
||||
|
||||
However, the documented parts are tested and should work well for most parts.
|
||||
|
||||
## Client
|
||||
|
||||
This module exposes a high level Client API that tries to support all drone
|
||||
features, while making them easy to use.
|
||||
|
||||
The best way to get started is to create a `repl.js` file like this:
|
||||
|
||||
```js
|
||||
var arDrone = require('ar-drone');
|
||||
var client = arDrone.createClient();
|
||||
client.createRepl();
|
||||
```
|
||||
|
||||
Using this REPL, you should be able to have some fun:
|
||||
|
||||
```js
|
||||
$ node repl.js
|
||||
// Make the drone takeoff
|
||||
drone> takeoff()
|
||||
true
|
||||
// Wait for the drone to takeoff
|
||||
drone> clockwise(0.5)
|
||||
0.5
|
||||
// Let the drone spin for a while
|
||||
drone> land()
|
||||
true
|
||||
// Wait for the drone to land
|
||||
```
|
||||
|
||||
Now you could write an autonomous program that does the same:
|
||||
|
||||
```js
|
||||
var arDrone = require('ar-drone');
|
||||
var client = arDrone.createClient();
|
||||
|
||||
client.takeoff();
|
||||
|
||||
client
|
||||
.after(5000, function() {
|
||||
this.clockwise(0.5);
|
||||
})
|
||||
.after(3000, function() {
|
||||
this.stop();
|
||||
this.land();
|
||||
});
|
||||
```
|
||||
|
||||
Ok, but what if you want to make your drone to interact with something? Well,
|
||||
you could start by looking at the sensor data:
|
||||
|
||||
```js
|
||||
client.on('navdata', console.log);
|
||||
```
|
||||
|
||||
Not all of this is handled by the Client library yet, but you should at the
|
||||
very least be able to receive `droneState` and `demo` data.
|
||||
|
||||
A good initial challenge might be to try flying to a certain altitude based
|
||||
on the `navdata.demo.altitudeMeters` property.
|
||||
|
||||
Once you have manged this, you may want to try looking at the camera image. Here
|
||||
is a simple way to get this as PngBuffers (requires a recent ffmpeg version to
|
||||
be found in your `$PATH`):
|
||||
|
||||
```js
|
||||
var pngStream = client.getPngStream();
|
||||
pngStream.on('data', console.log);
|
||||
```
|
||||
|
||||
Your first challenge might be to expose these png images as a node http web
|
||||
server. Once you have done that, you should try feeding them into the
|
||||
[opencv](https://npmjs.org/package/opencv) module.
|
||||
|
||||
### Client API
|
||||
|
||||
#### arDrone.createClient([options])
|
||||
|
||||
Returns a new `Client` object. `options` include:
|
||||
|
||||
* `ip`: The IP of the drone. Defaults to `'192.168.1.1'`.
|
||||
|
||||
#### client.createREPL()
|
||||
|
||||
Launches an interactive interface with all client methods available in the
|
||||
active scope. Additionally `client` resolves to the `client` instance itself.
|
||||
|
||||
#### client.getPngStream()
|
||||
|
||||
Returns a `PngEncoder` object that emits individual png image buffers as `'data'`
|
||||
events. Multiple calls to this method returns the same object. Connection lifecycle
|
||||
(e.g. reconnect on error) is managed by the client.
|
||||
|
||||
#### client.getVideoStream()
|
||||
|
||||
Returns a `TcpVideoStream` object that emits raw tcp packets as `'data'`
|
||||
events. Multiple calls to this method returns the same object. Connection lifecycle
|
||||
(e.g. reconnect on error) is managed by the client.
|
||||
|
||||
#### client.takeoff(cb)
|
||||
|
||||
Sets the internal `fly` state to `true`, `cb` is invoked after the drone
|
||||
reports that it is hovering.
|
||||
|
||||
#### client.land(cb)
|
||||
|
||||
Sets the internal `fly` state to `false`, `cb` is invoked after the drone
|
||||
reports it has landed.
|
||||
|
||||
#### client.up(speed) / client.down(speed)
|
||||
|
||||
Makes the drone gain or reduce altitude. `speed` can be a value from `0` to `1`.
|
||||
|
||||
#### client.clockwise(speed) / client.counterClockwise(speed)
|
||||
|
||||
Causes the drone to spin. `speed` can be a value from `0` to `1`.
|
||||
|
||||
#### client.front(speed) / client.back(speed)
|
||||
|
||||
Controls the pitch, which a horizontal movement using the camera
|
||||
as a reference point. `speed` can be a value from `0` to `1`.
|
||||
|
||||
#### client.left(speed) / client.right(speed)
|
||||
|
||||
Controls the roll, which is a horizontal movement using the camera
|
||||
as a reference point. `speed` can be a value from `0` to `1`.
|
||||
|
||||
#### client.stop()
|
||||
|
||||
Sets all drone movement commands to `0`, making it effectively hover in place.
|
||||
|
||||
#### client.config(key, value)
|
||||
|
||||
Sends a config command to the drone. You will need to download the drone
|
||||
[SDK](https://projects.ardrone.org/projects/show/ardrone-api) to find a full list of commands in the `ARDrone_Developer_Guide.pdf`.
|
||||
|
||||
For example, this command can be used to instruct the drone to send all navdata.
|
||||
|
||||
```js
|
||||
client.config('general:navdata_demo', 'FALSE');
|
||||
```
|
||||
|
||||
|
||||
#### client.animate(animation, duration)
|
||||
|
||||
Performs a pre-programmed flight sequence for a given `duration` (in ms).
|
||||
`animation` can be one of the following:
|
||||
|
||||
|
||||
```js
|
||||
['phiM30Deg', 'phi30Deg', 'thetaM30Deg', 'theta30Deg', 'theta20degYaw200deg',
|
||||
'theta20degYawM200deg', 'turnaround', 'turnaroundGodown', 'yawShake',
|
||||
'yawDance', 'phiDance', 'thetaDance', 'vzDance', 'wave', 'phiThetaMixed',
|
||||
'doublePhiThetaMixed', 'flipAhead', 'flipBehind', 'flipLeft', 'flipRight']
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
client.animate('flipLeft', 1500);
|
||||
```
|
||||
|
||||
Please note that the drone will need a good amount of altitude and headroom
|
||||
to perform a flip. So be careful!
|
||||
|
||||
#### client.animateLeds(animation, hz, duration)
|
||||
|
||||
Performs a pre-programmed led sequence at given `hz` frequency and `duration`
|
||||
(in sec!). `animation` can be one of the following:
|
||||
|
||||
```js
|
||||
['blinkGreenRed', 'blinkGreen', 'blinkRed', 'blinkOrange', 'snakeGreenRed',
|
||||
'fire', 'standard', 'red', 'green', 'redSnake', 'blank', 'rightMissile',
|
||||
'leftMissile', 'doubleMissile', 'frontLeftGreenOthersRed',
|
||||
'frontRightGreenOthersRed', 'rearRightGreenOthersRed',
|
||||
'rearLeftGreenOthersRed', 'leftGreenRightRed', 'leftRedRightGreen',
|
||||
'blinkStandard']
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
client.animateLeds('blinkRed', 5, 2)
|
||||
```
|
||||
|
||||
#### client.disableEmergency()
|
||||
|
||||
Causes the emergency REF bit to be set to 1 until
|
||||
`navdata.droneState.emergencyLanding` is 0. This recovers a drone that has
|
||||
flipped over and is showing red lights to be flyable again and show green
|
||||
lights. It is also done implicitly when creating a new high level client.
|
||||
|
||||
#### Events
|
||||
|
||||
A client will emit landed, hovering, flying, landing, batteryChange, and altitudeChange events as long as demo navdata is enabled.
|
||||
|
||||
To enable demo navdata use
|
||||
|
||||
```js
|
||||
client.config('general:navdata_demo', 'FALSE');
|
||||
```
|
||||
|
||||
## UdpControl
|
||||
|
||||
This is a low level API. If you prefer something simpler, check out the Client
|
||||
docs.
|
||||
|
||||
The drone is controlled by sending UDP packets on port 5556. Because UDP
|
||||
does not guarantee message ordering or delivery, clients must repeatedly send
|
||||
their instructions and include an incrementing sequence number with each
|
||||
command.
|
||||
|
||||
For example, the command used for takeoff/landing (REF), with a sequence number
|
||||
of 1, and a parameter of 512 (takeoff) looks like this:
|
||||
|
||||
```
|
||||
AT*REF=1,512\r
|
||||
```
|
||||
|
||||
To ease the creation and sending of these packets, this module exposes an
|
||||
`UdpControl` class handling this task. For example, the following program will
|
||||
cause your drone to takeoff and hover in place.
|
||||
|
||||
```js
|
||||
var arDrone = require('ar-drone');
|
||||
var control = arDrone.createUdpControl();
|
||||
|
||||
setInterval(function() {
|
||||
// The emergency: true option recovers your drone from emergency mode that can
|
||||
// be caused by flipping it upside down or the drone crashing into something.
|
||||
// In a real program you probably only want to send emergency: true for one
|
||||
// second in the beginning, otherwise your drone may attempt to takeoff again
|
||||
// after a crash.
|
||||
control.ref({fly: true, emergency: true});
|
||||
// This command makes sure your drone hovers in place and does not drift.
|
||||
control.pcmd();
|
||||
// This causes the actual udp message to be send (multiple commands are
|
||||
// combined into one message)
|
||||
control.flush();
|
||||
}, 30);
|
||||
```
|
||||
|
||||
Now that you are airborne, you can fly around by passing an argument to the
|
||||
`pcmd()` method:
|
||||
|
||||
```js
|
||||
control.pcmd({
|
||||
front: 0.5, // fly forward with 50% speed
|
||||
up: 0.3, // and also fly up with 30% speed
|
||||
});
|
||||
```
|
||||
|
||||
That's it! A full list of all `pcmd()` options can be found in the API docs
|
||||
below.
|
||||
|
||||
With what you have learned so far, you could create a simple program
|
||||
like this:
|
||||
|
||||
```js
|
||||
var arDrone = require('ar-drone');
|
||||
var control = arDrone.createUdpControl();
|
||||
var start = Date.now();
|
||||
|
||||
var ref = {};
|
||||
var pcmd = {};
|
||||
|
||||
console.log('Recovering from emergency mode if there was one ...');
|
||||
ref.emergency = true;
|
||||
setTimeout(function() {
|
||||
console.log('Takeoff ...');
|
||||
|
||||
ref.emergency = false;
|
||||
ref.fly = true;
|
||||
|
||||
}, 1000);
|
||||
|
||||
setTimeout(function() {
|
||||
console.log('Turning clockwise ...');
|
||||
|
||||
pcmd.clockwise = 0.5;
|
||||
}, 6000);
|
||||
|
||||
setTimeout(function() {
|
||||
console.log('Landing ...');
|
||||
|
||||
ref.fly = false;
|
||||
pcmd = {};
|
||||
}, 8000);
|
||||
|
||||
|
||||
setInterval(function() {
|
||||
control.ref(ref);
|
||||
control.pcmd(pcmd);
|
||||
control.flush();
|
||||
}, 30);
|
||||
```
|
||||
|
||||
### UdpControl API
|
||||
|
||||
#### arDrone.createUdpControl([options]) / new arDrone.UdpControl([options])
|
||||
|
||||
Creates a new UdpControl instance where `options` can include:
|
||||
|
||||
* `ip`: The drone IP address, defaults to `'192.168.1.1'`.
|
||||
* `port`: The port to use, defaults to `5556`.
|
||||
|
||||
#### udpControl.raw(command, [arg1, arg2, ...])
|
||||
|
||||
Enqueues a raw `AT*` command. This is useful if you want full control.
|
||||
|
||||
For example, a takeoff instructions be send like this:
|
||||
|
||||
```js
|
||||
udpControl.raw('REF', (1 << 9));
|
||||
```
|
||||
|
||||
#### udpControl.ref([options])
|
||||
|
||||
Enqueues a `AT*REF` command, options are:
|
||||
|
||||
* `fly`: Set this to `true` for takeoff / staying in air, or `false` to initiate
|
||||
landing / stay on the ground. Defaults to `false`.
|
||||
* `emergency`: Set this to `true` to set the emergency bit, or `false` to not
|
||||
set it. Details on this can be found in the official SDK Guide. Defaults to
|
||||
`false`.
|
||||
|
||||
#### udpControl.pcmd([options])
|
||||
|
||||
Enqueues a `AT*PCMD` (progressive) command, options are:
|
||||
|
||||
* `front` or `back`: Fly towards or away from front camera direction.
|
||||
* `left` or/ `right`: Fly towards the left or right of the front camera.
|
||||
* `up` or `down`: Gain or reduce altitude.
|
||||
* `clockwise` or `counterClockwise`: Rotate around the center axis.
|
||||
|
||||
The values for each option are the speed to use for the operation and can range
|
||||
from 0 to 1. You can also use negative values like `{front: -0.5}`, which is
|
||||
the same as `{back: 0.5}`.
|
||||
|
||||
#### udpControl.flush()
|
||||
|
||||
Sends all enqueued commands as an UDP packet to the drone.
|
||||
|
||||
## Video
|
||||
|
||||
@TODO Document the low level video API.
|
||||
|
||||
## Navdata
|
||||
|
||||
@TODO Document the low level navdata API.
|
||||
|
||||
## Environment variables
|
||||
|
||||
* DEFAULT_DRONE_IP
|
||||
|
||||
## Camera access
|
||||
|
||||
You can access the head camera and the bottom camera, you just have the change
|
||||
the config:
|
||||
|
||||
```javascript
|
||||
// access the head camera
|
||||
client.config('video:video_channel', 0);
|
||||
|
||||
// access the bottom camera
|
||||
client.config('video:video_channel', 3);
|
||||
```
|
||||
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// Run this to receive a png image stream from your drone.
|
||||
|
||||
var arDrone = require('..');
|
||||
var http = require('http');
|
||||
|
||||
console.log('Connecting png stream ...');
|
||||
|
||||
var pngStream = arDrone.createClient().getPngStream();
|
||||
|
||||
var lastPng;
|
||||
pngStream
|
||||
.on('error', console.log)
|
||||
.on('data', function(pngBuffer) {
|
||||
lastPng = pngBuffer;
|
||||
});
|
||||
|
||||
var server = http.createServer(function(req, res) {
|
||||
if (!lastPng) {
|
||||
res.writeHead(503);
|
||||
res.end('Did not receive any png data yet.');
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeHead(200, {'Content-Type': 'image/png'});
|
||||
res.end(lastPng);
|
||||
});
|
||||
|
||||
server.listen(8080, function() {
|
||||
console.log('Serving latest png on port 8080 ...');
|
||||
});
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// Run this to receive the raw video stream from your drone as buffers.
|
||||
|
||||
var arDrone = require('..');
|
||||
|
||||
var video = arDrone.createClient().getVideoStream();
|
||||
|
||||
video.on('data', console.log);
|
||||
video.on('error', console.log);
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Run this to make your drone take off for 5 seconds and then land itself
|
||||
// again.
|
||||
|
||||
var UdpControl = require('../lib/control/UdpControl');
|
||||
|
||||
var control = new UdpControl();
|
||||
var fly = true;
|
||||
var emergency = true;
|
||||
|
||||
setInterval(function() {
|
||||
control.ref({fly: fly, emergency: emergency});
|
||||
control.pcmd();
|
||||
control.flush();
|
||||
}, 30);
|
||||
|
||||
// For the first second, disable emergency if there was one
|
||||
setTimeout(function() {
|
||||
emergency = false;
|
||||
}, 1000);
|
||||
|
||||
setTimeout(function() {
|
||||
fly = false;
|
||||
}, 5000);
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
var arDrone = exports;
|
||||
|
||||
exports.Client = require('./lib/Client');
|
||||
exports.UdpControl = require('./lib/control/UdpControl');
|
||||
exports.PngStream = require('./lib/video/PngStream');
|
||||
exports.UdpNavdataStream = require('./lib/navdata/UdpNavdataStream');
|
||||
|
||||
exports.createClient = function(options) {
|
||||
var client = new arDrone.Client(options);
|
||||
client.resume();
|
||||
return client;
|
||||
};
|
||||
|
||||
exports.createUdpControl = function(options) {
|
||||
return new arDrone.UdpControl(options);
|
||||
};
|
||||
|
||||
exports.createPngStream = function(options) {
|
||||
var stream = new arDrone.PngStream(options);
|
||||
stream.resume();
|
||||
return stream;
|
||||
};
|
||||
|
||||
exports.createUdpNavdataStream = function(options) {
|
||||
var stream = new arDrone.UdpNavdataStream(options);
|
||||
stream.resume();
|
||||
return stream;
|
||||
};
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
I want to improve the API for the library to help with a few things:
|
||||
|
||||
* debugging
|
||||
* logging
|
||||
* ease of use
|
||||
|
||||
The new API I imagine would be like this:
|
||||
|
||||
```js
|
||||
var client = arDrone.createClient()
|
||||
client.control.takeoff(function(err, result) {
|
||||
client.control.up(1);
|
||||
|
||||
console.log(client.toJSON());
|
||||
// {
|
||||
// navdata: { ... },
|
||||
// control: { ... },
|
||||
// }
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
# Separation of APIs
|
||||
|
||||
```js
|
||||
client.navdata.on('data');
|
||||
client.control.on('data');
|
||||
client.video.on('data');
|
||||
var h264 = client.video.h264();
|
||||
```
|
||||
|
||||
# Logging
|
||||
|
||||
Another important aspect is to get logging right. I could imagine this to work:
|
||||
|
||||
```js
|
||||
client.log.tsv('prefix');
|
||||
client.log.json('file');
|
||||
```
|
||||
|
||||
|
||||
# Control
|
||||
|
||||
Let's define the control API
|
||||
|
||||
```js
|
||||
control.upDown = 0;
|
||||
control.leftRight = 0;
|
||||
control.frontBack = 0;
|
||||
control.upDown = 0;
|
||||
control.fly = 0;
|
||||
control.emergency = 0;
|
||||
control.configs = [];
|
||||
```
|
||||
|
||||
# Nadata
|
||||
|
||||
```js
|
||||
navdata.batteryPercentage = 0;
|
||||
navdata.altitudeMeters = 0;
|
||||
```
|
||||
|
||||
# Output folder
|
||||
|
||||
/example
|
||||
/navdata.tsv
|
||||
/control.movement.tsv
|
||||
/control.config.tsv
|
||||
/log.txt
|
||||
|
||||
|
||||
# Re-desiging the AT interface
|
||||
|
||||
|
||||
```js
|
||||
client.control.atUdpStream.write(message);
|
||||
client.control.atUdpStream.on('data', function(message) {
|
||||
// message.id keeps track of the packet number
|
||||
// message.commands holds individual at commands
|
||||
});
|
||||
```
|
||||
|
||||
```
|
||||
atMessage.write(new AtCommand('ref', 121, 12));
|
||||
atMessage.write(new AtCommand('pcmd', 121, 12));
|
||||
```
|
||||
|
||||
More thinking on message / at command streams:
|
||||
|
||||
```js
|
||||
var commands [
|
||||
atCommandSequence.ref(...),
|
||||
atCommandSequence.pcmd(...),
|
||||
atCommandSequence.config(...),
|
||||
];
|
||||
|
||||
var message = atMessageSequence.next(commands);
|
||||
return message;
|
||||
```
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
I am stuck again, I am unsure about the sensor data. Should I make it look like
|
||||
this?
|
||||
|
||||
```js
|
||||
{
|
||||
state: undefined,
|
||||
batteryLevel: undefined,
|
||||
flyState: undefined,
|
||||
emergency: undefined,
|
||||
altitudeMeters: undefined,
|
||||
frontBackDegrees: undefined,
|
||||
leftRightDegrees: undefined,
|
||||
clockSpinDegrees: undefined,
|
||||
frontBackSpeed: undefined,
|
||||
leftRightSpeed: undefined,
|
||||
upDownSpeed: undefined,
|
||||
.flying: undefined,
|
||||
videoEnabled: undefined,
|
||||
visionEnabled: undefined,
|
||||
controlAlgorithm: undefined,
|
||||
altitudeControlAlgorithm: undefined,
|
||||
startButtonState: undefined,
|
||||
controlCommandAck: undefined,
|
||||
cameraReady: undefined,
|
||||
travellingEnabled: undefined,
|
||||
usbReady: undefined,
|
||||
navdataDemo: undefined,
|
||||
navdataBootstrap: undefined,
|
||||
motorProblem: undefined,
|
||||
communicationLost: undefined,
|
||||
softwareFault: undefined,
|
||||
lowBattery: undefined,
|
||||
userEmergencyLanding: undefined,
|
||||
timerElapsed: undefined,
|
||||
magnometerNeedsCalibration: undefined,
|
||||
anglesOutOfRange: undefined,
|
||||
tooMuchWind: undefined,
|
||||
ultrasonicSensorDeaf: undefined,
|
||||
cutoutDetected: undefined,
|
||||
picVersionNumberOk: undefined,
|
||||
atCodecThreadOn: undefined,
|
||||
navdataThreadOn: undefined,
|
||||
videoThreadOn: undefined,
|
||||
acquisitionThreadOn: undefined,
|
||||
controlWatchdogDelay: undefined,
|
||||
adcWatchdogDelay: undefined,
|
||||
comWatchdogProblem: undefined,
|
||||
emergencyLanding: undefined,
|
||||
}
|
||||
```
|
||||
|
||||
Or like this:
|
||||
|
||||
```js
|
||||
{
|
||||
batteryLevel: undefined,
|
||||
controlStatus: undefined,
|
||||
flyStatus: undefined,
|
||||
altitudeMeters: undefined,
|
||||
gyros:{
|
||||
frontBack: undefined,
|
||||
leftRight: undefined,
|
||||
clockWise: undefined,
|
||||
},
|
||||
speed: {
|
||||
frontBack: undefined,
|
||||
leftRight: undefined,
|
||||
upDown: undefined,
|
||||
}
|
||||
status: {
|
||||
flying: undefined,
|
||||
videoEnabled: undefined,
|
||||
visionEnabled: undefined,
|
||||
controlAlgorithm: undefined,
|
||||
altitudeControlAlgorithm: undefined,
|
||||
startButtonState: undefined,
|
||||
controlCommandAck: undefined,
|
||||
cameraReady: undefined,
|
||||
travellingEnabled: undefined,
|
||||
usbReady: undefined,
|
||||
navdataDemo: undefined,
|
||||
navdataBootstrap: undefined,
|
||||
motorProblem: undefined,
|
||||
communicationLost: undefined,
|
||||
softwareFault: undefined,
|
||||
lowBattery: undefined,
|
||||
userEmergencyLanding: undefined,
|
||||
timerElapsed: undefined,
|
||||
magnometerNeedsCalibration: undefined,
|
||||
anglesOutOfRange: undefined,
|
||||
tooMuchWind: undefined,
|
||||
ultrasonicSensorDeaf: undefined,
|
||||
cutoutDetected: undefined,
|
||||
picVersionNumberOk: undefined,
|
||||
atCodecThreadOn: undefined,
|
||||
navdataThreadOn: undefined,
|
||||
videoThreadOn: undefined,
|
||||
acquisitionThreadOn: undefined,
|
||||
controlWatchdogDelay: undefined,
|
||||
adcWatchdogDelay: undefined,
|
||||
comWatchdogProblem: undefined,
|
||||
emergencyLanding: undefined,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
So this continue to be hard. I am wondering:
|
||||
|
||||
* should I rename 'sensors' to 'navdata' ?
|
||||
* should my option sparsers "augment" the data object, or produce their own
|
||||
results
|
||||
|
||||
The latter questions seems more interesting, so let's contrast the approaches.
|
||||
|
||||
a) Augment data
|
||||
|
||||
Pro:
|
||||
* No need to "merge" / easier to achieve the combined data structure I want
|
||||
|
||||
Con:
|
||||
* Worse to test
|
||||
* Bad design
|
||||
|
||||
b) Return data
|
||||
|
||||
Pro:
|
||||
* Clean design
|
||||
* Easy to test
|
||||
|
||||
Con:
|
||||
* Needs to merge the data structure
|
||||
* Final data structure is not documented in one place
|
||||
* Missing options will cause missing elements in final data structure
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
# control <-> navdata circular dependency
|
||||
|
||||
So I have a circular dependency between navdata and control:
|
||||
|
||||
* control needs navdata to provide a callback to takeoff
|
||||
* navdata needs control to request more navdata as needed
|
||||
|
||||
The following solutions seem feasable:
|
||||
|
||||
## a) Create circular dependency
|
||||
|
||||
Problems:
|
||||
* Nasty
|
||||
* Means I have to manipulate the navdata object after creating it to give
|
||||
it control.
|
||||
* Breaks the isolation of navdata and control
|
||||
|
||||
## b) Move higher level control to client
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
client.takeoff();
|
||||
client.land();
|
||||
```
|
||||
|
||||
Problems:
|
||||
* Unclear what the client object should do and what the control object should
|
||||
do.
|
||||
|
||||
|
||||
## c) Have the control listen to all navdata events
|
||||
|
||||
Problems:
|
||||
* None, as long as I'm ok with emitting all navdata events
|
||||
|
||||
# Side quest: emit 'data' events if not all navdata is included?
|
||||
|
||||
Problems:
|
||||
* may cause navdata events to not be emitted for an unknown amount of time
|
||||
|
||||
# control internals
|
||||
|
||||
I'm wondering how to do the control internals, this may be right:
|
||||
|
||||
* control.atStream.ref(); control.atStream.flush(); -> emits 'data' ControlMessage
|
||||
* control.atStream.pipe(control.udpStream);
|
||||
* udp emits what it is sending
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
|
||||
Client.UdpControl = require('./control/UdpControl');
|
||||
Client.Repl = require('./Repl');
|
||||
Client.UdpNavdataStream = require('./navdata/UdpNavdataStream');
|
||||
Client.PngStream = require('./video/PngStream');
|
||||
Client.PngEncoder = require('./video/PngEncoder');
|
||||
Client.TcpVideoStream = require('./video/TcpVideoStream');
|
||||
|
||||
module.exports = Client;
|
||||
util.inherits(Client, EventEmitter);
|
||||
function Client(options) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
options = options || {};
|
||||
|
||||
this._options = options;
|
||||
this._udpControl = options.udpControl || new Client.UdpControl(options);
|
||||
this._udpNavdatasStream = options.udpNavdataStream || new Client.UdpNavdataStream(options);
|
||||
this._pngStream = null;
|
||||
this._tcpStream = null;
|
||||
this._interval = null;
|
||||
this._ref = {};
|
||||
this._pcmd = {};
|
||||
this._repeaters = [];
|
||||
this._afterOffset = 0;
|
||||
this._disableEmergency = false;
|
||||
this._lastState = 'CTRL_LANDED';
|
||||
this._lastBattery = 100;
|
||||
this._lastAltitude = 0;
|
||||
}
|
||||
|
||||
Client.prototype.after = function(duration, fn) {
|
||||
setTimeout(fn.bind(this), this._afterOffset + duration);
|
||||
this._afterOffset += duration;
|
||||
return this;
|
||||
};
|
||||
|
||||
Client.prototype.createRepl = function() {
|
||||
var repl = new Client.Repl(this);
|
||||
repl.resume();
|
||||
return repl;
|
||||
};
|
||||
|
||||
Client.prototype.createPngStream = function() {
|
||||
console.warn("Client.createPngStream is deprecated. Use Client.getPngStream instead.");
|
||||
return this.getPngStream();
|
||||
};
|
||||
|
||||
Client.prototype.getPngStream = function() {
|
||||
if (this._pngStream == null) {
|
||||
this._pngStream = this._newPngStream();
|
||||
}
|
||||
return this._pngStream;
|
||||
}
|
||||
|
||||
Client.prototype.getVideoStream = function() {
|
||||
if (this._tcpVideoStream == null) {
|
||||
this._tcpVideoStream = this._newTcpVideoStream();
|
||||
}
|
||||
return this._tcpVideoStream;
|
||||
}
|
||||
|
||||
Client.prototype._newTcpVideoStream = function() {
|
||||
var stream = new Client.TcpVideoStream(this._options);
|
||||
var callback = function(err) {
|
||||
if (err !== null) {
|
||||
console.log('TcpVideoStream error: %s', err.message);
|
||||
setTimeout(function () {
|
||||
console.log('Attempting to reconnect to TcpVideoStream...');
|
||||
stream.connect(callback);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
stream.connect(callback);
|
||||
stream.on('error', callback);
|
||||
return stream;
|
||||
}
|
||||
|
||||
Client.prototype._newPngStream = function() {
|
||||
var videoStream = this.getVideoStream();
|
||||
var pngEncoder = new Client.PngEncoder(this._options);
|
||||
|
||||
videoStream.on('data', function(data) {
|
||||
pngEncoder.write(data);
|
||||
});
|
||||
|
||||
return pngEncoder;
|
||||
}
|
||||
|
||||
Client.prototype.resume = function() {
|
||||
// request basic navdata by default
|
||||
this.config('general:navdata_demo', 'TRUE');
|
||||
this.disableEmergency();
|
||||
this._setInterval(30);
|
||||
|
||||
this._udpNavdatasStream.removeAllListeners();
|
||||
this._udpNavdatasStream.resume();
|
||||
this._udpNavdatasStream
|
||||
.on('error', this._maybeEmitError.bind(this))
|
||||
.on('data', this._handleNavdata.bind(this));
|
||||
};
|
||||
|
||||
Client.prototype._handleNavdata = function(navdata) {
|
||||
if (navdata.droneState && navdata.droneState.emergencyLanding && this._disableEmergency) {
|
||||
this._ref.emergency = true;
|
||||
} else {
|
||||
this._ref.emergency = false;
|
||||
this._disableEmergency = false;
|
||||
}
|
||||
|
||||
this.emit('navdata', navdata);
|
||||
this._processNavdata(navdata);
|
||||
};
|
||||
|
||||
Client.prototype._processNavdata = function(navdata) {
|
||||
if (navdata.droneState && navdata.demo) {
|
||||
// controlState events
|
||||
var cstate = navdata.demo.controlState;
|
||||
var emitState = (function(e, state) {
|
||||
if (cstate === state && this._lastState !== state) {
|
||||
return this.emit(e);
|
||||
}
|
||||
}).bind(this);
|
||||
emitState('landing', 'CTRL_TRANS_LANDING');
|
||||
emitState('landed', 'CTRL_LANDED');
|
||||
emitState('takeoff', 'CTRL_TRANS_TAKEOFF');
|
||||
emitState('hovering', 'CTRL_HOVERING');
|
||||
emitState('flying', 'CTRL_FLYING');
|
||||
this._lastState = cstate;
|
||||
|
||||
// battery events
|
||||
var battery = navdata.demo.batteryPercentage;
|
||||
if (navdata.droneState.lowBattery === 1) {
|
||||
this.emit('lowBattery', battery);
|
||||
}
|
||||
if (navdata.demo.batteryPercentage !== this._lastBattery) {
|
||||
this.emit('batteryChange', battery);
|
||||
this._lastBattery = battery;
|
||||
}
|
||||
|
||||
// altitude events
|
||||
var altitude = navdata.demo.altitudeMeters;
|
||||
if (altitude !== this._lastAltitude) {
|
||||
this.emit('altitudeChange', altitude);
|
||||
this._lastAltitude = altitude;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// emits an 'error' event, but only if somebody is listening. This avoids
|
||||
// making node's EventEmitter throwing an exception for non-critical errors
|
||||
Client.prototype._maybeEmitError = function(err) {
|
||||
if (this.listeners('error').length > 0) {
|
||||
this.emit('error', err);
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype._setInterval = function(duration) {
|
||||
clearInterval(this._interval);
|
||||
this._interval = setInterval(this._sendCommands.bind(this), duration);
|
||||
};
|
||||
|
||||
Client.prototype._sendCommands = function() {
|
||||
this._udpControl.ref(this._ref);
|
||||
this._udpControl.pcmd(this._pcmd);
|
||||
this._udpControl.flush();
|
||||
|
||||
this._repeaters
|
||||
.forEach(function(repeat) {
|
||||
repeat.times--;
|
||||
repeat.method();
|
||||
});
|
||||
|
||||
this._repeaters = this._repeaters.filter(function(repeat) {
|
||||
return repeat.times > 0;
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.disableEmergency = function() {
|
||||
this._disableEmergency = true;
|
||||
};
|
||||
|
||||
Client.prototype.takeoff = function(cb) {
|
||||
this.once('hovering', cb || function() {});
|
||||
this._ref.fly = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
Client.prototype.land = function(cb) {
|
||||
this.once('landed', cb || function() {});
|
||||
this._ref.fly = false;
|
||||
return true;
|
||||
};
|
||||
|
||||
Client.prototype.stop = function() {
|
||||
this._pcmd = {};
|
||||
return true;
|
||||
};
|
||||
|
||||
Client.prototype.calibrate = function(device_num) {
|
||||
this._udpControl.calibrate(device_num);
|
||||
};
|
||||
|
||||
Client.prototype.config = function(key, value) {
|
||||
// @TODO Figure out if we can get a ACK for this, so we don't need to
|
||||
// repeat it blindly like this
|
||||
var self = this;
|
||||
this._repeat(10, function() {
|
||||
self._udpControl.config(key, value);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.animate = function(animation, duration) {
|
||||
// @TODO Figure out if we can get a ACK for this, so we don't need to
|
||||
// repeat it blindly like this
|
||||
var self = this;
|
||||
this._repeat(10, function() {
|
||||
self._udpControl.animate(animation, duration);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.animateLeds = function(animation, hz, duration) {
|
||||
// @TODO Figure out if we can get a ACK for this, so we don't need to
|
||||
// repeat it blindly like this
|
||||
var self = this;
|
||||
this._repeat(10, function() {
|
||||
self._udpControl.animateLeds(animation, hz, duration);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.battery = function() {
|
||||
return this._lastBattery;
|
||||
};
|
||||
|
||||
Client.prototype._repeat = function(times, fn) {
|
||||
this._repeaters.push({times: times, method: fn});
|
||||
};
|
||||
|
||||
var pcmdOptions = [
|
||||
['up', 'down'],
|
||||
['left', 'right'],
|
||||
['front', 'back'],
|
||||
['clockwise', 'counterClockwise'],
|
||||
]
|
||||
|
||||
pcmdOptions.forEach(function(pair) {
|
||||
Client.prototype[pair[0]] = function(speed) {
|
||||
speed = parseFloat(speed);
|
||||
|
||||
this._pcmd[pair[0]] = speed;
|
||||
delete this._pcmd[pair[1]];
|
||||
|
||||
return speed;
|
||||
};
|
||||
|
||||
Client.prototype[pair[1]] = function(speed) {
|
||||
speed = parseFloat(speed);
|
||||
|
||||
this._pcmd[pair[1]] = speed;
|
||||
delete this._pcmd[pair[0]];
|
||||
|
||||
return speed;
|
||||
};
|
||||
});
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
var repl = require('repl');
|
||||
var vm = require('vm');
|
||||
|
||||
module.exports = Repl;
|
||||
function Repl(client) {
|
||||
this._client = client;
|
||||
this._repl = null;
|
||||
this._nodeEval = null;
|
||||
}
|
||||
|
||||
Repl.prototype.resume = function() {
|
||||
this._repl = repl.start({
|
||||
prompt : 'drone> ',
|
||||
});
|
||||
|
||||
this._nodeEval = this._repl.eval;
|
||||
|
||||
// @TODO This does not seem to work for animate('yawShake', 2000), need
|
||||
// to fix this.
|
||||
//this._repl.eval = this._eval.bind(this);
|
||||
|
||||
this._setupAutocomplete();
|
||||
};
|
||||
|
||||
Repl.prototype._setupAutocomplete = function() {
|
||||
for (var property in this._client) {
|
||||
if (property.substr(0, 1) === '_') {
|
||||
// Skip "private" properties
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = this._client[property];
|
||||
if (typeof value === 'function') {
|
||||
value = value.bind(this._client);
|
||||
}
|
||||
|
||||
this._repl.context[property] = value;
|
||||
}
|
||||
|
||||
this._repl.context.client = this._client;
|
||||
};
|
||||
|
||||
Repl.prototype._eval = function(code, context, filename, cb) {
|
||||
var args = code.match(/[^() \n]+/g);
|
||||
if (!args) {
|
||||
return this._nodeEval.apply(this._repl, arguments);
|
||||
}
|
||||
|
||||
var property = args.shift();
|
||||
if (!this._client[property]) {
|
||||
return this._nodeEval.apply(this._repl, arguments);
|
||||
}
|
||||
|
||||
var type = typeof this._client[property];
|
||||
|
||||
if (type === 'function') {
|
||||
try {
|
||||
cb(null, this._client[property].apply(this._client, args));
|
||||
} catch (err) {
|
||||
cb(err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length > 1) {
|
||||
// Don't accept more than 1 argument
|
||||
cb(new Error('Cannot set property "' + property + '". Too many arguments.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length === 1) {
|
||||
// Set a client property
|
||||
cb(null, this._client[property] = args[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
// args.length === 0, return property value
|
||||
cb(null, this._client[property]);
|
||||
};
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
// Constants required to decode/encode the network communication with the drone.
|
||||
// Names taken from the official SDK are not renamed, but:
|
||||
//
|
||||
// * Consistent prefixes/suffixes are stripped
|
||||
// * Always converted to UPPER_CASE
|
||||
|
||||
var constants = module.exports;
|
||||
|
||||
constants.getName = function(group, value) {
|
||||
group = this[group];
|
||||
|
||||
for (var name in group) {
|
||||
if (group[name] === value) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constants.DEFAULT_DRONE_IP = process.env.DEFAULT_DRONE_IP || '192.168.1.1';
|
||||
|
||||
// from ARDrone_SDK_2_0/ARDroneLib/Soft/Common/config.h
|
||||
constants.ports = {
|
||||
FTP : 5551,
|
||||
AUTH : 5552,
|
||||
VIDEO_RECORDER : 5553,
|
||||
NAVDATA : 5554,
|
||||
VIDEO : 5555,
|
||||
AT : 5556,
|
||||
RAW_CAPTURE : 5557,
|
||||
PRINTF : 5558,
|
||||
CONTROL : 5559
|
||||
};
|
||||
|
||||
// from ARDrone_SDK_2_0/ARDroneLib/Soft/Common/config.h
|
||||
constants.droneStates = {
|
||||
FLY_MASK : 1 << 0, /*!< FLY MASK : (0) ardrone is landed, (1) ardrone is flying */
|
||||
VIDEO_MASK : 1 << 1, /*!< VIDEO MASK : (0) video disable, (1) video enable */
|
||||
VISION_MASK : 1 << 2, /*!< VISION MASK : (0) vision disable, (1) vision enable */
|
||||
CONTROL_MASK : 1 << 3, /*!< CONTROL ALGO : (0) euler angles control, (1) angular speed control */
|
||||
ALTITUDE_MASK : 1 << 4, /*!< ALTITUDE CONTROL ALGO : (0) altitude control inactive (1) altitude control active */
|
||||
USER_FEEDBACK_START : 1 << 5, /*!< USER feedback : Start button state */
|
||||
COMMAND_MASK : 1 << 6, /*!< Control command ACK : (0) None, (1) one received */
|
||||
CAMERA_MASK : 1 << 7, /*!< CAMERA MASK : (0) camera not ready, (1) Camera ready */
|
||||
TRAVELLING_MASK : 1 << 8, /*!< Travelling mask : (0) disable, (1) enable */
|
||||
USB_MASK : 1 << 9, /*!< USB key : (0) usb key not ready, (1) usb key ready */
|
||||
NAVDATA_DEMO_MASK : 1 << 10, /*!< Navdata demo : (0) All navdata, (1) only navdata demo */
|
||||
NAVDATA_BOOTSTRAP : 1 << 11, /*!< Navdata bootstrap : (0) options sent in all or demo mode, (1) no navdata options sent */
|
||||
MOTORS_MASK : 1 << 12, /*!< Motors status : (0) Ok, (1) Motors problem */
|
||||
COM_LOST_MASK : 1 << 13, /*!< Communication Lost : (1) com problem, (0) Com is ok */
|
||||
SOFTWARE_FAULT : 1 << 14, /*!< Software fault detected - user should land as quick as possible (1) */
|
||||
VBAT_LOW : 1 << 15, /*!< VBat low : (1) too low, (0) Ok */
|
||||
USER_EL : 1 << 16, /*!< User Emergency Landing : (1) User EL is ON, (0) User EL is OFF*/
|
||||
TIMER_ELAPSED : 1 << 17, /*!< Timer elapsed : (1) elapsed, (0) not elapsed */
|
||||
MAGNETO_NEEDS_CALIB : 1 << 18, /*!< Magnetometer calibration state : (0) Ok, no calibration needed, (1) not ok, calibration needed */
|
||||
ANGLES_OUT_OF_RANGE : 1 << 19, /*!< Angles : (0) Ok, (1) out of range */
|
||||
WIND_MASK : 1 << 20, /*!< WIND MASK: (0) ok, (1) Too much wind */
|
||||
ULTRASOUND_MASK : 1 << 21, /*!< Ultrasonic sensor : (0) Ok, (1) deaf */
|
||||
CUTOUT_MASK : 1 << 22, /*!< Cutout system detection : (0) Not detected, (1) detected */
|
||||
PIC_VERSION_MASK : 1 << 23, /*!< PIC Version number OK : (0) a bad version number, (1) version number is OK */
|
||||
ATCODEC_THREAD_ON : 1 << 24, /*!< ATCodec thread ON : (0) thread OFF (1) thread ON */
|
||||
NAVDATA_THREAD_ON : 1 << 25, /*!< Navdata thread ON : (0) thread OFF (1) thread ON */
|
||||
VIDEO_THREAD_ON : 1 << 26, /*!< Video thread ON : (0) thread OFF (1) thread ON */
|
||||
ACQ_THREAD_ON : 1 << 27, /*!< Acquisition thread ON : (0) thread OFF (1) thread ON */
|
||||
CTRL_WATCHDOG_MASK : 1 << 28, /*!< CTRL watchdog : (1) delay in control execution (> 5ms), (0) control is well scheduled */
|
||||
ADC_WATCHDOG_MASK : 1 << 29, /*!< ADC Watchdog : (1) delay in uart2 dsr (> 5ms), (0) uart2 is good */
|
||||
COM_WATCHDOG_MASK : 1 << 30, /*!< Communication Watchdog : (1) com problem, (0) Com is ok */
|
||||
EMERGENCY_MASK : 1 << 31 /*!< Emergency landing : (0) no emergency, (1) emergency */
|
||||
};
|
||||
|
||||
|
||||
// from ARDrone_SDK_2_0/ARDroneLib/Soft/Common/navdata_keys.h
|
||||
constants.options = {
|
||||
DEMO : 0,
|
||||
TIME : 1,
|
||||
RAW_MEASURES : 2,
|
||||
PHYS_MEASURES : 3,
|
||||
GYROS_OFFSETS : 4,
|
||||
EULER_ANGLES : 5,
|
||||
REFERENCES : 6,
|
||||
TRIMS : 7,
|
||||
RC_REFERENCES : 8,
|
||||
PWM : 9,
|
||||
ALTITUDE : 10,
|
||||
VISION_RAW : 11,
|
||||
VISION_OF : 12,
|
||||
VISION : 13,
|
||||
VISION_PERF : 14,
|
||||
TRACKERS_SEND : 15,
|
||||
VISION_DETECT : 16,
|
||||
WATCHDOG : 17,
|
||||
ADC_DATA_FRAME : 18,
|
||||
VIDEO_STREAM : 19,
|
||||
GAMES : 20,
|
||||
PRESSURE_RAW : 21,
|
||||
MAGNETO : 22,
|
||||
WIND_SPEED : 23,
|
||||
KALMAN_PRESSURE : 24,
|
||||
HDVIDEO_STREAM : 25,
|
||||
WIFI : 26,
|
||||
ZIMMU_3000 : 27,
|
||||
CKS : 65535
|
||||
};
|
||||
|
||||
constants.CAD_TYPE = {
|
||||
HORIZONTAL : 0, /*<! Deprecated */
|
||||
VERTICAL : 1, /*<! Deprecated */
|
||||
VISION : 2, /*<! Detection of 2D horizontal tags on drone shells */
|
||||
NONE : 3, /*<! Detection disabled */
|
||||
COCARDE : 4, /*<! Detects a roundel under the drone */
|
||||
ROUNDEL : 4, /*<! Detects a roundel under the drone */
|
||||
ORIENTED_COCARDE : 5, /*<! Detects an oriented roundel under the drone */
|
||||
ORIENTED_ROUNDEL : 5, /*<! Detects an oriented roundel under the drone */
|
||||
STRIPE : 6, /*<! Detects a uniform stripe on the ground */
|
||||
H_COCARDE : 7, /*<! Detects a roundel in front of the drone */
|
||||
H_ROUNDEL : 7, /*<! Detects a roundel in front of the drone */
|
||||
H_ORIENTED_COCARDE : 8, /*<! Detects an oriented roundel in front of the drone */
|
||||
H_ORIENTED_ROUNDEL : 8, /*<! Detects an oriented roundel in front of the drone */
|
||||
STRIPE_V : 9,
|
||||
MULTIPLE_DETECTION_MODE : 10, /* The drone uses several detections at the same time */
|
||||
CAP : 11, /*<! Detects a Cap orange and green in front of the drone */
|
||||
ORIENTED_COCARDE_BW : 12, /*<! Detects the black and white roundel */
|
||||
ORIENTED_ROUNDEL_BW : 12, /*<! Detects the black and white roundel */
|
||||
VISION_V2 : 13, /*<! Detects 2nd version of shell/tag in front of the drone */
|
||||
TOWER_SIDE : 14, /*<! Detect a tower side with the front camera */
|
||||
NUM : 15 /*<! Number of possible values for CAD_TYPE */
|
||||
};
|
||||
|
||||
constants.TAG_TYPE = {
|
||||
NONE : 0,
|
||||
SHELL_TAG : 1,
|
||||
ROUNDEL : 2,
|
||||
ORIENTED_ROUNDEL : 3,
|
||||
STRIPE : 4,
|
||||
CAP : 5,
|
||||
SHELL_TAG_V2 : 6,
|
||||
TOWER_SIDE : 7,
|
||||
BLACK_ROUNDEL : 8,
|
||||
NUM : 9
|
||||
};
|
||||
|
||||
constants.FLYING_MODE = {
|
||||
FREE_FLIGHT : 0, /**< Normal mode, commands are enabled */
|
||||
HOVER_ON_TOP_OF_ROUNDEL : 1 << 0, /**< Commands are disabled, drones hovers on top of a roundel - roundel detection MUST be activated by the user with 'detect_type' configuration. */
|
||||
HOVER_ON_TOP_OF_ORIENTED_ROUNDEL : 1 << 1 /**< Commands are disabled, drones hovers on top of an oriented roundel - oriented roundel detection MUST be activated by the user with 'detect_type' configuration. */
|
||||
};
|
||||
|
||||
constants.ARDRONE_DETECTION_COLOR = {
|
||||
ORANGE_GREEN : 1, /*!< Cameras detect orange-green-orange tags */
|
||||
ORANGE_YELLOW : 2, /*!< Cameras detect orange-yellow-orange tags*/
|
||||
ORANGE_BLUE : 3, /*!< Cameras detect orange-blue-orange tags */
|
||||
ARRACE_FINISH_LINE : 0x10,
|
||||
ARRACE_DONUT : 0x11
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
module.exports = AtCommand;
|
||||
function AtCommand(type, number, args) {
|
||||
this.type = type;
|
||||
this.number = number;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
AtCommand.prototype.toString = function() {
|
||||
var args = [this.number].concat(this.args);
|
||||
return 'AT*' + this.type + '=' + args.join(',') + '\r';
|
||||
};
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
var AtCommand = require('./AtCommand');
|
||||
var at = require('./at');
|
||||
|
||||
var exports = module.exports = AtCommandCreator;
|
||||
function AtCommandCreator() {
|
||||
this._number = 0;
|
||||
}
|
||||
|
||||
AtCommandCreator.prototype.raw = function(type, args) {
|
||||
args = (Array.isArray(args))
|
||||
? args
|
||||
: Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
return new AtCommand(type, this._number++, args);
|
||||
};
|
||||
|
||||
// Used for fly/land as well as emergency trigger/recover
|
||||
AtCommandCreator.prototype.ref = function(options) {
|
||||
options = options || {};
|
||||
|
||||
var args = [0];
|
||||
|
||||
if (options.fly) {
|
||||
args[0] = args[0] | REF_FLAGS.takeoff;
|
||||
}
|
||||
|
||||
if (options.emergency) {
|
||||
args[0] = args[0] | REF_FLAGS.emergency;
|
||||
}
|
||||
|
||||
return this.raw('REF', args);
|
||||
};
|
||||
|
||||
// Used to fly the drone around
|
||||
AtCommandCreator.prototype.pcmd = function(options) {
|
||||
options = options || {};
|
||||
|
||||
// flags, leftRight, frontBack, upDown, clockWise
|
||||
var args = [0, 0, 0, 0, 0];
|
||||
|
||||
for (var key in options) {
|
||||
var alias = PCMD_ALIASES[key];
|
||||
var value = options[key];
|
||||
|
||||
if (alias.invert) {
|
||||
value = -value;
|
||||
}
|
||||
|
||||
args[alias.index] = at.floatString(value);
|
||||
args[0] = args[0] | PCMD_FLAGS.progressive;
|
||||
}
|
||||
|
||||
return this.raw('PCMD', args);
|
||||
};
|
||||
|
||||
AtCommandCreator.prototype.calibrate = function(device_num) {
|
||||
var args = [device_num];
|
||||
|
||||
return this.raw('CALIB', args);
|
||||
};
|
||||
|
||||
AtCommandCreator.prototype.config = function(name, value) {
|
||||
return this.raw('CONFIG', '"' + name + '"', '"' + value + '"');
|
||||
};
|
||||
|
||||
AtCommandCreator.prototype.animateLeds = function(name, hz, duration) {
|
||||
// Default animation
|
||||
name = name || 'redSnake';
|
||||
hz = hz || 2;
|
||||
duration = duration || 3;
|
||||
|
||||
var animationId = LED_ANIMATIONS.indexOf(name);
|
||||
if (animationId < 0) {
|
||||
throw new Error('Unknown led animation: ' + name);
|
||||
}
|
||||
|
||||
hz = at.floatString(hz);
|
||||
|
||||
var params = [animationId, hz, duration].join(',');
|
||||
return this.config('leds:leds_anim', params);
|
||||
};
|
||||
|
||||
AtCommandCreator.prototype.animate = function(name, duration) {
|
||||
var animationId = ANIMATIONS.indexOf(name);
|
||||
if (animationId < 0) {
|
||||
throw new Error('Unknown animation: ' + name);
|
||||
}
|
||||
|
||||
var params = [animationId, duration].join(',');
|
||||
return this.config('control:flight_anim', params);
|
||||
};
|
||||
|
||||
// Constants
|
||||
|
||||
var REF_FLAGS = exports.REF_FLAGS = {
|
||||
emergency : (1 << 8),
|
||||
takeoff : (1 << 9),
|
||||
};
|
||||
|
||||
var PCMD_FLAGS = exports.PCMD_FLAGS = {
|
||||
progressive : (1 << 0),
|
||||
};
|
||||
|
||||
var PCMD_ALIASES = exports.PCMD_ALIASES = {
|
||||
left : {index: 1, invert: true},
|
||||
right : {index: 1, invert: false},
|
||||
front : {index: 2, invert: true},
|
||||
back : {index: 2, invert: false},
|
||||
up : {index: 3, invert: false},
|
||||
down : {index: 3, invert: true},
|
||||
clockwise : {index: 4, invert: false},
|
||||
counterClockwise : {index: 4, invert: true},
|
||||
};
|
||||
|
||||
// from ARDrone_SDK_2_0/ControlEngine/iPhone/Release/ARDroneGeneratedTypes.h
|
||||
var LED_ANIMATIONS = exports.LED_ANIMATIONS = [
|
||||
'blinkGreenRed',
|
||||
'blinkGreen',
|
||||
'blinkRed',
|
||||
'blinkOrange',
|
||||
'snakeGreenRed',
|
||||
'fire',
|
||||
'standard',
|
||||
'red',
|
||||
'green',
|
||||
'redSnake',
|
||||
'blank',
|
||||
'rightMissile',
|
||||
'leftMissile',
|
||||
'doubleMissile',
|
||||
'frontLeftGreenOthersRed',
|
||||
'frontRightGreenOthersRed',
|
||||
'rearRightGreenOthersRed',
|
||||
'rearLeftGreenOthersRed',
|
||||
'leftGreenRightRed',
|
||||
'leftRedRightGreen',
|
||||
'blinkStandard',
|
||||
];
|
||||
|
||||
// from ARDrone_SDK_2_0/ControlEngine/iPhone/Release/ARDroneGeneratedTypes.h
|
||||
var ANIMATIONS = exports.ANIMATIONS = [
|
||||
'phiM30Deg',
|
||||
'phi30Deg',
|
||||
'thetaM30Deg',
|
||||
'theta30Deg',
|
||||
'theta20degYaw200deg',
|
||||
'theta20degYawM200deg',
|
||||
'turnaround',
|
||||
'turnaroundGodown',
|
||||
'yawShake',
|
||||
'yawDance',
|
||||
'phiDance',
|
||||
'thetaDance',
|
||||
'vzDance',
|
||||
'wave',
|
||||
'phiThetaMixed',
|
||||
'doublePhiThetaMixed',
|
||||
'flipAhead',
|
||||
'flipBehind',
|
||||
'flipLeft',
|
||||
'flipRight',
|
||||
];
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
var AtCommandCreator = require('./AtCommandCreator');
|
||||
var meta = require('../misc/meta');
|
||||
var constants = require('../constants');
|
||||
var dgram = require('dgram');
|
||||
|
||||
module.exports = UdpControl;
|
||||
function UdpControl(options) {
|
||||
options = options || {};
|
||||
|
||||
this._socket = options.socket || dgram.createSocket('udp4');
|
||||
this._port = options.port || constants.ports.AT;
|
||||
this._ip = options.ip || constants.DEFAULT_DRONE_IP;
|
||||
this._cmdCreator = new AtCommandCreator();
|
||||
this._cmds = [];
|
||||
}
|
||||
|
||||
meta.methods(AtCommandCreator).forEach(function(methodName) {
|
||||
UdpControl.prototype[methodName] = function() {
|
||||
var cmd = this._cmdCreator[methodName].apply(this._cmdCreator, arguments);
|
||||
this._cmds.push(cmd);
|
||||
return cmd;
|
||||
};
|
||||
});
|
||||
|
||||
UdpControl.prototype.flush = function() {
|
||||
if (!this._cmds.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var buffer = new Buffer(this._concat(this._cmds));
|
||||
this._socket.send(buffer, 0, buffer.length, this._port, this._ip);
|
||||
|
||||
this._cmds = [];
|
||||
};
|
||||
|
||||
UdpControl.prototype._concat = function(commands) {
|
||||
return this._cmds.reduce(function(cmds, cmd) {
|
||||
return cmds + cmd;
|
||||
}, '');
|
||||
};
|
||||
|
||||
UdpControl.prototype.close = function() {
|
||||
this._socket.close();
|
||||
};
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// module for generic AT command related functionality
|
||||
var at = exports;
|
||||
|
||||
at.floatString = function(number) {
|
||||
// Not sure if this is correct, but it works for the example provided in
|
||||
// the drone manual ... (should be revisted)
|
||||
var buffer = new Buffer(4);
|
||||
buffer.writeFloatBE(number, 0);
|
||||
return -~parseInt(buffer.toString('hex'), 16) - 1;
|
||||
};
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
var meta = exports;
|
||||
|
||||
// lists public methods of a class
|
||||
meta.methods = function(Constructor) {
|
||||
var methods = Object.keys(Constructor.prototype);
|
||||
|
||||
return methods.filter(function(name) {
|
||||
return !/^_/.test(name);
|
||||
});
|
||||
};
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
var util = require('util');
|
||||
var Reader = require('buffy').Reader;
|
||||
|
||||
module.exports = NavdataReader;
|
||||
util.inherits(NavdataReader, Reader);
|
||||
function NavdataReader(buffer) {
|
||||
Reader.call(this, buffer);
|
||||
}
|
||||
|
||||
NavdataReader.prototype.int16 = function() {
|
||||
return this.int16LE();
|
||||
};
|
||||
|
||||
NavdataReader.prototype.uint16 = function() {
|
||||
return this.uint16LE();
|
||||
};
|
||||
|
||||
NavdataReader.prototype.int32 = function() {
|
||||
return this.int32LE();
|
||||
};
|
||||
|
||||
NavdataReader.prototype.uint32 = function() {
|
||||
return this.uint32LE();
|
||||
};
|
||||
|
||||
NavdataReader.prototype.float32 = function() {
|
||||
return this.float32LE();
|
||||
};
|
||||
|
||||
NavdataReader.prototype.char = function() {
|
||||
return this._buffer[ this._offset ];
|
||||
};
|
||||
|
||||
NavdataReader.prototype.bool = function() {
|
||||
return !!this.char();
|
||||
};
|
||||
|
||||
NavdataReader.prototype.matrix33 = function() {
|
||||
return {
|
||||
m11 : this.float32(),
|
||||
m12 : this.float32(),
|
||||
m13 : this.float32(),
|
||||
m21 : this.float32(),
|
||||
m22 : this.float32(),
|
||||
m23 : this.float32(),
|
||||
m31 : this.float32(),
|
||||
m32 : this.float32(),
|
||||
m33 : this.float32()
|
||||
};
|
||||
};
|
||||
|
||||
NavdataReader.prototype.vector31 = function() {
|
||||
return {
|
||||
x : this.float32(),
|
||||
y : this.float32(),
|
||||
z : this.float32()
|
||||
};
|
||||
};
|
||||
|
||||
NavdataReader.prototype.screenPoint = function() {
|
||||
return {
|
||||
x : this.int32(),
|
||||
y : this.int32()
|
||||
};
|
||||
};
|
||||
|
||||
NavdataReader.prototype.mask32 = function(mask) {
|
||||
var value = this.uint32();
|
||||
return this._mask(mask, value);
|
||||
};
|
||||
|
||||
NavdataReader.prototype._mask = function(mask, value) {
|
||||
var flags = {};
|
||||
for (var flag in mask) {
|
||||
flags[flag] = (value & mask[flag])
|
||||
? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
return flags;
|
||||
};
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
var Stream = require('stream').Stream;
|
||||
var util = require('util');
|
||||
var dgram = require('dgram');
|
||||
var constants = require('../constants');
|
||||
var parseNavdata = require('./parseNavdata');
|
||||
|
||||
module.exports = UdpNavdataStream;
|
||||
util.inherits(UdpNavdataStream, Stream);
|
||||
function UdpNavdataStream(options) {
|
||||
Stream.call(this);
|
||||
|
||||
options = options || {};
|
||||
|
||||
this.readable = true;
|
||||
this._socket = options.socket || dgram.createSocket('udp4');
|
||||
this._port = options.port || constants.ports.NAVDATA;
|
||||
this._ip = options.ip || constants.DEFAULT_DRONE_IP;
|
||||
this._initialized = false;
|
||||
this._parseNavdata = options.parser || parseNavdata;
|
||||
this._timeout = options.timeout || 100;
|
||||
this._timer = undefined;
|
||||
this._sequenceNumber = 0;
|
||||
}
|
||||
|
||||
UdpNavdataStream.prototype.resume = function() {
|
||||
if (!this._initialized) {
|
||||
this._init();
|
||||
this._initialized = true;
|
||||
}
|
||||
|
||||
this._requestNavdata();
|
||||
};
|
||||
|
||||
UdpNavdataStream.prototype.destroy = function() {
|
||||
this._socket.close();
|
||||
};
|
||||
|
||||
UdpNavdataStream.prototype._init = function() {
|
||||
this._socket.bind();
|
||||
this._socket.on('message', this._handleMessage.bind(this));
|
||||
};
|
||||
|
||||
UdpNavdataStream.prototype._requestNavdata = function() {
|
||||
var buffer = new Buffer([1]);
|
||||
this._socket.send(buffer, 0, buffer.length, this._port, this._ip);
|
||||
|
||||
this._setTimeout();
|
||||
|
||||
// @TODO logging
|
||||
};
|
||||
|
||||
UdpNavdataStream.prototype._setTimeout = function() {
|
||||
clearTimeout(this._timer);
|
||||
this._timer = setTimeout(this._requestNavdata.bind(this), this._timeout);
|
||||
};
|
||||
|
||||
UdpNavdataStream.prototype._handleMessage = function(buffer) {
|
||||
try {
|
||||
var navdata = this._parseNavdata(buffer);
|
||||
} catch (err) {
|
||||
// avoid 'error' causing an exception when nobody is listening
|
||||
if (this.listeners('error').length > 0) {
|
||||
this.emit('error', err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore out of order messages
|
||||
if (navdata.sequenceNumber > this._sequenceNumber) {
|
||||
this._sequenceNumber = navdata.sequenceNumber;
|
||||
this.emit('data', navdata);
|
||||
}
|
||||
|
||||
this._setTimeout();
|
||||
};
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
var constants = require('../constants');
|
||||
|
||||
exports.NAVDATA_NUM_TAGS = Object.keys(constants.options).length;
|
||||
exports.NAVDATA_OPTION_FULL_MASK = (1<<exports.NAVDATA_NUM_TAGS) - 1;
|
||||
|
||||
exports.TAG_TYPE_MASK = function (tagtype) {
|
||||
return (tagtype == 0) ? 0 : 1 << (tagtype - 1);
|
||||
};
|
||||
|
||||
exports.NAVDATA_OPTION_MASK = function (option) {
|
||||
return 1 << option;
|
||||
};
|
||||
|
||||
exports.maskFromNavdataOptions = function (options) {
|
||||
if (!Array.isArray(options)) {
|
||||
options = Array.prototype.slice.call(arguments);
|
||||
}
|
||||
|
||||
var masks = options.map(exports.NAVDATA_OPTION_MASK);
|
||||
var mask = masks.reduce(function (prev, curr) {
|
||||
return prev | curr;
|
||||
});
|
||||
|
||||
return mask;
|
||||
};
|
||||
+645
@@ -0,0 +1,645 @@
|
||||
var NavdataReader = require('./NavdataReader');
|
||||
|
||||
// call `iterator` `n` times, returning an array of the results
|
||||
var timesMap = function(n, iterator, context) {
|
||||
var results = [];
|
||||
for (var i = 0; i < n; i++) results[i] = iterator.call(context, i);
|
||||
return results;
|
||||
};
|
||||
|
||||
var droneTimeToMilliSeconds = function (time) {
|
||||
// first 11 bits are seconds
|
||||
var seconds = time >> 21;
|
||||
// last 21 bits are microseconds
|
||||
var microseconds = (time << 11) >> 11;
|
||||
|
||||
// Convert to ms (which is idiomatic for time in JS)
|
||||
return seconds * 1000 + microseconds / 1000;
|
||||
};
|
||||
|
||||
var exports = module.exports = function parseNavdata(buffer) {
|
||||
var reader = new NavdataReader(buffer);
|
||||
|
||||
var navdata = {};
|
||||
|
||||
navdata.header = reader.uint32();
|
||||
if (navdata.header !== exports.NAVDATA_HEADER) {
|
||||
throw new Error('Invalid header: 0x' + navdata.header.toString(16));
|
||||
}
|
||||
|
||||
navdata.droneState = reader.mask32(exports.DRONE_STATES);
|
||||
navdata.sequenceNumber = reader.uint32();
|
||||
navdata.visionFlag = reader.uint32();
|
||||
|
||||
while (true) {
|
||||
var optionId = reader.uint16();
|
||||
var optionName = exports.OPTION_IDS[optionId];
|
||||
var length = reader.uint16();
|
||||
|
||||
// length includes header size (4 bytes)
|
||||
var optionReader = new NavdataReader(reader.buffer(length - 4));
|
||||
|
||||
if (optionName == 'checksum') {
|
||||
var expectedChecksum = 0;
|
||||
for (var i = 0; i < buffer.length - length; i++) {
|
||||
expectedChecksum += buffer[i];
|
||||
}
|
||||
|
||||
var checksum = optionReader.uint32();
|
||||
|
||||
if (checksum !== expectedChecksum) {
|
||||
throw new Error('Invalid checksum, expected: ' + expectedChecksum + ', got: ' + checksum);
|
||||
}
|
||||
|
||||
// checksum is the last option
|
||||
break;
|
||||
}
|
||||
|
||||
// parse option according to ARDrone_SDK_2_0/ARDroneLib/Soft/Common/navdata_common.h)
|
||||
navdata[optionName] = (exports.OPTION_PARSERS[optionName])
|
||||
? exports.OPTION_PARSERS[optionName](optionReader)
|
||||
: new Error('Option not implemented yet: ' + optionName + ' (0x' + optionId.toString(16) + ')');
|
||||
}
|
||||
|
||||
return navdata;
|
||||
};
|
||||
|
||||
exports.OPTION_PARSERS = {
|
||||
'demo': function(reader) {
|
||||
// simplified version of ctrl_state_str (in ARDrone_SDK_2_0/Examples/Linux/Navigation/Sources/navdata_client/navdata_ihm.c)
|
||||
var flyState = exports.FLY_STATES[reader.uint16()];
|
||||
var controlState = exports.CONTROL_STATES[reader.uint16()];
|
||||
var batteryPercentage = reader.uint32();
|
||||
var theta = reader.float32() / 1000; // [mdeg]
|
||||
var phi = reader.float32() / 1000; // [mdeg]
|
||||
var psi = reader.float32() / 1000; // [mdeg]
|
||||
var altitude = reader.int32() / 1000; // [mm]
|
||||
var velocity = reader.vector31(); // [mm/s]
|
||||
var frameIndex = reader.uint32();
|
||||
var detection = {
|
||||
camera: {
|
||||
rotation : reader.matrix33(),
|
||||
translation : reader.vector31()
|
||||
},
|
||||
tagIndex: reader.uint32()
|
||||
};
|
||||
detection.camera.type = reader.uint32();
|
||||
var drone = {
|
||||
camera: {
|
||||
rotation : reader.matrix33(),
|
||||
translation : reader.vector31()
|
||||
}
|
||||
};
|
||||
var rotation = {
|
||||
frontBack : theta,
|
||||
pitch : theta,
|
||||
theta : theta,
|
||||
y : theta,
|
||||
leftRight : phi,
|
||||
roll : phi,
|
||||
phi : phi,
|
||||
x : phi,
|
||||
clockwise : psi,
|
||||
yaw : psi,
|
||||
psi : psi,
|
||||
z : psi
|
||||
};
|
||||
|
||||
return {
|
||||
controlState : controlState,
|
||||
flyState : flyState,
|
||||
batteryPercentage : batteryPercentage,
|
||||
rotation : rotation,
|
||||
frontBackDegrees : theta,
|
||||
leftRightDegrees : phi,
|
||||
clockwiseDegrees : psi,
|
||||
altitude : altitude,
|
||||
altitudeMeters : altitude,
|
||||
velocity : velocity,
|
||||
xVelocity : velocity.x,
|
||||
yVelocity : velocity.y,
|
||||
zVelocity : velocity.z,
|
||||
frameIndex : frameIndex,
|
||||
detection : detection,
|
||||
drone : drone
|
||||
};
|
||||
},
|
||||
|
||||
'time': function(reader) {
|
||||
var time = reader.uint32();
|
||||
return droneTimeToMilliSeconds(time);
|
||||
},
|
||||
|
||||
'rawMeasures': function(reader) {
|
||||
var accelerometers = {
|
||||
x: reader.uint16(), // [LSB]
|
||||
y: reader.uint16(), // [LSB]
|
||||
z: reader.uint16() // [LSB]
|
||||
};
|
||||
var gyroscopes = {
|
||||
x: reader.int16(), // [LSB]
|
||||
y: reader.int16(), // [LSB]
|
||||
z: reader.int16() // [LSB]
|
||||
};
|
||||
// gyroscopes x/y 110 deg/s (@TODO figure out what that means)
|
||||
var gyroscopes110 = {
|
||||
x: reader.int16(), // [LSB]
|
||||
y: reader.int16() // [LSB]
|
||||
};
|
||||
var batteryMilliVolt = reader.uint32(); // [mV]
|
||||
// no idea what any of those are
|
||||
var usEcho = {
|
||||
start : reader.uint16(), // [LSB]
|
||||
end : reader.uint16(), // [LSB]
|
||||
association : reader.uint16(), // [LSB?]
|
||||
distance : reader.uint16() // [LSB]
|
||||
};
|
||||
var usCurve = {
|
||||
time : reader.uint16(), // [LSB]
|
||||
value : reader.uint16(), // [LSB]
|
||||
ref : reader.uint16() // [LSB]
|
||||
};
|
||||
var echo = {
|
||||
flagIni : reader.uint16(), // [LSB]
|
||||
num : reader.uint16(), // [LSB] (is key `num` OR `name`?)
|
||||
sum : reader.uint32() // [LSB]
|
||||
};
|
||||
var altTemp = reader.int32(); // [mm]
|
||||
|
||||
return {
|
||||
accelerometers : accelerometers,
|
||||
gyroscopes : gyroscopes,
|
||||
gyrometers : gyroscopes,
|
||||
gyroscopes110 : gyroscopes110,
|
||||
gyrometers110 : [gyroscopes110.x, gyroscopes110.y],
|
||||
batteryMilliVolt : batteryMilliVolt,
|
||||
us : {echo: usEcho, curve: usCurve},
|
||||
usDebutEcho : usEcho.start,
|
||||
usFinEcho : usEcho.end,
|
||||
usAssociationEcho : usEcho.association,
|
||||
usDistanceEcho : usEcho.distance,
|
||||
usCourbeTemps : usCurve.time,
|
||||
usCourbeValeur : usCurve.value,
|
||||
usCourbeRef : usCurve.ref,
|
||||
echo : echo,
|
||||
flagEchoIni : echo.flagIni,
|
||||
nbEcho : echo.num,
|
||||
sumEcho : echo.sum,
|
||||
altTemp : altTemp,
|
||||
altTempRaw : altTemp
|
||||
};
|
||||
},
|
||||
|
||||
'physMeasures': function(reader) {
|
||||
return {
|
||||
temperature: {
|
||||
accelerometer: reader.float32(), // [K]
|
||||
gyroscope: reader.uint16() // [LSB]
|
||||
},
|
||||
accelerometers : reader.vector31(), // [mg]
|
||||
gyroscopes : reader.vector31(), // [deg/s]
|
||||
alim3V3 : reader.uint32(), // [LSB]
|
||||
vrefEpson : reader.uint32(), // [LSB]
|
||||
vrefIDG : reader.uint32() // [LSB]
|
||||
};
|
||||
},
|
||||
|
||||
'gyrosOffsets': function(reader) {
|
||||
return reader.vector31(); // [LSB]
|
||||
},
|
||||
|
||||
'eulerAngles': function(reader) {
|
||||
return {
|
||||
theta : reader.float32(), // [mdeg?]
|
||||
phi : reader.float32() // [mdeg?]
|
||||
};
|
||||
},
|
||||
|
||||
'references': function(reader) {
|
||||
return {
|
||||
theta : reader.int32(), // [mdeg]
|
||||
phi : reader.int32(), // [mdeg]
|
||||
thetaI : reader.int32(), // [mdeg]
|
||||
phiI : reader.int32(), // [mdeg]
|
||||
pitch : reader.int32(), // [mdeg]
|
||||
roll : reader.int32(), // [mdeg]
|
||||
yaw : reader.int32(), // [mdeg/s]
|
||||
psi : reader.int32(), // [mdeg]
|
||||
|
||||
vx : reader.float32(),
|
||||
vy : reader.float32(),
|
||||
thetaMod : reader.float32(),
|
||||
phiMod : reader.float32(),
|
||||
|
||||
kVX : reader.float32(),
|
||||
kVY : reader.float32(),
|
||||
kMode : reader.uint32(),
|
||||
|
||||
ui : {
|
||||
time : reader.float32(),
|
||||
theta : reader.float32(),
|
||||
phi : reader.float32(),
|
||||
psi : reader.float32(),
|
||||
psiAccuracy : reader.float32(),
|
||||
seq : reader.int32()
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
'trims': function(reader) {
|
||||
return {
|
||||
angularRates : {
|
||||
r : reader.float32()
|
||||
},
|
||||
eulerAngles : {
|
||||
theta : reader.float32(), // [mdeg?]
|
||||
phi : reader.float32() // [mdeg?]
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
'rcReferences': function(reader) {
|
||||
return {
|
||||
pitch : reader.int32(), // [mdeg?]
|
||||
roll : reader.int32(), // [mdeg?]
|
||||
yaw : reader.int32(), // [mdeg/s?]
|
||||
gaz : reader.int32(),
|
||||
ag : reader.int32()
|
||||
};
|
||||
},
|
||||
|
||||
'pwm': function(reader) {
|
||||
return {
|
||||
motors : timesMap(4, reader.uint8, reader), // [PWM]
|
||||
satMotors : timesMap(4, reader.uint8, reader), // [PWM]
|
||||
gazFeedForward : reader.float32(), // [PWM]
|
||||
gazAltitude : reader.float32(), // [PWM]
|
||||
altitudeIntegral : reader.float32(), // [mm/s]
|
||||
vzRef : reader.float32(), // [mm/s]
|
||||
uPitch : reader.int32(), // [PWM]
|
||||
uRoll : reader.int32(), // [PWM]
|
||||
uYaw : reader.int32(), // [PWM]
|
||||
yawUI : reader.float32(), // [PWM] yaw_u_I
|
||||
uPitchPlanif : reader.int32(), // [PWM]
|
||||
uRollPlanif : reader.int32(), // [PWM]
|
||||
uYawPlanif : reader.int32(), // [PWM]
|
||||
uGazPlanif : reader.float32(), // [PWM]
|
||||
motorCurrents : timesMap(4, reader.uint16, reader), // [mA]
|
||||
altitudeProp : reader.float32(), // [PWM]
|
||||
altitudeDer : reader.float32() // [PWM]
|
||||
};
|
||||
},
|
||||
|
||||
'altitude': function(reader) {
|
||||
return {
|
||||
vision : reader.int32(), // [mm]
|
||||
velocity : reader.float32(), // [mm/s]
|
||||
ref : reader.int32(), // [mm]
|
||||
raw : reader.int32(), // [mm]
|
||||
observer : {
|
||||
acceleration : reader.float32(), // [m/s2]
|
||||
altitude : reader.float32(), // [m]
|
||||
x : reader.vector31(),
|
||||
state : reader.uint32()
|
||||
},
|
||||
estimated : {
|
||||
vb : {
|
||||
x : reader.float32(),
|
||||
y : reader.float32()
|
||||
},
|
||||
state : reader.uint32()
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
'visionRaw': function(reader) {
|
||||
return {
|
||||
tx : reader.float32(),
|
||||
ty : reader.float32(),
|
||||
tz : reader.float32()
|
||||
};
|
||||
},
|
||||
|
||||
'visionOf': function(reader) {
|
||||
return {
|
||||
dx : timesMap(5, reader.float32, reader),
|
||||
dy : timesMap(5, reader.float32, reader)
|
||||
};
|
||||
},
|
||||
|
||||
'vision': function(reader) {
|
||||
return {
|
||||
state : reader.uint32(),
|
||||
misc : reader.int32(),
|
||||
phi: {
|
||||
trim : reader.float32(), // [rad]
|
||||
refProp : reader.float32() // [rad]
|
||||
},
|
||||
theta: {
|
||||
trim : reader.float32(), // [rad]
|
||||
refProp : reader.float32() // [rad]
|
||||
},
|
||||
newRawPicture : reader.int32(),
|
||||
capture : {
|
||||
theta : reader.float32(),
|
||||
phi : reader.float32(),
|
||||
psi : reader.float32(),
|
||||
altitude : reader.int32(),
|
||||
time : droneTimeToMilliSeconds(reader.uint32())
|
||||
},
|
||||
bodyV : reader.vector31(), // [mm/s]
|
||||
delta : {
|
||||
phi : reader.float32(),
|
||||
theta : reader.float32(),
|
||||
psi : reader.float32()
|
||||
},
|
||||
gold : {
|
||||
defined : reader.uint32(),
|
||||
reset : reader.uint32(),
|
||||
x : reader.float32(),
|
||||
y : reader.float32()
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
'visionPerf': function(reader) {
|
||||
return {
|
||||
szo : reader.float32(),
|
||||
corners : reader.float32(),
|
||||
compute : reader.float32(),
|
||||
tracking : reader.float32(),
|
||||
trans : reader.float32(),
|
||||
update : reader.float32(),
|
||||
custom : timesMap(20, reader.float32, reader)
|
||||
};
|
||||
},
|
||||
|
||||
'trackersSend': function(reader) {
|
||||
return {
|
||||
locked : timesMap(30, reader.int32, reader),
|
||||
point : timesMap(30, reader.screenPoint, reader)
|
||||
};
|
||||
},
|
||||
|
||||
'visionDetect': function(reader) {
|
||||
return {
|
||||
nbDetected : reader.uint32(),
|
||||
type : timesMap(4, reader.uint32, reader),
|
||||
xc : timesMap(4, reader.uint32, reader),
|
||||
yc : timesMap(4, reader.uint32, reader),
|
||||
width : timesMap(4, reader.uint32, reader),
|
||||
height : timesMap(4, reader.uint32, reader),
|
||||
dist : timesMap(4, reader.uint32, reader),
|
||||
orientationAngle : timesMap(4, reader.uint32, reader),
|
||||
rotation : timesMap(4, reader.matrix33, reader),
|
||||
translation : timesMap(4, reader.vector31, reader),
|
||||
cameraSource : timesMap(4, reader.uint32, reader)
|
||||
};
|
||||
},
|
||||
|
||||
'watchdog': function(reader) {
|
||||
return reader.uint32();
|
||||
},
|
||||
|
||||
'adcDataFrame': function(reader) {
|
||||
return {
|
||||
version : reader.uint32(),
|
||||
dataFrame : timesMap(32, reader.uint8, reader)
|
||||
};
|
||||
},
|
||||
|
||||
'videoStream': function(reader) {
|
||||
return {
|
||||
quant : reader.uint8(),
|
||||
frame : {
|
||||
size : reader.uint32(), // [bytes]
|
||||
number : reader.uint32()
|
||||
},
|
||||
atcmd : {
|
||||
sequence : reader.uint32(),
|
||||
meanGap : reader.uint32(), // [ms]
|
||||
varGap : reader.float32(), // [SU]
|
||||
quality : reader.uint32()
|
||||
},
|
||||
bitrate : {
|
||||
out : reader.uint32(),
|
||||
desired : reader.uint32()
|
||||
},
|
||||
data : timesMap(5, reader.int32, reader),
|
||||
tcpQueueLevel : reader.uint32(),
|
||||
fifoQueueLevel : reader.uint32()
|
||||
};
|
||||
},
|
||||
|
||||
'games': function(reader) {
|
||||
return {
|
||||
counters: {
|
||||
doubleTap : reader.uint32(),
|
||||
finishLine : reader.uint32()
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
'pressureRaw': function(reader) {
|
||||
return {
|
||||
up : reader.int32(), // [LSB] (UP?)
|
||||
ut : reader.int16(), // [LSB] (UT?)
|
||||
temperature : reader.int32(), // [0_1C]
|
||||
pressure : reader.int32() // [Pa]
|
||||
};
|
||||
},
|
||||
|
||||
'magneto': function(reader) {
|
||||
return {
|
||||
mx : reader.int16(), // [LSB]
|
||||
my : reader.int16(), // [LSB]
|
||||
mz : reader.int16(), // [LSB]
|
||||
raw : reader.vector31(), // [mG]
|
||||
rectified : reader.vector31(), // [mG]
|
||||
offset : reader.vector31(), // [mG]
|
||||
heading : {
|
||||
unwrapped: reader.float32(), // [deg]
|
||||
gyroUnwrapped: reader.float32(), // [deg]
|
||||
fusionUnwrapped: reader.float32() // [mdeg]
|
||||
},
|
||||
ok : reader.char(),
|
||||
state : reader.uint32(), // [SU]
|
||||
radius : reader.float32(), // [mG]
|
||||
error : {
|
||||
mean : reader.float32(), // [mG]
|
||||
variance : reader.float32() // [mG2]
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
'windSpeed': function(reader) {
|
||||
return {
|
||||
speed: reader.float32(), // [m/s]
|
||||
angle: reader.float32(), // [deg]
|
||||
compensation: {
|
||||
theta: reader.float32(), // [rad]
|
||||
phi: reader.float32()
|
||||
},
|
||||
stateX: timesMap(6, reader.float32, reader), // [SU]
|
||||
debug: timesMap(3, reader.float32, reader)
|
||||
};
|
||||
},
|
||||
|
||||
'kalmanPressure': function(reader) {
|
||||
return {
|
||||
offsetPressure: reader.float32(), // [Pa]
|
||||
estimated: {
|
||||
altitude: reader.float32(), // [mm]
|
||||
velocity: reader.float32(), // [m/s]
|
||||
angle: {
|
||||
pwm: reader.float32(), // [m/s2]
|
||||
pressure: reader.float32() // [Pa]
|
||||
},
|
||||
us: {
|
||||
offset: reader.float32(), // [m]
|
||||
prediction: reader.float32() // [mm]
|
||||
},
|
||||
covariance: {
|
||||
alt: reader.float32(), // [m]
|
||||
pwm: reader.float32(),
|
||||
velocity: reader.float32() // [m/s]
|
||||
},
|
||||
groundEffect: reader.bool(), // [SU]
|
||||
sum: reader.float32(), // [mm]
|
||||
reject: reader.bool(), // [SU]
|
||||
uMultisinus: reader.float32(),
|
||||
gazAltitude: reader.float32(),
|
||||
flagMultisinus: reader.bool(),
|
||||
flagMultisinusStart: reader.bool()
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
'hdvideoStream': function(reader) {
|
||||
var values = {
|
||||
hdvideoState : reader.uint32(),
|
||||
storageFifo : {
|
||||
nbPackets : reader.uint32(),
|
||||
size : reader.uint32()
|
||||
},
|
||||
usbkey : {
|
||||
size : reader.uint32(),
|
||||
freespace : reader.uint32()
|
||||
},
|
||||
frameNumber : reader.uint32()
|
||||
};
|
||||
values.usbkey.remainingTime = reader.uint32();
|
||||
|
||||
return values;
|
||||
},
|
||||
|
||||
'wifi': function(reader) {
|
||||
return {
|
||||
// from ARDrone_SDK_2_0/ControlEngine/iPhone/Classes/Controllers/MainViewController.m
|
||||
linkQuality: (1 - (reader.float32()))
|
||||
};
|
||||
},
|
||||
|
||||
'zimmu3000': function(reader) {
|
||||
return {
|
||||
vzimmuLSB: reader.int32(), // [LSB]
|
||||
vzfind: reader.float32()
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Constants
|
||||
|
||||
exports.NAVDATA_HEADER = 0x55667788;
|
||||
|
||||
// from ARDrone_SDK_2_0/ARDroneLib/Soft/Common/config.h
|
||||
exports.DRONE_STATES = {
|
||||
flying : 1 << 0, /*!< FLY MASK : (0) ardrone is landed, (1) ardrone is flying */
|
||||
videoEnabled : 1 << 1, /*!< VIDEO MASK : (0) video disable, (1) video enable */
|
||||
visionEnabled : 1 << 2, /*!< VISION MASK : (0) vision disable, (1) vision enable */
|
||||
controlAlgorithm : 1 << 3, /*!< CONTROL ALGO : (0) euler angles control, (1) angular speed control */
|
||||
altitudeControlAlgorithm : 1 << 4, /*!< ALTITUDE CONTROL ALGO : (0) altitude control inactive (1) altitude control active */
|
||||
startButtonState : 1 << 5, /*!< USER feedback : Start button state */
|
||||
controlCommandAck : 1 << 6, /*!< Control command ACK : (0) None, (1) one received */
|
||||
cameraReady : 1 << 7, /*!< CAMERA MASK : (0) camera not ready, (1) Camera ready */
|
||||
travellingEnabled : 1 << 8, /*!< Travelling mask : (0) disable, (1) enable */
|
||||
usbReady : 1 << 9, /*!< USB key : (0) usb key not ready, (1) usb key ready */
|
||||
navdataDemo : 1 << 10, /*!< Navdata demo : (0) All navdata, (1) only navdata demo */
|
||||
navdataBootstrap : 1 << 11, /*!< Navdata bootstrap : (0) options sent in all or demo mode, (1) no navdata options sent */
|
||||
motorProblem : 1 << 12, /*!< Motors status : (0) Ok, (1) Motors problem */
|
||||
communicationLost : 1 << 13, /*!< Communication Lost : (1) com problem, (0) Com is ok */
|
||||
softwareFault : 1 << 14, /*!< Software fault detected - user should land as quick as possible (1) */
|
||||
lowBattery : 1 << 15, /*!< VBat low : (1) too low, (0) Ok */
|
||||
userEmergencyLanding : 1 << 16, /*!< User Emergency Landing : (1) User EL is ON, (0) User EL is OFF*/
|
||||
timerElapsed : 1 << 17, /*!< Timer elapsed : (1) elapsed, (0) not elapsed */
|
||||
MagnometerNeedsCalibration : 1 << 18, /*!< Magnetometer calibration state : (0) Ok, no calibration needed, (1) not ok, calibration needed */
|
||||
anglesOutOfRange : 1 << 19, /*!< Angles : (0) Ok, (1) out of range */
|
||||
tooMuchWind : 1 << 20, /*!< WIND MASK: (0) ok, (1) Too much wind */
|
||||
ultrasonicSensorDeaf : 1 << 21, /*!< Ultrasonic sensor : (0) Ok, (1) deaf */
|
||||
cutoutDetected : 1 << 22, /*!< Cutout system detection : (0) Not detected, (1) detected */
|
||||
picVersionNumberOk : 1 << 23, /*!< PIC Version number OK : (0) a bad version number, (1) version number is OK */
|
||||
atCodecThreadOn : 1 << 24, /*!< ATCodec thread ON : (0) thread OFF (1) thread ON */
|
||||
navdataThreadOn : 1 << 25, /*!< Navdata thread ON : (0) thread OFF (1) thread ON */
|
||||
videoThreadOn : 1 << 26, /*!< Video thread ON : (0) thread OFF (1) thread ON */
|
||||
acquisitionThreadOn : 1 << 27, /*!< Acquisition thread ON : (0) thread OFF (1) thread ON */
|
||||
controlWatchdogDelay : 1 << 28, /*!< CTRL watchdog : (1) delay in control execution (> 5ms), (0) control is well scheduled */
|
||||
adcWatchdogDelay : 1 << 29, /*!< ADC Watchdog : (1) delay in uart2 dsr (> 5ms), (0) uart2 is good */
|
||||
comWatchdogProblem : 1 << 30, /*!< Communication Watchdog : (1) com problem, (0) Com is ok */
|
||||
emergencyLanding : 1 << 31 /*!< Emergency landing : (0) no emergency, (1) emergency */
|
||||
};
|
||||
|
||||
exports.OPTION_IDS = {
|
||||
0 : 'demo',
|
||||
1 : 'time',
|
||||
2 : 'rawMeasures',
|
||||
3 : 'physMeasures',
|
||||
4 : 'gyrosOffsets',
|
||||
5 : 'eulerAngles',
|
||||
6 : 'references',
|
||||
7 : 'trims',
|
||||
8 : 'rcReferences',
|
||||
9 : 'pwm',
|
||||
10 : 'altitude',
|
||||
11 : 'visionRaw',
|
||||
12 : 'visionOf',
|
||||
13 : 'vision',
|
||||
14 : 'visionPerf',
|
||||
15 : 'trackersSend',
|
||||
16 : 'visionDetect',
|
||||
17 : 'watchdog',
|
||||
18 : 'adcDataFrame',
|
||||
19 : 'videoStream',
|
||||
20 : 'games',
|
||||
21 : 'pressureRaw',
|
||||
22 : 'magneto',
|
||||
23 : 'windSpeed',
|
||||
24 : 'kalmanPressure',
|
||||
25 : 'hdvideoStream',
|
||||
26 : 'wifi',
|
||||
27 : 'zimmu3000',
|
||||
65535 : 'checksum'
|
||||
};
|
||||
|
||||
exports.CONTROL_STATES = {
|
||||
0: 'CTRL_DEFAULT',
|
||||
1: 'CTRL_INIT',
|
||||
2: 'CTRL_LANDED',
|
||||
3: 'CTRL_FLYING',
|
||||
4: 'CTRL_HOVERING',
|
||||
5: 'CTRL_TEST',
|
||||
6: 'CTRL_TRANS_TAKEOFF',
|
||||
7: 'CTRL_TRANS_GOTOFIX',
|
||||
8: 'CTRL_TRANS_LANDING',
|
||||
9: 'CTRL_TRANS_LOOPING'
|
||||
};
|
||||
|
||||
exports.FLY_STATES = {
|
||||
0: 'FLYING_OK',
|
||||
1: 'FLYING_LOST_ALT',
|
||||
2: 'FLYING_LOST_ALT_GO_DOWN',
|
||||
3: 'FLYING_ALT_OUT_ZONE',
|
||||
4: 'FLYING_COMBINED_YAW',
|
||||
5: 'FLYING_BRAKE',
|
||||
6: 'FLYING_NO_VISION'
|
||||
};
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
// The AR Drone 2.0 allows a tcp client to receive H264 (MPEG4.10 AVC) video
|
||||
// from the drone. However, the frames are wrapped by Parrot Video
|
||||
// Encapsulation (PaVE), which this class parses.
|
||||
|
||||
var util = require('util');
|
||||
var Stream = require('stream').Stream;
|
||||
var buffy = require('buffy');
|
||||
|
||||
module.exports = PaVEParser;
|
||||
util.inherits(PaVEParser, Stream);
|
||||
function PaVEParser() {
|
||||
Stream.call(this);
|
||||
|
||||
this.writable = true;
|
||||
this.readable = true;
|
||||
|
||||
this._parser = buffy.createReader();
|
||||
this._state = 'header';
|
||||
this._frame = undefined;
|
||||
}
|
||||
|
||||
PaVEParser.HEADER_SIZE_SHORT = 64;
|
||||
PaVEParser.HEADER_SIZE_LONG = 68;
|
||||
|
||||
PaVEParser.prototype.write = function(buffer) {
|
||||
var parser = this._parser;
|
||||
|
||||
parser.write(buffer);
|
||||
|
||||
while (true) {
|
||||
switch (this._state) {
|
||||
case 'header':
|
||||
if (parser.bytesAhead() < PaVEParser.HEADER_SIZE_LONG) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._frame = {
|
||||
signature : parser.ascii(4),
|
||||
version : parser.uint8(),
|
||||
video_codec : parser.uint8(),
|
||||
header_size : parser.uint16LE(),
|
||||
payload_size : parser.uint32LE(),
|
||||
encoded_stream_width : parser.uint16LE(),
|
||||
encoded_stream_height : parser.uint16LE(),
|
||||
display_width : parser.uint16LE(),
|
||||
display_height : parser.uint16LE(),
|
||||
frame_number : parser.uint32LE(),
|
||||
timestamp : parser.uint32LE(),
|
||||
total_chunks : parser.uint8(),
|
||||
chunk_index : parser.uint8(),
|
||||
frame_type : parser.uint8(),
|
||||
control : parser.uint8(),
|
||||
stream_byte_position_lw : parser.uint32LE(),
|
||||
stream_byte_position_uw : parser.uint32LE(),
|
||||
stream_id : parser.uint16LE(),
|
||||
total_slices : parser.uint8(),
|
||||
slice_index : parser.uint8(),
|
||||
header1_size : parser.uint8(),
|
||||
header2_size : parser.uint8(),
|
||||
reserved2 : parser.buffer(2),
|
||||
advertised_size : parser.uint32LE(),
|
||||
reserved3 : parser.buffer(12),
|
||||
payload : null,
|
||||
};
|
||||
|
||||
if (this._frame.signature !== 'PaVE') {
|
||||
this.emit('error', new Error('Invalid signature: ' + JSON.stringify(this._frame.signature)));
|
||||
// TODO: skip forward until next frame
|
||||
return;
|
||||
}
|
||||
|
||||
// stupid kludge for https://projects.ardrone.org/issues/show/159
|
||||
parser.buffer(this._frame.header_size - PaVEParser.HEADER_SIZE_SHORT);
|
||||
|
||||
this._state = 'payload';
|
||||
break;
|
||||
case 'payload':
|
||||
if (parser.bytesAhead() < this._frame.payload_size) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._frame.payload = parser.buffer(this._frame.payload_size);
|
||||
|
||||
this.emit('data', this._frame);
|
||||
this._frame = undefined;
|
||||
this._state = 'header';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
PaVEParser.prototype.end = function() {
|
||||
// nothing to do, just here so pipe() does not complain
|
||||
};
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
// Converts a video stream into a stream of png buffers. Each 'data' event
|
||||
// is guaranteed to contain exactly 1 png image.
|
||||
|
||||
// @TODO handle ffmpeg exit (and trigger "error" if unexpected)
|
||||
|
||||
var Stream = require('stream').Stream;
|
||||
var util = require('util');
|
||||
var spawn = require('child_process').spawn;
|
||||
var PngSplitter = require('./PngSplitter');
|
||||
|
||||
module.exports = PngEncoder;
|
||||
util.inherits(PngEncoder, Stream);
|
||||
function PngEncoder(options) {
|
||||
Stream.call(this);
|
||||
|
||||
options = options || {};
|
||||
|
||||
this.writable = true;
|
||||
this.readable = true;
|
||||
|
||||
this._spawn = options.spawn || spawn;
|
||||
this._frameRate = options.frameRate || 5;
|
||||
this._pngSplitter = options.pngSplitter || new PngSplitter();
|
||||
this._log = options.log;
|
||||
this._ffmpeg = undefined;
|
||||
this._ending = false;
|
||||
}
|
||||
|
||||
PngEncoder.prototype.write = function(buffer) {
|
||||
if (!this._ffmpeg) {
|
||||
this._initFfmpegAndPipes();
|
||||
}
|
||||
|
||||
return this._ffmpeg.stdin.write(buffer);
|
||||
};
|
||||
|
||||
PngEncoder.prototype._initFfmpegAndPipes = function() {
|
||||
this._ffmpeg = this._spawnFfmpeg();
|
||||
|
||||
// @TODO: Make this more sophisticated for somehow and figure out how it
|
||||
// will work with the planned new data recording system.
|
||||
if (this._log) {
|
||||
this._ffmpeg.stderr.pipe(this._log);
|
||||
}
|
||||
|
||||
this._ffmpeg.stdout.pipe(this._pngSplitter);
|
||||
this._pngSplitter.on('data', this.emit.bind(this, 'data'));
|
||||
|
||||
// 'error' can be EPIPE if ffmpeg does not exist. We handle this with the
|
||||
// 'exit' event below.
|
||||
this._ffmpeg.stdin.on('error', function() {});
|
||||
|
||||
this._ffmpeg.stdin.on('drain', this.emit.bind(this, 'drain'));
|
||||
|
||||
var self = this;
|
||||
// Since node 0.10, spawn emits 'error' when the child can't be spawned.
|
||||
// http://nodejs.org/api/child_process.html#child_process_event_error
|
||||
this._ffmpeg.on('error', function(err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
self.emit('error', new Error('Ffmpeg was not found. Have you installed the ffmpeg package?'));
|
||||
} else {
|
||||
self.emit('error', new Error('Unexpected error when launching ffmpeg:' + err.toString()));
|
||||
}
|
||||
});
|
||||
|
||||
this._ffmpeg.on('exit', function(code) {
|
||||
if (code === 0) {
|
||||
// we expected ffmpeg to exit
|
||||
if (self._ending) {
|
||||
self.emit('end');
|
||||
return;
|
||||
}
|
||||
|
||||
self.emit('error', new Error('Unexpected ffmpeg exit with code 0.'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 127 is used by the OS to indicate that ffmpeg was not found
|
||||
// required when using node < 0.10
|
||||
if (code === 127) {
|
||||
self.emit('error', new Error('Ffmpeg was not found / exit code 127.'));
|
||||
return;
|
||||
}
|
||||
|
||||
self.emit('error', new Error('Ffmpeg exited with error code: ' + code));
|
||||
});
|
||||
};
|
||||
|
||||
PngEncoder.prototype._spawnFfmpeg = function() {
|
||||
// @TODO allow people to configure the ffmpeg path
|
||||
return this._spawn('ffmpeg', [
|
||||
'-i' ,'-',
|
||||
'-f', 'image2pipe',
|
||||
'-vcodec', 'png',
|
||||
'-r', this._frameRate,
|
||||
'-',
|
||||
]);
|
||||
};
|
||||
|
||||
PngEncoder.prototype.end = function() {
|
||||
// No data handled yet? Nothing to do for ending.
|
||||
if (!this._ffmpeg) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._ending = true;
|
||||
this._ffmpeg.stdin.end();
|
||||
};
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
|
||||
module.exports = PngSplitter;
|
||||
util.inherits(PngSplitter, EventEmitter);
|
||||
function PngSplitter() {
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.writable = true;
|
||||
this._buffer = new Buffer(0);
|
||||
this._offset = 0;
|
||||
this._pngStartOffset = null;
|
||||
this._state = 'header';
|
||||
this._chunk = null;
|
||||
}
|
||||
|
||||
PngSplitter.prototype.write = function(buffer) {
|
||||
this._buffer = Buffer.concat([this._buffer, buffer]);
|
||||
|
||||
while (true) {
|
||||
switch (this._state) {
|
||||
case 'header':
|
||||
this._pngStartOffset = this._offset;
|
||||
|
||||
if (this.remainingBytes() < 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._offset += 8;
|
||||
this._state = 'chunk.header';
|
||||
break;
|
||||
case 'chunk.header':
|
||||
if (this.remainingBytes() < 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._chunk = {
|
||||
length : this.unsignedNumber(4),
|
||||
type : this.array(4).join(' '),
|
||||
};
|
||||
|
||||
this._state = 'chunk.data';
|
||||
break;
|
||||
case 'chunk.data':
|
||||
var chunkSize = this._chunk.length + 4; // 4 bytes = CRC
|
||||
if (this.remainingBytes() < chunkSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._offset += chunkSize;
|
||||
|
||||
// IEND chunk
|
||||
if (this._chunk.type == '73 69 78 68') {
|
||||
var png = this._buffer.slice(this._pngStartOffset, this._offset);
|
||||
this.emit('data', png);
|
||||
|
||||
this._buffer = this._buffer.slice(this._offset);
|
||||
this._offset = 0;
|
||||
this._state = 'header';
|
||||
break;
|
||||
}
|
||||
|
||||
this._state = 'chunk.header'
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
PngSplitter.prototype.remainingBytes = function() {
|
||||
return this._buffer.length - this._offset;
|
||||
};
|
||||
|
||||
PngSplitter.prototype.array = function(bytes) {
|
||||
var array = [];
|
||||
|
||||
for (var i = 0; i < bytes; i++) {
|
||||
array.push(this._buffer[this._offset++]);
|
||||
}
|
||||
|
||||
return array;
|
||||
};
|
||||
|
||||
PngSplitter.prototype.unsignedNumber = function(bytes) {
|
||||
var bytesRead = 0;
|
||||
var value = 0;
|
||||
|
||||
while (bytesRead < bytes) {
|
||||
var byte = this._buffer[this._offset++];
|
||||
|
||||
value += byte * Math.pow(256, bytes - bytesRead - 1);
|
||||
|
||||
bytesRead++;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
PngSplitter.prototype.end = function() {
|
||||
};
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
PngStream.TcpVideoStream = require('./TcpVideoStream');
|
||||
PngStream.PngEncoder = require('./PngEncoder');
|
||||
|
||||
var Stream = require('stream').Stream;
|
||||
var util = require('util');
|
||||
|
||||
module.exports = PngStream;
|
||||
util.inherits(PngStream, Stream);
|
||||
function PngStream(options) {
|
||||
console.warn('The PngStream class is deprecated. Use client.getPngStream() instead.');
|
||||
Stream.call(this);
|
||||
|
||||
options = options || {};
|
||||
|
||||
this.readable = true;
|
||||
this._options = options;
|
||||
this._tcpVideoStream = null;
|
||||
this._pngEncoder = null;
|
||||
}
|
||||
|
||||
PngStream.prototype.resume = function() {
|
||||
this._resume();
|
||||
};
|
||||
|
||||
PngStream.prototype._resume = function() {
|
||||
var self = this;
|
||||
|
||||
this._tcpVideoStream = new PngStream.TcpVideoStream(this._options);
|
||||
this._pngEncoder = new PngStream.PngEncoder(this._options);
|
||||
|
||||
this._tcpVideoStream.on('error', function(err) {
|
||||
self._pngEncoder.end();
|
||||
self._resume();
|
||||
|
||||
if (self.listeners('error').length > 0) {
|
||||
self.emit('error', err);
|
||||
}
|
||||
});
|
||||
|
||||
this._tcpVideoStream.connect();
|
||||
this._tcpVideoStream.pipe(this._pngEncoder);
|
||||
this._pngEncoder.on('data', this.emit.bind(this, 'data'));
|
||||
this._pngEncoder.on('error', this.emit.bind(this, 'error'));
|
||||
};
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
var Stream = require('stream').Stream;
|
||||
var util = require('util');
|
||||
var net = require('net');
|
||||
var constants = require('../constants');
|
||||
|
||||
module.exports = TcpVideoStream;
|
||||
util.inherits(TcpVideoStream, Stream);
|
||||
function TcpVideoStream(options) {
|
||||
Stream.call(this);
|
||||
|
||||
options = options || {};
|
||||
|
||||
this.readable = true;
|
||||
this._socket = options.socket || new net.Socket;
|
||||
this._port = options.port || constants.ports.VIDEO;
|
||||
this._ip = options.ip || constants.DEFAULT_DRONE_IP;
|
||||
this._timeout = options.timeout || 1 * 1000;
|
||||
this._expectFIN = false;
|
||||
}
|
||||
|
||||
TcpVideoStream.prototype.connect = function(cb) {
|
||||
cb = cb || function() {};
|
||||
|
||||
this._socket.removeAllListeners(); // To avoid duplicates when re-connecting
|
||||
this._socket.connect(this._port, this._ip);
|
||||
this._socket.setTimeout(this._timeout);
|
||||
|
||||
var self = this;
|
||||
this._socket
|
||||
.on('connect', function() {
|
||||
cb(null);
|
||||
})
|
||||
.on('data', function(buffer) {
|
||||
self.emit('data', buffer);
|
||||
})
|
||||
.on('timeout', function() {
|
||||
var err = new Error('TcpVideoStream timeout after ' + self._timeout + ' ms.');
|
||||
self.emit('error', err);
|
||||
self.emit('close', err);
|
||||
self._socket.destroy();
|
||||
})
|
||||
.on('error', function(err) {
|
||||
cb(err);
|
||||
})
|
||||
.on('end', function() {
|
||||
if (self._expectFIN) {
|
||||
self.emit('close');
|
||||
return;
|
||||
}
|
||||
|
||||
var err = new Error('TcpVideoStream received FIN unexpectedly.');
|
||||
self.emit('error', err);
|
||||
self.emit('close', err);
|
||||
});
|
||||
};
|
||||
|
||||
TcpVideoStream.prototype.end = function() {
|
||||
this._expectFIN = true;
|
||||
this._socket.end();
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
*.un~
|
||||
/node_modules
|
||||
/benchmarks/fixtures/*.bin
|
||||
/benchmarks/node_modules
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Contributing
|
||||
|
||||
## Running the test suite
|
||||
|
||||
```js
|
||||
$ git clone <git url>
|
||||
$ cd <clone dir>
|
||||
$ npm install
|
||||
$ make test
|
||||
```
|
||||
|
||||
## Running an individual test
|
||||
|
||||
```js
|
||||
$ node test/unit/test-Reader.js
|
||||
```
|
||||
|
||||
## TODOS
|
||||
|
||||
If you are looking for something to work on, here are a few things I'd like to
|
||||
see in this module:
|
||||
|
||||
* Implement benchmark suite (with some nice R analysis)
|
||||
* Implement `reader.compact()` and/or `new Reader({compact: true})`. Allows for
|
||||
the internal `this._buffer` to shrink. (The current behavior is to grow the
|
||||
internal buffer until it can hold the biggest buffer passed into `write()`).
|
||||
* Implement Writer class. Will probably create many internal buffers and flatten
|
||||
them into one at the end. Should also support to be initialized with a length
|
||||
in which case small buffers are not created all the time.
|
||||
|
||||
And of course, feel free to send patches for any open tickets / bugs.
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
SHELL := /bin/bash
|
||||
|
||||
test:
|
||||
node test/run.js
|
||||
|
||||
.PHONY: test
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
# buffy (The Buffer Slayer)
|
||||
|
||||
A module to read / write binary data and streams.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install buffy
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Let's say you want to parse a simple C struct, buffy can help:
|
||||
|
||||
```js
|
||||
var buffy = require('buffy');
|
||||
|
||||
var buffer = new Buffer([23, 0, 0, 0, 15, 116, 101, 115, 116]);
|
||||
var reader = buffy.createReader(buffer);
|
||||
|
||||
var record = {
|
||||
version : reader.uint8(),
|
||||
id : reader.uint32(),
|
||||
name : reader.ascii(4),
|
||||
};
|
||||
|
||||
// {version: 23, id: 15, name: 'test'}
|
||||
```
|
||||
|
||||
Parsing a buffer is nice, but what about streams? Well, buffy has your back:
|
||||
|
||||
```js
|
||||
var buffy = require('buffy');
|
||||
var net = require('net');
|
||||
var connection = net.createConnection(1337, 'example.org');
|
||||
|
||||
var reader = buffy.createReader();
|
||||
connection.pipe(reader);
|
||||
|
||||
reader.on('data', function() {
|
||||
while (reader.bytesAhead() >= 9) {
|
||||
var record = {
|
||||
version : reader.uint8(),
|
||||
id : reader.uint32(),
|
||||
name : reader.ascii(4),
|
||||
};
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Future version may also support a declarative syntax for defining structs and
|
||||
their sequences.
|
||||
|
||||
## Reader API
|
||||
|
||||
### reader.write(buffer)
|
||||
|
||||
Appends the given `buffer` to the internal buffer. Whenever possible, existing
|
||||
space inside the internal buffer will be reused, otherwise a new / bigger buffer
|
||||
will be created.
|
||||
|
||||
### reader.bytesAhead()
|
||||
|
||||
Returns the number of unread bytes available to the reader.
|
||||
|
||||
### reader.bytesBuffered()
|
||||
|
||||
Returns the number of bytes that are buffered by the Reader internally.
|
||||
|
||||
### reader.int8() / reader.uint8()
|
||||
|
||||
Returns the next (un)signed 8 bit integer.
|
||||
|
||||
### reader.int16BE() / reader.uint16BE() / reader.int16LE() / reader.uint16LE()
|
||||
|
||||
Returns the next (un)signed 16 bit integer in the chosen endianness.
|
||||
|
||||
### reader.int32BE() / reader.uint32BE() / reader.int32LE() / reader.uint32LE()
|
||||
|
||||
Returns the next (un)signed 32 bit integer in the chosen endianness.
|
||||
|
||||
### reader.float32BE() / reader.float32LE()
|
||||
|
||||
Returns the next 32 bit float in the chosen endianness.
|
||||
|
||||
### reader.double64BE() / reader.double64LE()
|
||||
|
||||
Returns the next 64 bit double in the chosen endianness.
|
||||
|
||||
### reader.ascii([bytes]) / reader.utf8([bytes])
|
||||
|
||||
Returns the next `bytes` as a string of the chosen encoding. If `bytes` is
|
||||
omitted, a null terminated string is assumed.
|
||||
|
||||
### reader.buffer([bytes])
|
||||
|
||||
Returns the next `bytes` as a buffer.
|
||||
|
||||
### reader.skip(bytes)
|
||||
|
||||
Skips `bytes` bytes of the buffer.
|
||||
|
||||
|
||||
## Writer API
|
||||
|
||||
The Writer has not been implemented yet.
|
||||
|
||||
## Error Handling
|
||||
|
||||
The reader will throw an exception whenever an operation exceeds the boundary
|
||||
of the internal buffer.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
results/ascii-strings-binary.csv: binary.js fixtures/ascii-strings.bin
|
||||
node $^ | tee $@
|
||||
|
||||
results/ascii-strings-buffy.csv: buffy.js fixtures/ascii-strings.bin
|
||||
node $^ | tee $@
|
||||
|
||||
fixtures/ascii-strings.bin: fixtures/generate-strings.js
|
||||
node $^ $@
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# Benchmarks
|
||||
|
||||
Work in progress ...
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
var benchmark = require('./lib/benchmark')('binary', process.argv[2]);
|
||||
|
||||
benchmark('ascii-strings', function(binary, stream, cb) {
|
||||
var checksum = 0;
|
||||
var reachedEnd = false;
|
||||
|
||||
var ws = binary()
|
||||
.loop(function (end) {
|
||||
this
|
||||
.word8('stringLength')
|
||||
.buffer('string', 'stringLength')
|
||||
.tap(function(vars) {
|
||||
checksum += vars.string.length;
|
||||
|
||||
if (checksum === 650303) {
|
||||
cb(null, checksum);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
stream
|
||||
.on('data', function(buffer) {
|
||||
ws.write(buffer);
|
||||
})
|
||||
.on('end', function() {
|
||||
ws.end();
|
||||
});
|
||||
});
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
var benchmark = require('./lib/benchmark')('buffy', process.argv[2]);
|
||||
|
||||
benchmark('ascii-strings', function(buffy, stream, cb) {
|
||||
var reader = buffy.createReader();
|
||||
|
||||
var checksum = 0;
|
||||
var stringLength = undefined;
|
||||
|
||||
stream
|
||||
.on('data', function(buffer) {
|
||||
reader.write(buffer);
|
||||
|
||||
while (true) {
|
||||
if (stringLength === undefined) {
|
||||
if (reader.bytesAhead() < 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
stringLength = reader.uint8();
|
||||
}
|
||||
|
||||
if (reader.bytesAhead() < stringLength) {
|
||||
break;
|
||||
}
|
||||
|
||||
var string = reader.ascii(stringLength);
|
||||
|
||||
checksum += stringLength;
|
||||
stringLength = undefined;
|
||||
}
|
||||
})
|
||||
.on('end', function() {
|
||||
cb(null, checksum);
|
||||
});
|
||||
});
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
var fs = require('fs');
|
||||
var loremIpsum = require('lorem-ipsum')
|
||||
|
||||
var gigabit = (1000 * 1000 * 1000) / 8;
|
||||
var bufferSize = 64 * 1024;
|
||||
|
||||
var filename = process.argv[2];
|
||||
fs.writeFileSync(filename, new Buffer(0));
|
||||
|
||||
var fileOffset = 0;
|
||||
while (fileOffset < gigabit) {
|
||||
var bufferOffset = 0;
|
||||
var buffer = (gigabit - fileOffset < bufferSize)
|
||||
? new Buffer(gigabit - fileOffset)
|
||||
: new Buffer(bufferSize);
|
||||
|
||||
while (bufferOffset < buffer.length) {
|
||||
var stringLength = Math.floor(Math.random() * 256);
|
||||
if (stringLength + 1 > buffer.length - bufferOffset) {
|
||||
stringLength = buffer.length - bufferOffset - 1;
|
||||
}
|
||||
|
||||
var string = loremIpsum({
|
||||
count: 50,
|
||||
units: 'words',
|
||||
paragraphPrefix: '',
|
||||
paragraphSuffix: '',
|
||||
}).substr(0, stringLength);
|
||||
|
||||
buffer[bufferOffset++] = stringLength;
|
||||
buffer.write(string, bufferOffset);
|
||||
bufferOffset += stringLength;
|
||||
}
|
||||
|
||||
fs.appendFileSync(filename, buffer);
|
||||
fileOffset += buffer.length;
|
||||
|
||||
process.stdout.write('\rCreating ' + filename + ' (' + (fileOffset * 100 / gigabit).toFixed(2) + ' %)');
|
||||
}
|
||||
|
||||
process.stdout.write('\n');
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
var fs = require('fs');
|
||||
var Stream = require('stream').Stream;
|
||||
var util = require('util');
|
||||
|
||||
module.exports = FixtureStream;
|
||||
util.inherits(FixtureStream, Stream);
|
||||
function FixtureStream(path, bufferSize) {
|
||||
bufferSize = bufferSize || 64 * 1024;
|
||||
|
||||
this.readable = true;
|
||||
this._buffers = [];
|
||||
|
||||
var buffer = fs.readFileSync(path).slice(0, bufferSize * 10);
|
||||
var start = 0;
|
||||
|
||||
while (start < buffer.length) {
|
||||
var bytesLeft = buffer.length - start ;
|
||||
var size = (bytesLeft >= bufferSize)
|
||||
? bufferSize
|
||||
: bytesLeft;
|
||||
|
||||
this._buffers.push(buffer.slice(start, start + size));
|
||||
start += size;
|
||||
}
|
||||
|
||||
this.length = buffer.length;
|
||||
}
|
||||
|
||||
FixtureStream.prototype.resume = function() {
|
||||
for (var i = 0; i < this._buffers.length; i++) {
|
||||
this.emit('data', this._buffers[i]);
|
||||
}
|
||||
|
||||
this.emit('end');
|
||||
};
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
var path = require('path');
|
||||
var FixtureStream = require('./FixtureStream');
|
||||
|
||||
module.exports = function(moduleName, fixturePath) {
|
||||
var lib = require(moduleName);
|
||||
var packageJSON = require(moduleName + '/package.json');
|
||||
|
||||
var fixtureName = path.basename(fixturePath, '.bin');
|
||||
|
||||
return function benchmark(name, benchmarkFn) {
|
||||
if (name !== fixtureName) {
|
||||
return;
|
||||
}
|
||||
|
||||
run(benchmarkFn, fixturePath, lib, packageJSON);
|
||||
};
|
||||
};
|
||||
|
||||
function run(benchmarkFn, fixturePath, lib, packageJSON) {
|
||||
var stream = new FixtureStream(fixturePath);
|
||||
var iterations = 100;
|
||||
|
||||
console.log(['time', 'bytesPerSec', 'memoryUsage', 'lib', 'version'].join('\t'));
|
||||
|
||||
function nextIteration() {
|
||||
var start = Date.now();
|
||||
|
||||
benchmarkFn(lib, stream, function(err, checksum) {
|
||||
if (err) throw err;
|
||||
|
||||
var duration = Date.now() - start;
|
||||
|
||||
if (checksum !== 124067628) {
|
||||
//throw new Error('bad checksum: ' + checksum);
|
||||
}
|
||||
|
||||
stream.removeAllListeners();
|
||||
|
||||
var bytesPerSec = Math.round(stream.length / (duration / 1000));
|
||||
|
||||
console.log([
|
||||
Date.now(),
|
||||
bytesPerSec,
|
||||
process.memoryUsage().rss,
|
||||
packageJSON.name,
|
||||
packageJSON.version
|
||||
].join('\t'));
|
||||
|
||||
iterations--;
|
||||
if (iterations) {
|
||||
process.nextTick(nextIteration);
|
||||
}
|
||||
});
|
||||
|
||||
stream.resume();
|
||||
}
|
||||
|
||||
nextIteration();
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"author": "Felix Geisendörfer <felix@debuggable.com> (http://debuggable.com/)",
|
||||
"name": "buffy-benchmarks",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"binary": "~0.3.0",
|
||||
"lorem-ipsum": "0.0.7"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"optionalDependencies": {},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
var buffy = exports;
|
||||
|
||||
buffy.Reader = require('./lib/Reader');
|
||||
buffy.createReader = function(options) {
|
||||
return new buffy.Reader(options);
|
||||
};
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
var util = require('util');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
module.exports = Reader;
|
||||
util.inherits(Reader, EventEmitter);
|
||||
function Reader(options) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
if (Buffer.isBuffer(options)) {
|
||||
options = {buffer: options};
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
|
||||
this.writable = true;
|
||||
|
||||
this._buffer = options.buffer || new Buffer(0);
|
||||
this._offset = options.offset || 0;
|
||||
this._paused = false;
|
||||
}
|
||||
|
||||
Reader.prototype.write = function(newBuffer) {
|
||||
var bytesAhead = this.bytesAhead();
|
||||
|
||||
// existing buffer has enough space in the beginning?
|
||||
var reuseBuffer = (this._offset >= newBuffer.length);
|
||||
|
||||
if (reuseBuffer) {
|
||||
// move unread bytes forward to make room for the new
|
||||
this._buffer.copy(this._buffer, this._offset - newBuffer.length, this._offset);
|
||||
this._offset -= newBuffer.length;
|
||||
|
||||
// add the new bytes at the end
|
||||
newBuffer.copy(this._buffer, this._buffer.length - newBuffer.length);
|
||||
} else {
|
||||
var oldBuffer = this._buffer;
|
||||
|
||||
// grow a new buffer that can hold both
|
||||
this._buffer = new Buffer(bytesAhead + newBuffer.length);
|
||||
|
||||
// copy the old and new buffer into it
|
||||
oldBuffer.copy(this._buffer, 0, this._offset);
|
||||
newBuffer.copy(this._buffer, bytesAhead);
|
||||
this._offset = 0;
|
||||
}
|
||||
|
||||
return !this._paused;
|
||||
};
|
||||
|
||||
Reader.prototype.pause = function() {
|
||||
this._paused = true;
|
||||
};
|
||||
|
||||
Reader.prototype.resume = function() {
|
||||
this._paused = false;
|
||||
};
|
||||
|
||||
Reader.prototype.bytesAhead = function() {
|
||||
return this._buffer.length - this._offset;
|
||||
};
|
||||
|
||||
Reader.prototype.bytesBuffered = function() {
|
||||
return this._buffer.length;
|
||||
};
|
||||
|
||||
Reader.prototype.uint8 = function() {
|
||||
return this._buffer.readUInt8(this._offset++);
|
||||
};
|
||||
|
||||
Reader.prototype.int8 = function() {
|
||||
return this._buffer.readInt8(this._offset++);
|
||||
};
|
||||
|
||||
Reader.prototype.uint16BE = function() {
|
||||
this._offset += 2;
|
||||
return this._buffer.readUInt16BE(this._offset - 2);
|
||||
};
|
||||
|
||||
Reader.prototype.int16BE = function() {
|
||||
this._offset += 2;
|
||||
return this._buffer.readInt16BE(this._offset - 2);
|
||||
};
|
||||
|
||||
Reader.prototype.uint16LE = function() {
|
||||
this._offset += 2;
|
||||
return this._buffer.readUInt16LE(this._offset - 2);
|
||||
};
|
||||
|
||||
Reader.prototype.int16LE = function() {
|
||||
this._offset += 2;
|
||||
return this._buffer.readInt16LE(this._offset - 2);
|
||||
};
|
||||
|
||||
Reader.prototype.uint32BE = function() {
|
||||
this._offset += 4;
|
||||
return this._buffer.readUInt32BE(this._offset - 4);
|
||||
};
|
||||
|
||||
Reader.prototype.int32BE = function() {
|
||||
this._offset += 4;
|
||||
return this._buffer.readInt32BE(this._offset - 4);
|
||||
};
|
||||
|
||||
Reader.prototype.uint32LE = function() {
|
||||
this._offset += 4;
|
||||
return this._buffer.readUInt32LE(this._offset - 4);
|
||||
};
|
||||
|
||||
Reader.prototype.int32LE = function() {
|
||||
this._offset += 4;
|
||||
return this._buffer.readInt32LE(this._offset - 4);
|
||||
};
|
||||
|
||||
Reader.prototype.float32BE = function() {
|
||||
this._offset += 4;
|
||||
return this._buffer.readFloatBE(this._offset - 4);
|
||||
};
|
||||
|
||||
Reader.prototype.float32LE = function() {
|
||||
this._offset += 4;
|
||||
return this._buffer.readFloatLE(this._offset - 4);
|
||||
};
|
||||
|
||||
Reader.prototype.double64BE = function() {
|
||||
this._offset += 8;
|
||||
return this._buffer.readDoubleBE(this._offset - 8);
|
||||
};
|
||||
|
||||
Reader.prototype.double64LE = function() {
|
||||
this._offset += 8;
|
||||
return this._buffer.readDoubleLE(this._offset - 8);
|
||||
};
|
||||
|
||||
Reader.prototype.ascii = function(bytes) {
|
||||
return this._string('ascii', bytes);
|
||||
};
|
||||
|
||||
Reader.prototype.utf8 = function(bytes) {
|
||||
return this._string('utf8', bytes);
|
||||
};
|
||||
|
||||
Reader.prototype._string = function(encoding, bytes) {
|
||||
var nullTerminated = (bytes === undefined);
|
||||
if (nullTerminated) {
|
||||
bytes = this._nullDistance();
|
||||
}
|
||||
|
||||
var offset = this._offset;
|
||||
this._offset += bytes;
|
||||
|
||||
var value = this._buffer.toString(encoding, offset, this._offset);
|
||||
|
||||
if (nullTerminated) {
|
||||
this._offset++;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
Reader.prototype._nullDistance = function() {
|
||||
for (var i = this._offset; i < this._buffer.length; i++) {
|
||||
var byte = this._buffer[i];
|
||||
if (byte === 0) {
|
||||
return i - this._offset;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reader.prototype.buffer = function(bytes) {
|
||||
this._offset += bytes;
|
||||
var ret = new Buffer(bytes);
|
||||
this._buffer.copy(ret, 0, this._offset - bytes, this._offset);
|
||||
return ret;
|
||||
};
|
||||
|
||||
Reader.prototype.skip = function(bytes) {
|
||||
if (bytes > this.bytesAhead() || bytes < 0) {
|
||||
this.emit('error', new Error('tried to skip outsite of the buffer'));
|
||||
}
|
||||
this._offset += bytes;
|
||||
};
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"author": {
|
||||
"name": "Felix Geisendörfer",
|
||||
"email": "felix@debuggable.com",
|
||||
"url": "http://debuggable.com/"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Bernhard K. Weisshuhn",
|
||||
"email": "bkw@codingforce.com"
|
||||
}
|
||||
],
|
||||
"name": "buffy",
|
||||
"description": "A module to read / write binary data and streams.",
|
||||
"version": "0.0.4",
|
||||
"homepage": "https://github.com/felixge/node-buffy",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/felixge/node-buffy.git"
|
||||
},
|
||||
"main": "./index",
|
||||
"scripts": {
|
||||
"test": "make test"
|
||||
},
|
||||
"dependencies": {},
|
||||
"optionalDependencies": {},
|
||||
"devDependencies": {
|
||||
"utest": "0.0.6",
|
||||
"urun": "0.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"readme": "# buffy (The Buffer Slayer)\n\nA module to read / write binary data and streams.\n\n## Install\n\n```bash\nnpm install buffy\n```\n\n## Usage\n\nLet's say you want to parse a simple C struct, buffy can help:\n\n```js\nvar buffy = require('buffy');\n\nvar buffer = new Buffer([23, 0, 0, 0, 15, 116, 101, 115, 116]);\nvar reader = buffy.createReader(buffer);\n\nvar record = {\n version : reader.uint8(),\n id : reader.uint32(),\n name : reader.ascii(4),\n};\n\n// {version: 23, id: 15, name: 'test'}\n```\n\nParsing a buffer is nice, but what about streams? Well, buffy has your back:\n\n```js\nvar buffy = require('buffy');\nvar net = require('net');\nvar connection = net.createConnection(1337, 'example.org');\n\nvar reader = buffy.createReader();\nconnection.pipe(reader);\n\nreader.on('data', function() {\n while (reader.bytesAhead() >= 9) {\n var record = {\n version : reader.uint8(),\n id : reader.uint32(),\n name : reader.ascii(4),\n };\n }\n});\n```\n\nFuture version may also support a declarative syntax for defining structs and\ntheir sequences.\n\n## Reader API\n\n### reader.write(buffer)\n\nAppends the given `buffer` to the internal buffer. Whenever possible, existing\nspace inside the internal buffer will be reused, otherwise a new / bigger buffer\nwill be created.\n\n### reader.bytesAhead()\n\nReturns the number of unread bytes available to the reader.\n\n### reader.bytesBuffered()\n\nReturns the number of bytes that are buffered by the Reader internally.\n\n### reader.int8() / reader.uint8()\n\nReturns the next (un)signed 8 bit integer.\n\n### reader.int16BE() / reader.uint16BE() / reader.int16LE() / reader.uint16LE()\n\nReturns the next (un)signed 16 bit integer in the chosen endianness.\n\n### reader.int32BE() / reader.uint32BE() / reader.int32LE() / reader.uint32LE()\n\nReturns the next (un)signed 32 bit integer in the chosen endianness.\n\n### reader.float32BE() / reader.float32LE()\n\nReturns the next 32 bit float in the chosen endianness.\n\n### reader.double64BE() / reader.double64LE()\n\nReturns the next 64 bit double in the chosen endianness.\n\n### reader.ascii([bytes]) / reader.utf8([bytes])\n\nReturns the next `bytes` as a string of the chosen encoding. If `bytes` is\nomitted, a null terminated string is assumed.\n\n### reader.buffer([bytes])\n\nReturns the next `bytes` as a buffer.\n\n### reader.skip(bytes)\n\nSkips `bytes` bytes of the buffer.\n\n\n## Writer API\n\nThe Writer has not been implemented yet.\n\n## Error Handling\n\nThe reader will throw an exception whenever an operation exceeds the boundary\nof the internal buffer.\n",
|
||||
"readmeFilename": "README.md",
|
||||
"bugs": {
|
||||
"url": "https://github.com/felixge/node-buffy/issues"
|
||||
},
|
||||
"_id": "buffy@0.0.4",
|
||||
"dist": {
|
||||
"shasum": "d208104fbd9b47a87cbb01fe3b2ef2df54300df8"
|
||||
},
|
||||
"_from": "buffy@0.0.4",
|
||||
"_resolved": "https://registry.npmjs.org/buffy/-/buffy-0.0.4.tgz"
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
var common = exports;
|
||||
var path = require('path');
|
||||
|
||||
common.root = path.join(__dirname, '..');
|
||||
common.lib = path.join(common.root, 'lib');
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
var urun = require('urun');
|
||||
urun(__dirname);
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
var common = require('../common');
|
||||
var test = require('utest');
|
||||
var assert = require('assert');
|
||||
var Reader = require(common.lib + '/Reader');
|
||||
|
||||
test('Constructor', {
|
||||
'offset option': function() {
|
||||
var buffer = new Buffer([0, 127]);
|
||||
var reader = new Reader({buffer: buffer, offset: 1});
|
||||
|
||||
assert.equal(reader.uint8(), 127);
|
||||
},
|
||||
});
|
||||
|
||||
test('WritableStream interface', {
|
||||
'is writable by default': function() {
|
||||
var reader = new Reader();
|
||||
assert.equal(reader.writable, true);
|
||||
},
|
||||
|
||||
'write: returns true when active': function() {
|
||||
var reader = new Reader();
|
||||
assert.equal(reader.write(new Buffer(0)), true);
|
||||
},
|
||||
|
||||
'write: returns false when paused': function() {
|
||||
var reader = new Reader();
|
||||
|
||||
reader.pause();
|
||||
assert.equal(reader.write(new Buffer(0)), false);
|
||||
},
|
||||
|
||||
'write: returns true when resumed': function() {
|
||||
var reader = new Reader();
|
||||
|
||||
reader.pause();
|
||||
assert.equal(reader.write(new Buffer(0)), false);
|
||||
|
||||
reader.resume();
|
||||
assert.equal(reader.write(new Buffer(0)), true);
|
||||
},
|
||||
|
||||
'write: collects buffer data': function() {
|
||||
var reader = new Reader();
|
||||
|
||||
reader.write(new Buffer([1, 2]));
|
||||
assert.equal(reader.uint8(), 1);
|
||||
|
||||
reader.write(new Buffer([3]));
|
||||
assert.equal(reader.uint8(), 2);
|
||||
assert.equal(reader.uint8(), 3);
|
||||
},
|
||||
|
||||
'write: uses existing buffer if possible': function() {
|
||||
var reader = new Reader();
|
||||
|
||||
reader.write(new Buffer([1, 2, 3]));
|
||||
assert.equal(reader.uint8(), 1);
|
||||
reader.write(new Buffer([4]));
|
||||
|
||||
assert.equal(reader.bytesBuffered(), 3);
|
||||
assert.equal(reader.uint8(), 2);
|
||||
assert.equal(reader.uint8(), 3);
|
||||
assert.equal(reader.uint8(), 4);
|
||||
},
|
||||
|
||||
'write: grows buffer if neccessary': function() {
|
||||
var reader = new Reader();
|
||||
|
||||
reader.write(new Buffer([1, 2]));
|
||||
assert.equal(reader.uint8(), 1);
|
||||
reader.write(new Buffer([3, 4, 5]));
|
||||
assert.equal(reader.uint8(), 2);
|
||||
assert.equal(reader.uint8(), 3);
|
||||
assert.equal(reader.uint8(), 4);
|
||||
assert.equal(reader.uint8(), 5);
|
||||
},
|
||||
});
|
||||
|
||||
test('Read Methods', {
|
||||
'bytesAhead': function() {
|
||||
var buffer = new Buffer([1, 127, 128, 255]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader.bytesAhead(), 4);
|
||||
reader.uint8();
|
||||
reader.uint8();
|
||||
|
||||
assert.equal(reader.bytesAhead(), 2);
|
||||
},
|
||||
|
||||
'bytesAhead: reports correctly with smaller buffer': function() {
|
||||
var reader = new Reader();
|
||||
|
||||
reader.write(new Buffer([1, 2]));
|
||||
reader.buffer(2);
|
||||
reader.write(new Buffer([3]));
|
||||
assert.equal(reader.bytesAhead(), 1);
|
||||
},
|
||||
|
||||
'uint8': function() {
|
||||
var buffer = new Buffer([1, 127, 128, 255]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader.uint8(), 1);
|
||||
assert.equal(reader.uint8(), 127);
|
||||
assert.equal(reader.uint8(), 128);
|
||||
assert.equal(reader.uint8(), 255);
|
||||
},
|
||||
|
||||
'int8': function() {
|
||||
var buffer = new Buffer([1, 127, 128, 255]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader.int8(), 1);
|
||||
assert.equal(reader.int8(), 127);
|
||||
assert.equal(reader.int8(), -128);
|
||||
assert.equal(reader.int8(), -1);
|
||||
},
|
||||
|
||||
'uint16BE': function() {
|
||||
var buffer = new Buffer([1, 127, 128, 255]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader.uint16BE(), 1 * 256 + 127);
|
||||
assert.equal(reader.uint16BE(), 128 * 256 + 255);
|
||||
},
|
||||
|
||||
'int16BE': function() {
|
||||
var buffer = new Buffer([1, 127, 128, 255]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader.int16BE(), 1 * 256 + 127);
|
||||
assert.equal(reader.int16BE(), -128 * 256 + 255);
|
||||
},
|
||||
|
||||
'uint16LE': function() {
|
||||
var buffer = new Buffer([1, 127, 128, 255]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader.uint16LE(), 1 + 127 * 256);
|
||||
assert.equal(reader.uint16LE(), 128 + 255 * 256);
|
||||
},
|
||||
|
||||
'int16LE': function() {
|
||||
var buffer = new Buffer([1, 127, 128, 255]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader.int16LE(), 1 + 127 * 256);
|
||||
assert.equal(reader.int16LE(), -128);
|
||||
},
|
||||
|
||||
'uint32BE': function() {
|
||||
var buffer = new Buffer([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader.uint32BE(), 16909060);
|
||||
assert.equal(reader.uint32BE(), 84281096);
|
||||
},
|
||||
|
||||
'int32BE': function() {
|
||||
var buffer = new Buffer([1, 2, 3, 4, 255, 254, 253, 252]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader.int32BE(), 16909060);
|
||||
assert.equal(reader.int32BE(), -66052);
|
||||
},
|
||||
|
||||
'uint32LE': function() {
|
||||
var buffer = new Buffer([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader.uint32LE(), 67305985);
|
||||
assert.equal(reader.uint32LE(), 134678021);
|
||||
},
|
||||
|
||||
'int32LE': function() {
|
||||
var buffer = new Buffer([1, 2, 3, 4, 255, 254, 253, 252]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader.int32LE(), 67305985);
|
||||
assert.equal(reader.int32LE(), -50462977);
|
||||
},
|
||||
|
||||
'float32BE': function() {
|
||||
var buffer = new Buffer([
|
||||
0x41, 0xb8, 0x00, 0x00, // 23
|
||||
0xbf, 0x80, 0x00, 0x00, // -1
|
||||
]);
|
||||
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader.float32BE(), 23);
|
||||
assert.equal(reader.float32BE(), -1);
|
||||
},
|
||||
|
||||
'float32LE': function() {
|
||||
var buffer = new Buffer([
|
||||
0x00, 0x00, 0xb8, 0x41, // 23
|
||||
0x00, 0x00, 0x80, 0xbf, // -1
|
||||
]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader.float32LE(), 23);
|
||||
assert.equal(reader.float32LE(), -1);
|
||||
},
|
||||
|
||||
'double64BE': function() {
|
||||
var buffer = new Buffer([
|
||||
0x40, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xbf, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader.double64BE(), 23);
|
||||
assert.equal(reader.double64BE(), -1);
|
||||
},
|
||||
|
||||
'double64LE': function() {
|
||||
var buffer = new Buffer([
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x40,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xbf,
|
||||
]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader.double64LE(), 23);
|
||||
assert.equal(reader.double64LE(), -1);
|
||||
},
|
||||
|
||||
'ascii: fixed length ascii': testParseFixedLengthAsciiWith('ascii'),
|
||||
'ascii: fixed length snowman': testParseFixedLengthSnowmanWith('ascii'),
|
||||
'ascii: null terminated ascii': testParseNullTerminatedAsciiWith('ascii'),
|
||||
|
||||
'utf8: fixed length ascii': testParseFixedLengthAsciiWith('utf8'),
|
||||
'utf8: fixed length snowman': testParseFixedLengthSnowmanWith('utf8'),
|
||||
'utf8: null terminated ascii': testParseNullTerminatedAsciiWith('utf8'),
|
||||
|
||||
'buffer': function() {
|
||||
var buffer = new Buffer([1, 2, 3, 4, 5]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.deepEqual(reader.buffer(3), new Buffer([1, 2, 3]));
|
||||
assert.deepEqual(reader.buffer(2), new Buffer([4, 5]));
|
||||
},
|
||||
'buffer: return a copy' : function () {
|
||||
var reader = new Reader(new Buffer([1]));
|
||||
var origResult = reader.buffer(1);
|
||||
reader.write(new Buffer([2]));
|
||||
assert.deepEqual(origResult, new Buffer([1]));
|
||||
},
|
||||
|
||||
'skip' : function() {
|
||||
var reader = new Reader(new Buffer([1, 2, 3, 4, 5]));
|
||||
reader.skip(2);
|
||||
assert.deepEqual(reader.buffer(3), new Buffer([3, 4, 5]));
|
||||
assert.throws(function() { reader.skip(1); });
|
||||
assert.throws(function() { reader.skip(-1); });
|
||||
}
|
||||
});
|
||||
|
||||
function testParseFixedLengthAsciiWith(encoding) {
|
||||
return function() {
|
||||
var buffer = new Buffer([
|
||||
'a'.charCodeAt(),
|
||||
'b'.charCodeAt(),
|
||||
'c'.charCodeAt(),
|
||||
'd'.charCodeAt(),
|
||||
'e'.charCodeAt(),
|
||||
]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader[encoding](3), 'abc');
|
||||
assert.equal(reader[encoding](2), 'de');
|
||||
};
|
||||
}
|
||||
|
||||
function testParseFixedLengthSnowmanWith(encoding) {
|
||||
return function() {
|
||||
var buffer = new Buffer('\u2603', 'utf8');
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader[encoding](3), '☃');
|
||||
};
|
||||
}
|
||||
|
||||
function testParseNullTerminatedAsciiWith(encoding) {
|
||||
return function() {
|
||||
var buffer = new Buffer([
|
||||
'a'.charCodeAt(),
|
||||
'b'.charCodeAt(),
|
||||
'c'.charCodeAt(),
|
||||
0,
|
||||
'd'.charCodeAt(),
|
||||
'e'.charCodeAt(),
|
||||
0,
|
||||
]);
|
||||
var reader = new Reader(buffer);
|
||||
|
||||
assert.equal(reader[encoding](), 'abc');
|
||||
assert.equal(reader[encoding](), 'de');
|
||||
};
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
var common = require('../common');
|
||||
var test = require('utest');
|
||||
var assert = require('assert');
|
||||
var buffy = require(common.root);
|
||||
var Reader = require(common.lib + '/Reader');
|
||||
|
||||
test('buffy', {
|
||||
'exports: Reader': function() {
|
||||
assert.strictEqual(buffy.Reader, Reader);
|
||||
},
|
||||
|
||||
'createReader: maps to Reader constructor': function() {
|
||||
var reader = buffy.createReader(new Buffer(23));
|
||||
assert.equal(reader instanceof Reader, true);
|
||||
assert.equal(reader.bytesAhead(), 23);
|
||||
},
|
||||
});
|
||||
+42
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+13
@@ -0,0 +1,13 @@
|
||||
var common = exports;
|
||||
var path = require('path');
|
||||
|
||||
common.root = path.join(__dirname, '..');
|
||||
common.lib = path.join(common.root, 'lib');
|
||||
common.fixtures = path.join(__dirname, 'fixtures');
|
||||
|
||||
common.UDP_PORT = 13571;
|
||||
common.TCP_PORT = 13572;
|
||||
|
||||
common.isTravisCi = function() {
|
||||
return Boolean(process.env.CI);
|
||||
};
|
||||
BIN
Arquivo binário não exibido.
BIN
Arquivo binário não exibido.
BIN
Arquivo binário não exibido.
+26
@@ -0,0 +1,26 @@
|
||||
var common = require('../../../common');
|
||||
var UdpControl = require(common.lib + '/control/UdpControl');
|
||||
var dgram = require('dgram');
|
||||
var assert = require('assert');
|
||||
|
||||
var receiver = dgram.createSocket('udp4');
|
||||
receiver.bind(common.UDP_PORT);
|
||||
|
||||
var control = new UdpControl({ip: '127.0.0.1', port: common.UDP_PORT});
|
||||
|
||||
var expectMessage = control.ref() + control.pcmd();
|
||||
control.flush();
|
||||
|
||||
var receivedMessage = false;
|
||||
receiver.on('message', function(buffer) {
|
||||
assert.deepEqual(buffer.toString(), expectMessage);
|
||||
|
||||
receiver.close();
|
||||
control.close();
|
||||
|
||||
receivedMessage = true;
|
||||
});
|
||||
|
||||
process.on('exit', function() {
|
||||
assert.equal(receivedMessage, true);
|
||||
});
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
var common = require('../../../common');
|
||||
var fs = require('fs');
|
||||
var assert = require('assert');
|
||||
var PngEncoder = require(common.lib + '/video/PngEncoder');
|
||||
|
||||
if (common.isTravisCi()) {
|
||||
console.log('Skipping - travis does not have ffmpeg / apt-get ffmpeg seems to fail this test.');
|
||||
return;
|
||||
}
|
||||
|
||||
var encoder = new PngEncoder();
|
||||
var fixture = fs.createReadStream(common.fixtures + '/pave.bin');
|
||||
|
||||
fixture.pipe(encoder);
|
||||
|
||||
var pngs = [];
|
||||
encoder.on('data', function(buffer) {
|
||||
pngs.push(buffer);
|
||||
});
|
||||
|
||||
|
||||
process.on('exit', function() {
|
||||
assert.equal(pngs.length, 6);
|
||||
|
||||
for (var i = 0; i < pngs.length; i++) {
|
||||
var png = pngs[i];
|
||||
|
||||
// Looks like a valid PNG
|
||||
assert.equal(png.toString('ascii', 1, 4), 'PNG');
|
||||
|
||||
// Not the same as previous png
|
||||
assert.ok(png !== pngs[i - 1]);
|
||||
}
|
||||
});
|
||||
gerado
externo
+30
@@ -0,0 +1,30 @@
|
||||
var common = require('../../../common');
|
||||
var net = require('net');
|
||||
var assert = require('assert');
|
||||
var TcpVideoStream = require(common.lib + '/video/TcpVideoStream');
|
||||
|
||||
var server = net.createServer(function(connection) {
|
||||
});
|
||||
|
||||
var events = [];
|
||||
server.listen(common.TCP_PORT, function() {
|
||||
var video = new TcpVideoStream({ip: 'localhost', port: common.TCP_PORT, timeout: 100});
|
||||
|
||||
video.connect(function(err) {
|
||||
if (err) throw err;
|
||||
|
||||
events.push('connectCb');
|
||||
});
|
||||
|
||||
video
|
||||
.on('error', function(err) {
|
||||
assert.equal(/timeout/i.test(err.message), true);
|
||||
|
||||
events.push('close');
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
|
||||
process.on('exit', function() {
|
||||
assert.deepEqual(events, ['connectCb', 'close']);
|
||||
});
|
||||
gerado
externo
+36
@@ -0,0 +1,36 @@
|
||||
var common = require('../../../common');
|
||||
var net = require('net');
|
||||
var assert = require('assert');
|
||||
var TcpVideoStream = require(common.lib + '/video/TcpVideoStream');
|
||||
|
||||
var expectedData = 'some data';
|
||||
var server = net.createServer(function(connection) {
|
||||
connection.write(expectedData);
|
||||
});
|
||||
|
||||
var events = [];
|
||||
server.listen(common.TCP_PORT, function() {
|
||||
var video = new TcpVideoStream({ip: 'localhost', port: common.TCP_PORT});
|
||||
|
||||
video.connect(function(err) {
|
||||
if (err) throw err;
|
||||
|
||||
events.push('connectCb');
|
||||
});
|
||||
|
||||
video
|
||||
.on('data', function(buffer) {
|
||||
assert.equal(buffer.toString(), expectedData);
|
||||
video.end();
|
||||
|
||||
events.push('data');
|
||||
})
|
||||
.on('close', function() {
|
||||
events.push('close');
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
|
||||
process.on('exit', function() {
|
||||
assert.deepEqual(events, ['connectCb', 'data', 'close']);
|
||||
});
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
var urun = require('urun');
|
||||
urun(__dirname);
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
var common = require('../../common');
|
||||
var assert = require('assert');
|
||||
var test = require('utest');
|
||||
var AtCommand = require(common.lib + '/control/AtCommand');
|
||||
|
||||
test('AtCommand', {
|
||||
'converting to string': function() {
|
||||
var cmd = new AtCommand('FOO', 1, [2, '3']);
|
||||
assert.equal(cmd.toString(), 'AT*FOO=1,2,3\r');
|
||||
},
|
||||
});
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
var common = require('../../common');
|
||||
var assert = require('assert');
|
||||
var test = require('utest');
|
||||
var AtCommandCreator = require(common.lib + '/control/AtCommandCreator');
|
||||
var at = require(common.lib + '/control/at');
|
||||
|
||||
test('AtCommandCreator', {
|
||||
before: function() {
|
||||
this.creator = new AtCommandCreator();
|
||||
},
|
||||
|
||||
'command.number keeps incrementing': function() {
|
||||
assert.equal(this.creator.ref().number, 0);
|
||||
assert.equal(this.creator.ref().number, 1);
|
||||
assert.equal(this.creator.ref().number, 2);
|
||||
},
|
||||
|
||||
'raw': function() {
|
||||
var cmd = this.creator.raw('FOO', 1, 2, 3);
|
||||
assert.equal(cmd.type, 'FOO');
|
||||
assert.deepEqual(cmd.args, [1, 2, 3]);
|
||||
|
||||
// An array can be given as well
|
||||
var cmd = this.creator.raw('FOO', [1, 2, 3]);
|
||||
assert.equal(cmd.type, 'FOO');
|
||||
assert.deepEqual(cmd.args, [1, 2, 3]);
|
||||
},
|
||||
|
||||
'ref': function() {
|
||||
var cmd = this.creator.ref();
|
||||
assert.equal(cmd.type, 'REF');
|
||||
assert.equal(cmd.args.length, 1);
|
||||
assert.equal(cmd.args[0], 0);
|
||||
|
||||
var cmd = this.creator.ref({fly: true});
|
||||
assert.ok(cmd.args[0] & AtCommandCreator.REF_FLAGS.takeoff);
|
||||
|
||||
var cmd = this.creator.ref({emergency: true});
|
||||
assert.ok(cmd.args[0] & AtCommandCreator.REF_FLAGS.emergency);
|
||||
},
|
||||
|
||||
'pcmd': function() {
|
||||
var cmd = this.creator.pcmd();
|
||||
assert.equal(cmd.type, 'PCMD');
|
||||
assert.equal(cmd.args.length, 5);
|
||||
assert.deepEqual(cmd.args, [0, 0, 0, 0, 0]);
|
||||
|
||||
// test all the aliases mapping to pcmd args
|
||||
var val = 0.75;
|
||||
assert.equal(this.creator.pcmd({left: val}).args[1], at.floatString(-val));
|
||||
assert.equal(this.creator.pcmd({right: val}).args[1], at.floatString(val));
|
||||
assert.equal(this.creator.pcmd({front: val}).args[2], at.floatString(-val));
|
||||
assert.equal(this.creator.pcmd({back: val}).args[2], at.floatString(val));
|
||||
assert.equal(this.creator.pcmd({up: val}).args[3], at.floatString(val));
|
||||
assert.equal(this.creator.pcmd({down: val}).args[3], at.floatString(-val));
|
||||
assert.equal(this.creator.pcmd({clockwise: val}).args[4], at.floatString(val));
|
||||
assert.equal(this.creator.pcmd({counterClockwise: val}).args[4], at.floatString(-val));
|
||||
|
||||
// test multiple aliases togeter
|
||||
var cmd = this.creator.pcmd({left: 0.1, clockwise: 0.3});
|
||||
assert.equal(cmd.args[1], at.floatString(-0.1));
|
||||
assert.equal(cmd.args[4], at.floatString(0.3));
|
||||
|
||||
// test progressive bit being unset when no aliases are provided
|
||||
var cmd = this.creator.pcmd();
|
||||
assert.equal(cmd.args[0] & AtCommandCreator.PCMD_FLAGS.progressive, false);
|
||||
|
||||
// test progressive bit being set automatically
|
||||
var cmd = this.creator.pcmd({left: 0.1});
|
||||
assert.ok(cmd.args[0] & (1 << 0));
|
||||
},
|
||||
|
||||
'calibrate': function() {
|
||||
var cmd = this.creator.calibrate(0);
|
||||
assert.equal(cmd.type, 'CALIB');
|
||||
assert.equal(cmd.args.length, 1);
|
||||
assert.equal(cmd.args[0], '0');
|
||||
cmd = this.creator.calibrate(1);
|
||||
assert.equal(cmd.type, 'CALIB');
|
||||
assert.equal(cmd.args.length, 1);
|
||||
assert.equal(cmd.args[0], '1');
|
||||
},
|
||||
|
||||
'config': function() {
|
||||
var cmd = this.creator.config('foo', 'bar');
|
||||
assert.equal(cmd.type, 'CONFIG');
|
||||
assert.equal(cmd.args.length, 2);
|
||||
assert.equal(cmd.args[0], '"foo"');
|
||||
assert.equal(cmd.args[1], '"bar"');
|
||||
},
|
||||
|
||||
'animateLeds() works as expected': function() {
|
||||
var hz = 3;
|
||||
var duration = 3;
|
||||
|
||||
var cmd = this.creator.animateLeds('blinkGreen', hz, duration);
|
||||
var expected = '1,' + at.floatString(hz) + ',' + duration;
|
||||
|
||||
assert.equal(cmd.type, 'CONFIG');
|
||||
assert.equal(cmd.args.length, 2);
|
||||
assert.equal(cmd.args[0], '"leds:leds_anim"');
|
||||
assert.equal(cmd.args[1], '"' + expected + '"');
|
||||
},
|
||||
|
||||
'animateLeds() does a red snake at 2 hz for 3s by default': function() {
|
||||
var cmd = this.creator.animateLeds();
|
||||
var expected = '9,' + at.floatString(2) + ',' + 3;
|
||||
assert.equal(cmd.args[1], '"' + expected + '"');
|
||||
},
|
||||
|
||||
'animateLeds() throws an error for unknown animations': function() {
|
||||
var self = this;
|
||||
assert.throws(function() {
|
||||
self.creator.animateLeds('does not exist');
|
||||
},/animation/);
|
||||
},
|
||||
|
||||
'animate() works as expected': function() {
|
||||
var duration = 2000;
|
||||
var cmd = this.creator.animate('yawShake', duration);
|
||||
var expected = '8,' + duration;
|
||||
|
||||
assert.equal(cmd.type, 'CONFIG');
|
||||
assert.equal(cmd.args.length, 2);
|
||||
assert.equal(cmd.args[0], '"control:flight_anim"');
|
||||
assert.equal(cmd.args[1], '"' + expected + '"');
|
||||
},
|
||||
|
||||
'animate() throws an error for unknown animations': function() {
|
||||
var self = this;
|
||||
assert.throws(function() {
|
||||
self.creator.animate('does not exist');
|
||||
},/animation/);
|
||||
},
|
||||
});
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
var common = require('../../common');
|
||||
var assert = require('assert');
|
||||
var test = require('utest');
|
||||
var sinon = require('sinon');
|
||||
var UdpControl = require(common.lib + '/control/UdpControl');
|
||||
|
||||
test('UdpControl', {
|
||||
'queues commands until flush() is invoked': function() {
|
||||
var fakeSocket = {send: sinon.spy()};
|
||||
var fakePort = 12345;
|
||||
var fakeIp = '255.0.23.42';
|
||||
|
||||
var control = new UdpControl({
|
||||
socket : fakeSocket,
|
||||
port : fakePort,
|
||||
ip : fakeIp,
|
||||
});
|
||||
|
||||
var ref = control.ref();
|
||||
assert.equal(ref.type, 'REF');
|
||||
|
||||
var pcmd = control.pcmd();
|
||||
assert.equal(pcmd.type, 'PCMD');
|
||||
|
||||
control.flush();
|
||||
|
||||
assert.equal(fakeSocket.send.callCount, 1);
|
||||
|
||||
var sendArgs = fakeSocket.send.getCall(0).args;
|
||||
var buffer = sendArgs.shift();
|
||||
var offset = sendArgs.shift();
|
||||
var length = sendArgs.shift();
|
||||
var port = sendArgs.shift();
|
||||
var ip = sendArgs.shift();
|
||||
|
||||
assert.equal(Buffer.isBuffer(buffer), true);
|
||||
assert.deepEqual(buffer.toString(), ref + pcmd);
|
||||
assert.deepEqual(offset, 0);
|
||||
assert.deepEqual(length, buffer.length);
|
||||
assert.deepEqual(port, fakePort);
|
||||
assert.deepEqual(ip, fakeIp);
|
||||
|
||||
// there should be nothing to flush now
|
||||
control.flush();
|
||||
assert.equal(fakeSocket.send.callCount, 1);
|
||||
},
|
||||
|
||||
'flush() does not do anything if no commands are queued': function() {
|
||||
var control = new UdpControl();
|
||||
control.flush();
|
||||
},
|
||||
|
||||
'close() is delgated to socket': function() {
|
||||
var fakeSocket = {close: sinon.spy()};
|
||||
var control = new UdpControl({socket: fakeSocket});
|
||||
|
||||
control.close();
|
||||
assert.equal(fakeSocket.close.callCount, 1);
|
||||
},
|
||||
});
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
var common = require('../../common');
|
||||
var assert = require('assert');
|
||||
var test = require('utest');
|
||||
var at = require(common.lib + '/control/at');
|
||||
|
||||
test('at', {
|
||||
'-0.8': function() {
|
||||
assert.equal(at.floatString(-0.8), -1085485875);
|
||||
},
|
||||
|
||||
'0': function() {
|
||||
assert.equal(at.floatString(0), 0);
|
||||
},
|
||||
});
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
var common = require('../../common');
|
||||
var assert = require('assert');
|
||||
var test = require('utest');
|
||||
var meta = require(common.lib + '/misc/meta');
|
||||
|
||||
function SampleClass() {}
|
||||
SampleClass.prototype.a = function() {};
|
||||
SampleClass.prototype.b = function() {};
|
||||
SampleClass.prototype._c = function() {};
|
||||
SampleClass.prototype._d = function() {};
|
||||
|
||||
test('meta', {
|
||||
'methods': function() {
|
||||
assert.deepEqual(meta.methods(SampleClass), ['a', 'b']);
|
||||
},
|
||||
});
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
var common = require('../../common');
|
||||
var assert = require('assert');
|
||||
var test = require('utest');
|
||||
var sinon = require('sinon');
|
||||
var UdpNavdataStream = require(common.lib + '/navdata/UdpNavdataStream');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
test('UdpNavdataStream', {
|
||||
before: function() {
|
||||
this.fakePort = 18943;
|
||||
this.fakeIp = '23.42.1776.20';
|
||||
this.fakeTimeout = 100;
|
||||
|
||||
this.fakeSocket = new EventEmitter();
|
||||
this.fakeSocket.bind = sinon.stub();
|
||||
this.fakeSocket.send = sinon.stub();
|
||||
this.fakeSocket.close = sinon.stub();
|
||||
|
||||
this.fakeParser = sinon.stub();
|
||||
|
||||
this.stream = new UdpNavdataStream({
|
||||
socket : this.fakeSocket,
|
||||
port : this.fakePort,
|
||||
ip : this.fakeIp,
|
||||
parser : this.fakeParser,
|
||||
timeout : this.fakeTimeout,
|
||||
});
|
||||
|
||||
this.clock = sinon.useFakeTimers();
|
||||
},
|
||||
|
||||
after: function() {
|
||||
this.clock.restore();
|
||||
},
|
||||
|
||||
'is a readable stream': function() {
|
||||
assert.equal(this.stream.readable, true);
|
||||
assert.equal(typeof this.stream.pipe, 'function');
|
||||
},
|
||||
|
||||
'resume() configures the socket': function() {
|
||||
this.stream.resume();
|
||||
|
||||
// verify socket.bind()
|
||||
assert.equal(this.fakeSocket.bind.callCount, 1);
|
||||
|
||||
// verify socket.send()
|
||||
assert.equal(this.fakeSocket.send.callCount, 1);
|
||||
var sendArgs = this.fakeSocket.send.getCall(0).args;
|
||||
var buffer = sendArgs.shift();
|
||||
var offset = sendArgs.shift();
|
||||
var length = sendArgs.shift();
|
||||
var port = sendArgs.shift();
|
||||
var ip = sendArgs.shift();
|
||||
assert.equal(Buffer.isBuffer(buffer), true);
|
||||
assert.deepEqual(buffer, new Buffer([1]));
|
||||
assert.equal(offset, 0);
|
||||
assert.equal(length, buffer.length);
|
||||
assert.equal(port, this.fakePort);
|
||||
assert.equal(ip, this.fakeIp);
|
||||
},
|
||||
|
||||
'calling resume() does not rebind socket, but requests navdata again': function() {
|
||||
this.stream.resume();
|
||||
this.stream.resume();
|
||||
|
||||
assert.equal(this.fakeSocket.bind.callCount, 1);
|
||||
assert.equal(this.fakeSocket.send.callCount, 2);
|
||||
},
|
||||
|
||||
'navdata is requested again after timeout': function() {
|
||||
this.stream.resume();
|
||||
assert.equal(this.fakeSocket.send.callCount, 1);
|
||||
|
||||
this.clock.tick(this.fakeTimeout - 1);
|
||||
assert.equal(this.fakeSocket.send.callCount, 1);
|
||||
|
||||
this.clock.tick(1);
|
||||
assert.equal(this.fakeSocket.send.callCount, 2);
|
||||
},
|
||||
|
||||
'timeout is reset by navdata arriving': function() {
|
||||
this.stream.resume();
|
||||
|
||||
this.clock.tick(this.fakeTimeout - 1);
|
||||
|
||||
this.fakeParser.returns({});
|
||||
this.fakeSocket.emit('message', new Buffer(0));
|
||||
|
||||
this.clock.tick(1);
|
||||
assert.equal(this.fakeSocket.send.callCount, 1);
|
||||
|
||||
this.clock.tick(this.fakeTimeout);
|
||||
assert.equal(this.fakeSocket.send.callCount, 2);
|
||||
},
|
||||
|
||||
'incoming messages are parsed': function() {
|
||||
var fakeBuffer = new Buffer([1, 2, 3]);
|
||||
var fakeNavdata = {fake: 'navdata', sequenceNumber: 1};
|
||||
var dataSpy = sinon.spy();
|
||||
|
||||
this.fakeParser.returns(fakeNavdata);
|
||||
|
||||
this.stream.on('data', dataSpy);
|
||||
|
||||
this.stream.resume();
|
||||
this.fakeSocket.emit('message', fakeBuffer);
|
||||
|
||||
assert.equal(this.fakeParser.callCount, 1);
|
||||
assert.strictEqual(this.fakeParser.getCall(0).args[0], fakeBuffer);
|
||||
|
||||
assert.equal(dataSpy.callCount, 1);
|
||||
assert.strictEqual(dataSpy.getCall(0).args[0], fakeNavdata);
|
||||
},
|
||||
|
||||
'old navdata messages are ignored': function() {
|
||||
var fakeNavdataA = {sequenceNumber: 1};
|
||||
var fakeNavdataB = {sequenceNumber: 2};
|
||||
var fakeNavdataC = {sequenceNumber: 3};
|
||||
|
||||
this.fakeParser.withArgs(1).returns(fakeNavdataA);
|
||||
this.fakeParser.withArgs(2).returns(fakeNavdataB);
|
||||
this.fakeParser.withArgs(3).returns(fakeNavdataC);
|
||||
|
||||
var dataSpy = sinon.spy();
|
||||
this.stream.on('data', dataSpy);
|
||||
|
||||
this.stream.resume();
|
||||
this.fakeSocket.emit('message', 1);
|
||||
this.fakeSocket.emit('message', 3);
|
||||
this.fakeSocket.emit('message', 2);
|
||||
|
||||
assert.equal(this.fakeParser.callCount, 3);
|
||||
assert.equal(dataSpy.callCount, 2);
|
||||
|
||||
assert.equal(dataSpy.getCall(0).args[0].sequenceNumber, 1);
|
||||
assert.equal(dataSpy.getCall(1).args[0].sequenceNumber, 3);
|
||||
},
|
||||
|
||||
'navdata errors are ignored by default': function() {
|
||||
this.fakeParser.throws(new Error('bad'));
|
||||
|
||||
var dataSpy = sinon.spy();
|
||||
this.stream.on('data', dataSpy);
|
||||
|
||||
this.stream.resume();
|
||||
this.fakeSocket.emit('message', 1);
|
||||
|
||||
assert.equal(this.fakeParser.callCount, 1);
|
||||
assert.equal(dataSpy.callCount, 0);
|
||||
},
|
||||
|
||||
'navdata errors are emitted if there is an error handler': function() {
|
||||
var fakeErr = new Error('bad');
|
||||
this.fakeParser.throws(fakeErr);
|
||||
|
||||
var dataSpy = sinon.spy();
|
||||
var errorSpy = sinon.spy();
|
||||
|
||||
this.stream.on('data', dataSpy);
|
||||
this.stream.on('error', errorSpy);
|
||||
|
||||
this.stream.resume();
|
||||
this.fakeSocket.emit('message', 1);
|
||||
|
||||
assert.equal(this.fakeParser.callCount, 1);
|
||||
assert.equal(dataSpy.callCount, 0);
|
||||
assert.equal(errorSpy.callCount, 1);
|
||||
assert.strictEqual(errorSpy.getCall(0).args[0], fakeErr);
|
||||
},
|
||||
|
||||
'destroy() cleans up': function() {
|
||||
this.stream.destroy();
|
||||
assert.equal(this.fakeSocket.close.callCount, 1);
|
||||
},
|
||||
});
|
||||
+827
@@ -0,0 +1,827 @@
|
||||
var common = require('../../common');
|
||||
var assert = require('assert');
|
||||
var test = require('utest');
|
||||
var parseNavdata = require(common.lib + '/navdata/parseNavdata');
|
||||
var fs = require('fs');
|
||||
var fixture = fs.readFileSync(common.fixtures + '/navdata.bin');
|
||||
|
||||
test('parseNavdata', {
|
||||
'parses main payload': function() {
|
||||
var navdata = parseNavdata(fixture);
|
||||
|
||||
assert.equal(navdata.header, parseNavdata.NAVDATA_HEADER);
|
||||
|
||||
var droneState = navdata.droneState;
|
||||
assert.equal(droneState.flying, 0);
|
||||
assert.equal(droneState.videoEnabled, 0);
|
||||
assert.equal(droneState.visionEnabled, 0);
|
||||
assert.equal(droneState.controlAlgorithm, 0);
|
||||
assert.equal(droneState.altitudeControlAlgorithm, 1);
|
||||
assert.equal(droneState.startButtonState, 0);
|
||||
assert.equal(droneState.controlCommandAck, 1);
|
||||
assert.equal(droneState.cameraReady, 1);
|
||||
assert.equal(droneState.travellingEnabled, 0);
|
||||
assert.equal(droneState.usbReady, 0);
|
||||
assert.equal(droneState.navdataDemo, 0);
|
||||
assert.equal(droneState.navdataBootstrap, 0);
|
||||
assert.equal(droneState.motorProblem, 0);
|
||||
assert.equal(droneState.communicationLost, 0);
|
||||
assert.equal(droneState.softwareFault, 0);
|
||||
assert.equal(droneState.lowBattery, 0);
|
||||
assert.equal(droneState.userEmergencyLanding, 0);
|
||||
assert.equal(droneState.timerElapsed, 0);
|
||||
assert.equal(droneState.MagnometerNeedsCalibration, 0);
|
||||
assert.equal(droneState.anglesOutOfRange, 0);
|
||||
assert.equal(droneState.tooMuchWind, 0);
|
||||
assert.equal(droneState.ultrasonicSensorDeaf, 0);
|
||||
assert.equal(droneState.cutoutDetected, 0);
|
||||
assert.equal(droneState.picVersionNumberOk, 1);
|
||||
assert.equal(droneState.atCodecThreadOn, 1);
|
||||
assert.equal(droneState.navdataThreadOn, 1);
|
||||
assert.equal(droneState.videoThreadOn, 1);
|
||||
assert.equal(droneState.acquisitionThreadOn, 1);
|
||||
assert.equal(droneState.controlWatchdogDelay, 0);
|
||||
assert.equal(droneState.adcWatchdogDelay, 0);
|
||||
assert.equal(droneState.comWatchdogProblem, 1);
|
||||
assert.equal(droneState.emergencyLanding, 0);
|
||||
|
||||
assert.equal(navdata.sequenceNumber, 300711);
|
||||
assert.equal(navdata.visionFlag, 1);
|
||||
},
|
||||
|
||||
'parses demo option': function() {
|
||||
var demo = parseNavdata(fixture).demo;
|
||||
assert.equal(demo.flyState, 'FLYING_OK');
|
||||
assert.equal(demo.controlState, 'CTRL_LANDED');
|
||||
assert.equal(demo.batteryPercentage, 50);
|
||||
|
||||
assert.equal(demo.frontBackDegrees, 2.974);
|
||||
assert.equal(demo.leftRightDegrees, 0.55);
|
||||
assert.equal(demo.clockwiseDegrees, 1.933);
|
||||
|
||||
assert.equal(demo.rotation.frontBack, 2.974);
|
||||
assert.equal(demo.rotation.leftRight, 0.55);
|
||||
assert.equal(demo.rotation.clockwise, 1.933);
|
||||
|
||||
assert.equal(demo.rotation.pitch, 2.974);
|
||||
assert.equal(demo.rotation.roll, 0.55);
|
||||
assert.equal(demo.rotation.yaw, 1.933);
|
||||
|
||||
assert.equal(demo.rotation.theta, 2.974);
|
||||
assert.equal(demo.rotation.phi, 0.55);
|
||||
assert.equal(demo.rotation.psi, 1.933);
|
||||
|
||||
assert.equal(demo.rotation.y, 2.974);
|
||||
assert.equal(demo.rotation.x, 0.55);
|
||||
assert.equal(demo.rotation.z, 1.933);
|
||||
|
||||
assert.equal(demo.altitudeMeters, 0);
|
||||
assert.equal(demo.altitude, 0);
|
||||
|
||||
assert.equal(demo.xVelocity, 0.0585307739675045);
|
||||
assert.equal(demo.yVelocity, -0.8817979097366333);
|
||||
assert.equal(demo.zVelocity, 0);
|
||||
assert.equal(demo.velocity.x, 0.0585307739675045);
|
||||
assert.equal(demo.velocity.y, -0.8817979097366333);
|
||||
assert.equal(demo.velocity.z, 0);
|
||||
},
|
||||
|
||||
'parses time option': function() {
|
||||
var time = parseNavdata(fixture).time;
|
||||
assert.equal(time, 362979.125);
|
||||
},
|
||||
|
||||
'parses rawMeasures option': function() {
|
||||
var rawMeasures = parseNavdata(fixture).rawMeasures;
|
||||
|
||||
assert.equal(rawMeasures.accelerometers.x, 2040);
|
||||
assert.equal(rawMeasures.accelerometers.y, 2036);
|
||||
assert.equal(rawMeasures.accelerometers.z, 2528);
|
||||
|
||||
assert.equal(rawMeasures.gyrometers.x, -23);
|
||||
assert.equal(rawMeasures.gyrometers.y, 15);
|
||||
assert.equal(rawMeasures.gyrometers.z, 0);
|
||||
assert.equal(rawMeasures.gyroscopes.x, -23);
|
||||
assert.equal(rawMeasures.gyroscopes.y, 15);
|
||||
assert.equal(rawMeasures.gyroscopes.z, 0);
|
||||
|
||||
assert.equal(rawMeasures.gyrometers110[0], 0);
|
||||
assert.equal(rawMeasures.gyrometers110[1], 0);
|
||||
assert.equal(rawMeasures.gyrometers110.length, 2);
|
||||
assert.equal(rawMeasures.gyroscopes110.x, 0);
|
||||
assert.equal(rawMeasures.gyroscopes110.y, 0);
|
||||
|
||||
assert.equal(rawMeasures.batteryMilliVolt, 11686);
|
||||
|
||||
assert.equal(rawMeasures.usDebutEcho, 0);
|
||||
assert.equal(rawMeasures.usFinEcho, 0);
|
||||
assert.equal(rawMeasures.usAssociationEcho, 3758);
|
||||
assert.equal(rawMeasures.usDistanceEcho, 0);
|
||||
assert.equal(rawMeasures.us.echo.start, 0);
|
||||
assert.equal(rawMeasures.us.echo.end, 0);
|
||||
assert.equal(rawMeasures.us.echo.association, 3758);
|
||||
assert.equal(rawMeasures.us.echo.distance, 0);
|
||||
|
||||
assert.equal(rawMeasures.usCourbeTemps, 21423);
|
||||
assert.equal(rawMeasures.usCourbeValeur, 0);
|
||||
assert.equal(rawMeasures.usCourbeRef, 120);
|
||||
assert.equal(rawMeasures.us.curve.time, 21423);
|
||||
assert.equal(rawMeasures.us.curve.value, 0);
|
||||
assert.equal(rawMeasures.us.curve.ref, 120);
|
||||
|
||||
|
||||
assert.equal(rawMeasures.flagEchoIni, 1);
|
||||
assert.equal(rawMeasures.nbEcho, 1);
|
||||
assert.equal(rawMeasures.sumEcho, 3539193);
|
||||
assert.equal(rawMeasures.echo.flagIni, 1);
|
||||
assert.equal(rawMeasures.echo.num, 1);
|
||||
assert.equal(rawMeasures.echo.sum, 3539193);
|
||||
|
||||
assert.equal(rawMeasures.altTempRaw, 243);
|
||||
assert.equal(rawMeasures.altTemp, 243);
|
||||
},
|
||||
|
||||
'parses physMeasures option': function() {
|
||||
var actual = parseNavdata(fixture).physMeasures;
|
||||
var expected = {
|
||||
temperature : {
|
||||
accelerometer : 45.309303283691406,
|
||||
gyroscope : 55738
|
||||
},
|
||||
accelerometers : {
|
||||
x : 80.2970962524414,
|
||||
y : -33.318603515625,
|
||||
z : -942.5283203125
|
||||
},
|
||||
gyroscopes : {
|
||||
x : -0.11236488074064255,
|
||||
y : 0.06872134655714035,
|
||||
z : 0.06200997903943062
|
||||
},
|
||||
alim3V3 : 0,
|
||||
vrefEpson : 0,
|
||||
vrefIDG : 0
|
||||
};
|
||||
assert.equal(actual.temperature.accelerometer, expected.temperature.accelerometer);
|
||||
assert.equal(actual.temperature.gyroscope, expected.temperature.gyroscope);
|
||||
assert.equal(actual.accelerometers.x, expected.accelerometers.x);
|
||||
assert.equal(actual.accelerometers.y, expected.accelerometers.y);
|
||||
assert.equal(actual.accelerometers.z, expected.accelerometers.z);
|
||||
assert.equal(actual.gyroscopes.x, expected.gyroscopes.x);
|
||||
assert.equal(actual.gyroscopes.y, expected.gyroscopes.y);
|
||||
assert.equal(actual.gyroscopes.z, expected.gyroscopes.z);
|
||||
assert.equal(actual.alim3V3, expected.alim3V3);
|
||||
assert.equal(actual.vrefEpson, expected.vrefEpson);
|
||||
assert.equal(actual.vrefIDG, expected.vrefIDG);
|
||||
},
|
||||
|
||||
'parses gyrosOffsets option': function() {
|
||||
var actual = parseNavdata(fixture).gyrosOffsets;
|
||||
var expected = {
|
||||
x : -0.5329172611236572,
|
||||
y : 0.1788240224123001,
|
||||
z : 0
|
||||
};
|
||||
|
||||
assert.equal(actual.x, expected.x);
|
||||
assert.equal(actual.y, expected.y);
|
||||
assert.equal(actual.z, expected.z);
|
||||
},
|
||||
|
||||
'parses eulerAngles option': function() {
|
||||
var actual = parseNavdata(fixture).eulerAngles;
|
||||
var expected = {
|
||||
theta : 4866,
|
||||
phi : 2024
|
||||
};
|
||||
|
||||
assert.equal(actual.theta, expected.theta);
|
||||
assert.equal(actual.phi, expected.phi);
|
||||
},
|
||||
|
||||
'parses references option': function() {
|
||||
var actual = parseNavdata(fixture).references;
|
||||
var expected = {
|
||||
theta : 0,
|
||||
phi : 0,
|
||||
thetaI : 0,
|
||||
phiI : 0,
|
||||
pitch : 0,
|
||||
roll : 0,
|
||||
yaw : 0,
|
||||
psi : 0,
|
||||
vx : 0,
|
||||
vy : 0,
|
||||
thetaMod : 0,
|
||||
phiMod : 0,
|
||||
kVX : 0,
|
||||
kVY : 0,
|
||||
kMode : 0,
|
||||
ui : {
|
||||
time : 0,
|
||||
theta : 0,
|
||||
phi : 0,
|
||||
psi : 0,
|
||||
psiAccuracy : 0,
|
||||
seq : 0
|
||||
}
|
||||
};
|
||||
|
||||
assert.equal(actual.theta, expected.theta);
|
||||
assert.equal(actual.phi, expected.phi);
|
||||
assert.equal(actual.thetaI, expected.thetaI);
|
||||
assert.equal(actual.phiI, expected.phiI);
|
||||
assert.equal(actual.pitch, expected.pitch);
|
||||
assert.equal(actual.roll, expected.roll);
|
||||
assert.equal(actual.yaw, expected.yaw);
|
||||
assert.equal(actual.psi, expected.psi);
|
||||
assert.equal(actual.vx, expected.vx);
|
||||
assert.equal(actual.vy, expected.vy);
|
||||
assert.equal(actual.thetaMod, expected.thetaMod);
|
||||
assert.equal(actual.phiMod, expected.phiMod);
|
||||
assert.equal(actual.kVX, expected.kVX);
|
||||
assert.equal(actual.kVY, expected.kVY);
|
||||
assert.equal(actual.kMode, expected.kMode);
|
||||
assert.equal(actual.ui.time, expected.ui.time);
|
||||
assert.equal(actual.ui.theta, expected.ui.theta);
|
||||
assert.equal(actual.ui.phi, expected.ui.phi);
|
||||
assert.equal(actual.ui.psi, expected.ui.psi);
|
||||
assert.equal(actual.ui.psiAccuracy, expected.ui.psiAccuracy);
|
||||
assert.equal(actual.ui.seq, expected.ui.seq);
|
||||
},
|
||||
|
||||
'parses trims option': function() {
|
||||
var actual = parseNavdata(fixture).trims;
|
||||
var expected = {
|
||||
angularRates : {
|
||||
r : 0
|
||||
},
|
||||
eulerAngles : {
|
||||
theta : 3028.916015625,
|
||||
phi : 1544.318359375
|
||||
}
|
||||
};
|
||||
|
||||
assert.equal(actual.angularRates.r, expected.angularRates.r);
|
||||
assert.equal(actual.eulerAngles.theta, expected.eulerAngles.theta);
|
||||
assert.equal(actual.eulerAngles.phi, expected.eulerAngles.phi);
|
||||
},
|
||||
|
||||
'parses rcReferences option': function() {
|
||||
var actual = parseNavdata(fixture).rcReferences;
|
||||
var expected = {
|
||||
pitch : 0,
|
||||
roll : 0,
|
||||
yaw : 0,
|
||||
gaz : 0,
|
||||
ag : 0
|
||||
};
|
||||
|
||||
assert.equal(actual.pitch, expected.pitch);
|
||||
assert.equal(actual.roll, expected.roll);
|
||||
assert.equal(actual.yaw, expected.yaw);
|
||||
assert.equal(actual.gaz, expected.gaz);
|
||||
assert.equal(actual.ag, expected.ag);
|
||||
},
|
||||
|
||||
'parses pwm option': function() {
|
||||
var pwm = parseNavdata(fixture).pwm;
|
||||
|
||||
assert.deepEqual(pwm.motors, [0, 0, 0, 0]);
|
||||
assert.deepEqual(pwm.satMotors, [255, 255, 255, 255]);
|
||||
assert.equal(pwm.gazFeedForward, 0);
|
||||
assert.equal(pwm.gazAltitude, 0);
|
||||
assert.equal(pwm.altitudeIntegral, 0);
|
||||
assert.equal(pwm.vzRef, 0);
|
||||
assert.equal(pwm.uPitch, 0);
|
||||
assert.equal(pwm.uRoll, 0);
|
||||
assert.equal(pwm.uYaw, 0);
|
||||
assert.equal(pwm.yawUI, 0);
|
||||
assert.equal(pwm.uPitchPlanif, 0);
|
||||
assert.equal(pwm.uRollPlanif, 0);
|
||||
assert.equal(pwm.uYawPlanif, 0);
|
||||
assert.equal(pwm.uGazPlanif, 0);
|
||||
assert.deepEqual(pwm.motorCurrents, [0, 0, 0, 0]);
|
||||
assert.equal(pwm.altitudeProp, 0);
|
||||
assert.equal(pwm.altitudeDer, 0);
|
||||
},
|
||||
|
||||
'parses altitude option': function() {
|
||||
var actual = parseNavdata(fixture).altitude;
|
||||
var expected = {
|
||||
vision : 243,
|
||||
velocity : 0,
|
||||
ref : 0,
|
||||
raw : 243,
|
||||
observer : {
|
||||
acceleration: 0,
|
||||
altitude: 0,
|
||||
x: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
state: 0
|
||||
},
|
||||
estimated: {
|
||||
vb: {
|
||||
x: 0,
|
||||
y: 0
|
||||
},
|
||||
state: 0
|
||||
}
|
||||
};
|
||||
|
||||
assert.equal(actual.vision, expected.vision);
|
||||
assert.equal(actual.velocity, expected.velocity);
|
||||
assert.equal(actual.ref, expected.ref);
|
||||
assert.equal(actual.raw, expected.raw);
|
||||
assert.equal(actual.observer.acceleration, expected.observer.acceleration);
|
||||
assert.equal(actual.observer.altitude, expected.observer.altitude);
|
||||
assert.equal(actual.observer.x.x, expected.observer.x.x);
|
||||
assert.equal(actual.observer.x.y, expected.observer.x.y);
|
||||
assert.equal(actual.observer.x.z, expected.observer.x.z);
|
||||
assert.equal(actual.observer.state, expected.observer.state);
|
||||
assert.equal(actual.estimated.vb.x, expected.estimated.vb.x);
|
||||
assert.equal(actual.estimated.vb.y, expected.estimated.vb.y);
|
||||
assert.equal(actual.estimated.state, expected.estimated.state);
|
||||
},
|
||||
|
||||
'parses visionRaw option': function() {
|
||||
var actual = parseNavdata(fixture).visionRaw;
|
||||
var expected = {
|
||||
tx: 1.3266397714614868,
|
||||
ty: -0.7230937480926514,
|
||||
tz: 0
|
||||
};
|
||||
|
||||
assert.equal(actual.tx, expected.tx);
|
||||
assert.equal(actual.ty, expected.ty);
|
||||
assert.equal(actual.tz, expected.tz);
|
||||
},
|
||||
|
||||
'parses visionOf option': function() {
|
||||
var actual = parseNavdata(fixture).visionOf;
|
||||
var expected = {
|
||||
dx: [0, 0, 0, 0, 0],
|
||||
dy: [0, 0, 0, 0, 0]
|
||||
};
|
||||
|
||||
assert.deepEqual(actual.dx, expected.dx);
|
||||
assert.deepEqual(actual.dy, expected.dy);
|
||||
},
|
||||
|
||||
'parses vision option': function() {
|
||||
var actual = parseNavdata(fixture).vision;
|
||||
var expected = {
|
||||
state: 2,
|
||||
misc: 0,
|
||||
phi: {
|
||||
trim: 0,
|
||||
refProp: 0
|
||||
},
|
||||
theta: {
|
||||
trim: 0,
|
||||
refProp: 0
|
||||
},
|
||||
newRawPicture: 0,
|
||||
capture: {
|
||||
theta: 0.05190306529402733,
|
||||
phi: 0.009620788507163525,
|
||||
psi: 0.033727407455444336,
|
||||
altitude: 243,
|
||||
time: 362.969
|
||||
},
|
||||
bodyV: {
|
||||
x: 0.05845191329717636,
|
||||
y: -0.8817280530929565,
|
||||
z: 0.011505687609314919
|
||||
},
|
||||
delta: {
|
||||
phi: 0,
|
||||
theta: 0,
|
||||
psi: 0
|
||||
},
|
||||
gold: {
|
||||
defined: 0,
|
||||
reset: 0,
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
};
|
||||
|
||||
assert.equal(actual.state, expected.state);
|
||||
assert.equal(actual.misc, expected.misc);
|
||||
assert.deepEqual(actual.phi, expected.phi);
|
||||
assert.deepEqual(actual.theta, expected.theta);
|
||||
assert.equal(actual.newRawPicture, expected.newRawPicture);
|
||||
assert.deepEqual(actual.capture, expected.capture);
|
||||
assert.deepEqual(actual.bodyV, expected.bodyV);
|
||||
assert.deepEqual(actual.delta, expected.delta);
|
||||
assert.deepEqual(actual.gold, expected.gold);
|
||||
},
|
||||
|
||||
'parses visionPerf option': function() {
|
||||
var actual = parseNavdata(fixture).visionPerf;
|
||||
var expected = {
|
||||
szo: 0,
|
||||
corners: 0,
|
||||
compute: 0,
|
||||
tracking: 0,
|
||||
trans: 0,
|
||||
update: 0,
|
||||
custom: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
};
|
||||
|
||||
assert.equal(actual.szo, expected.szo);
|
||||
assert.equal(actual.corners, expected.corners);
|
||||
assert.equal(actual.compute, expected.compute);
|
||||
assert.equal(actual.tracking, expected.tracking);
|
||||
assert.equal(actual.trans, expected.trans);
|
||||
assert.equal(actual.update, expected.update);
|
||||
assert.deepEqual(actual.custom, expected.custom);
|
||||
},
|
||||
|
||||
'parses trackersSend option': function() {
|
||||
var actual = parseNavdata(fixture).trackersSend;
|
||||
var expected = {
|
||||
locked: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
|
||||
point: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 0 }
|
||||
]
|
||||
};
|
||||
|
||||
assert.deepEqual(actual.locked, expected.locked);
|
||||
assert.deepEqual(actual.point, expected.point);
|
||||
},
|
||||
|
||||
'parses visionDetect option': function() {
|
||||
var actual = parseNavdata(fixture).visionDetect;
|
||||
var expected = {
|
||||
nbDetected: 0,
|
||||
type: [0, 0, 0, 0],
|
||||
xc: [0, 0, 0, 0],
|
||||
yc: [0, 0, 0, 0],
|
||||
width: [0, 0, 0, 0],
|
||||
height: [0, 0, 0, 0],
|
||||
dist: [0, 0, 0, 0],
|
||||
orientationAngle: [0, 0, 0, 0],
|
||||
rotation: [{
|
||||
m11: 0,
|
||||
m12: 0,
|
||||
m13: 0,
|
||||
m21: 0,
|
||||
m22: 0,
|
||||
m23: 0,
|
||||
m31: 0,
|
||||
m32: 0,
|
||||
m33: 0
|
||||
}, {
|
||||
m11: 0,
|
||||
m12: 0,
|
||||
m13: 0,
|
||||
m21: 0,
|
||||
m22: 0,
|
||||
m23: 0,
|
||||
m31: 0,
|
||||
m32: 0,
|
||||
m33: 0
|
||||
}, {
|
||||
m11: 0,
|
||||
m12: 0,
|
||||
m13: 0,
|
||||
m21: 0,
|
||||
m22: 0,
|
||||
m23: 0,
|
||||
m31: 0,
|
||||
m32: 0,
|
||||
m33: 0
|
||||
}, {
|
||||
m11: 0,
|
||||
m12: 0,
|
||||
m13: 0,
|
||||
m21: 0,
|
||||
m22: 0,
|
||||
m23: 0,
|
||||
m31: 0,
|
||||
m32: 0,
|
||||
m33: 0
|
||||
}],
|
||||
translation: [{
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
}, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
}, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
}, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
}],
|
||||
cameraSource: [0, 0, 0, 0]
|
||||
};
|
||||
|
||||
assert.equal(actual.nbDetected, expected.nbDetected);
|
||||
assert.deepEqual(actual.type, expected.type);
|
||||
assert.deepEqual(actual.xc, expected.xc);
|
||||
assert.deepEqual(actual.yc, expected.yc);
|
||||
assert.deepEqual(actual.width, expected.width);
|
||||
assert.deepEqual(actual.height, expected.height);
|
||||
assert.deepEqual(actual.dist, expected.dist);
|
||||
assert.deepEqual(actual.orientationAngle, expected.orientationAngle);
|
||||
assert.deepEqual(actual.rotation, expected.rotation);
|
||||
assert.deepEqual(actual.translation, expected.translation);
|
||||
assert.deepEqual(actual.cameraSource, expected.cameraSource);
|
||||
},
|
||||
|
||||
'parses watchdog option': function() {
|
||||
var watchdog = parseNavdata(fixture).watchdog;
|
||||
assert.equal(watchdog, 4822);
|
||||
},
|
||||
|
||||
'parses adcDataFrame option': function() {
|
||||
var actual = parseNavdata(fixture).adcDataFrame;
|
||||
var expected = {
|
||||
version: 0,
|
||||
dataFrame: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
|
||||
};
|
||||
|
||||
assert.equal(actual.version, expected.version);
|
||||
assert.deepEqual(actual.dataFrame, expected.dataFrame);
|
||||
},
|
||||
|
||||
'parses videoStream option': function() {
|
||||
var actual = parseNavdata(fixture).videoStream;
|
||||
var expected = {
|
||||
quant: 0,
|
||||
frame: {
|
||||
size: 4597,
|
||||
number: 46105
|
||||
},
|
||||
atcmd: {
|
||||
sequence: 0,
|
||||
meanGap: 0,
|
||||
varGap: 0,
|
||||
quality: 0
|
||||
},
|
||||
bitrate: {
|
||||
out: 0,
|
||||
desired: 0
|
||||
},
|
||||
data: [0, 0, 0, 0, 0],
|
||||
tcpQueueLevel: 0,
|
||||
fifoQueueLevel: 0
|
||||
};
|
||||
|
||||
assert.equal(actual.quant, expected.quant);
|
||||
assert.deepEqual(actual.frame, expected.frame);
|
||||
assert.deepEqual(actual.atcmd, expected.atcmd);
|
||||
assert.deepEqual(actual.bitrate, expected.bitrate);
|
||||
assert.deepEqual(actual.data, expected.data);
|
||||
assert.equal(actual.tcpQueueLevel, expected.tcpQueueLevel);
|
||||
assert.equal(actual.fifoQueueLevel, expected.fifoQueueLevel);
|
||||
},
|
||||
|
||||
'parses games option': function() {
|
||||
var actual = parseNavdata(fixture).games;
|
||||
var expected = {
|
||||
counters: {
|
||||
doubleTap: 0,
|
||||
finishLine: 0
|
||||
}
|
||||
};
|
||||
|
||||
assert.equal(actual.counters.doubleTap, expected.counters.doubleTap);
|
||||
assert.equal(actual.counters.finishLine, expected.counters.finishLine);
|
||||
},
|
||||
|
||||
'parses pressureRaw option': function() {
|
||||
var actual = parseNavdata(fixture).pressureRaw;
|
||||
var expected = {
|
||||
up: 39148,
|
||||
ut: 32556,
|
||||
temperature: 435,
|
||||
pressure: 101586
|
||||
};
|
||||
|
||||
assert.equal(actual.up, expected.up);
|
||||
assert.equal(actual.ut, expected.ut);
|
||||
assert.equal(actual.temperature, expected.temperature);
|
||||
assert.equal(actual.pressure, expected.pressure);
|
||||
},
|
||||
|
||||
'parses magneto option': function() {
|
||||
var actual = parseNavdata(fixture).magneto;
|
||||
var expected = {
|
||||
mx: 30,
|
||||
my: -56,
|
||||
mz: 80,
|
||||
raw: {
|
||||
x: 189,
|
||||
y: -100.8984375,
|
||||
z: -278.4375
|
||||
},
|
||||
rectified: {
|
||||
x: 145.08058166503906,
|
||||
y: -84.93736267089844,
|
||||
z: -287.18157958984375
|
||||
},
|
||||
offset: {
|
||||
x: 29.21237564086914,
|
||||
y: -13.282999038696289,
|
||||
z: 0
|
||||
},
|
||||
heading: {
|
||||
unwrapped: 0,
|
||||
gyroUnwrapped: 0.00041322660399600863,
|
||||
fusionUnwrapped: 1.933355689048767
|
||||
},
|
||||
ok: 1,
|
||||
state: 513,
|
||||
radius: -20.9833984375,
|
||||
error: {
|
||||
mean: 1129450962944,
|
||||
variance: -1.9905589299967167e-20
|
||||
}
|
||||
};
|
||||
|
||||
assert.equal(actual.mx, expected.mx);
|
||||
assert.equal(actual.my, expected.my);
|
||||
assert.deepEqual(actual.raw, expected.raw);
|
||||
assert.deepEqual(actual.rectified, expected.rectified);
|
||||
assert.deepEqual(actual.offset, expected.offset);
|
||||
assert.deepEqual(actual.heading, expected.heading);
|
||||
assert.equal(actual.mz, expected.mz);
|
||||
assert.equal(actual.ok, expected.ok);
|
||||
assert.equal(actual.state, expected.state);
|
||||
assert.equal(actual.radius, expected.radius);
|
||||
assert.deepEqual(actual.error, expected.error);
|
||||
},
|
||||
|
||||
'parses windSpeed option': function() {
|
||||
var actual = parseNavdata(fixture).windSpeed;
|
||||
var expected = {
|
||||
speed: 0,
|
||||
angle: 0,
|
||||
compensation: {
|
||||
theta: 0,
|
||||
phi: 0
|
||||
},
|
||||
stateX: [0.05845191329717636, -0.8817280530929565, 0, 0, 305.5962829589844, -236.80516052246094],
|
||||
debug: [0, 0, 0]
|
||||
};
|
||||
|
||||
assert.equal(actual.speed, expected.speed);
|
||||
assert.equal(actual.angle, expected.angle);
|
||||
assert.deepEqual(actual.compensation, expected.compensation);
|
||||
assert.deepEqual(actual.stateX, expected.stateX);
|
||||
assert.deepEqual(actual.debug, expected.debug);
|
||||
},
|
||||
|
||||
'parses kalmanPressure option': function() {
|
||||
var actual = parseNavdata(fixture).kalmanPressure;
|
||||
var expected = {
|
||||
offsetPressure: 101580,
|
||||
estimated: {
|
||||
altitude: 0,
|
||||
velocity: 0,
|
||||
angle: {
|
||||
pwm: 0,
|
||||
pressure: 0
|
||||
},
|
||||
us: {
|
||||
offset: 0,
|
||||
prediction: 0
|
||||
},
|
||||
covariance: {
|
||||
alt: 0.0005193915567360818,
|
||||
pwm: 0.6806257367134094,
|
||||
velocity: 0.025059189647436142
|
||||
},
|
||||
groundEffect: true,
|
||||
sum: 1.401298464324817e-45,
|
||||
reject: false,
|
||||
uMultisinus: 0,
|
||||
gazAltitude: 0,
|
||||
flagMultisinus: false,
|
||||
flagMultisinusStart: false
|
||||
}
|
||||
};
|
||||
|
||||
assert.equal(actual.offsetPressure, expected.offsetPressure);
|
||||
assert.equal(actual.estimated.altitude, expected.estimated.altitude);
|
||||
assert.equal(actual.estimated.velocity, expected.estimated.velocity);
|
||||
assert.deepEqual(actual.estimated.angle, expected.estimated.angle);
|
||||
assert.deepEqual(actual.estimated.us, expected.estimated.us);
|
||||
assert.deepEqual(actual.estimated.covariance, expected.estimated.covariance);
|
||||
assert.equal(actual.estimated.groundEffect, expected.estimated.groundEffect);
|
||||
assert.equal(actual.estimated.sum, expected.estimated.sum);
|
||||
assert.equal(actual.estimated.reject, expected.estimated.reject);
|
||||
assert.equal(actual.estimated.uMultisinus, expected.estimated.uMultisinus);
|
||||
assert.equal(actual.estimated.gazAltitude, expected.estimated.gazAltitude);
|
||||
assert.equal(actual.estimated.flagMultisinus, expected.estimated.flagMultisinus);
|
||||
assert.equal(actual.estimated.flagMultisinusStart, expected.estimated.flagMultisinusStart);
|
||||
},
|
||||
|
||||
'parses hdvideoStream option': function() {
|
||||
var actual = parseNavdata(fixture).hdvideoStream;
|
||||
var expected = {
|
||||
hdvideoState: 0,
|
||||
storageFifo: {
|
||||
nbPackets: 0,
|
||||
size: 0
|
||||
},
|
||||
usbkey: {
|
||||
size: 0,
|
||||
freespace: 0,
|
||||
remainingTime: 0
|
||||
},
|
||||
frameNumber: 0
|
||||
};
|
||||
|
||||
assert.equal(actual.hdvideoState, expected.hdvideoState);
|
||||
assert.deepEqual(actual.storageFifo, expected.storageFifo);
|
||||
assert.deepEqual(actual.usbkey, expected.usbkey);
|
||||
assert.equal(actual.frameNumber, expected.frameNumber);
|
||||
},
|
||||
|
||||
'parses wifi option': function() {
|
||||
var wifi = parseNavdata(fixture).wifi;
|
||||
assert.equal(wifi.linkQuality, 1);
|
||||
},
|
||||
|
||||
'parses zimmu3000 option': function() {
|
||||
var actual = parseNavdata(fixture).zimmu3000;
|
||||
var expected = {
|
||||
vzimmuLSB: 0,
|
||||
vzfind: 0
|
||||
};
|
||||
|
||||
assert.equal(actual.vzimmuLSB, expected.vzimmuLSB);
|
||||
assert.equal(actual.vzfind, expected.vzfind);
|
||||
},
|
||||
|
||||
'throws exception on invalid header': function() {
|
||||
assert.throws(function() {
|
||||
parseNavdata(new Buffer([1, 2, 3, 4]));
|
||||
}, /header/i);
|
||||
},
|
||||
|
||||
'detects bad checksum': function() {
|
||||
// hacky way to get a copy of our fixture
|
||||
var fixtureCopy = Buffer.concat([new Buffer(0), fixture]);
|
||||
|
||||
// corrupt a byte inside our fixture
|
||||
fixtureCopy[23] = fixtureCopy[23] + 1;
|
||||
|
||||
assert.throws(function() {
|
||||
parseNavdata(fixtureCopy)
|
||||
}, /checksum/i);
|
||||
},
|
||||
|
||||
'no infinite loop when checksum is cut off': function() {
|
||||
var incompleteFixture = Buffer.concat([
|
||||
new Buffer(0),
|
||||
fixture.slice(0, fixture.length - 8) // strip checksum from end
|
||||
]);
|
||||
|
||||
assert.throws(function() {
|
||||
parseNavdata(incompleteFixture)
|
||||
}, /beyond/i);
|
||||
},
|
||||
});
|
||||
+515
@@ -0,0 +1,515 @@
|
||||
var common = require('../common');
|
||||
var assert = require('assert');
|
||||
var test = require('utest');
|
||||
var sinon = require('sinon');
|
||||
var Client = require(common.lib + '/Client');
|
||||
var PngEncoder = Client.PngEncoder;
|
||||
var TcpVideoStream = Client.TcpVideoStream;
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
test('Client', {
|
||||
before: function() {
|
||||
this.fakeUdpControl = {};
|
||||
this.fakeUdpControl.ref = sinon.stub();
|
||||
this.fakeUdpControl.pcmd = sinon.stub();
|
||||
this.fakeUdpControl.animateLeds = sinon.stub();
|
||||
this.fakeUdpControl.animate = sinon.stub();
|
||||
this.fakeUdpControl.config = sinon.stub();
|
||||
this.fakeUdpControl.flush = sinon.stub();
|
||||
|
||||
this.fakeUdpNavdataStream = new EventEmitter;
|
||||
this.fakeUdpNavdataStream.resume = sinon.stub();
|
||||
|
||||
this.pngEncoder = new PngEncoder();
|
||||
Client.PngEncoder = sinon.stub();
|
||||
Client.PngEncoder.returns(this.pngEncoder);
|
||||
|
||||
this.tcpVideoStream = new TcpVideoStream();
|
||||
Client.TcpVideoStream = sinon.stub();
|
||||
Client.TcpVideoStream.returns(this.tcpVideoStream);
|
||||
|
||||
this.options = {
|
||||
udpControl : this.fakeUdpControl,
|
||||
udpNavdataStream : this.fakeUdpNavdataStream,
|
||||
};
|
||||
|
||||
this.client = new Client(this.options);
|
||||
|
||||
this.clock = sinon.useFakeTimers();
|
||||
},
|
||||
|
||||
after: function() {
|
||||
this.clock.restore();
|
||||
},
|
||||
|
||||
'resume() calls _setInterval': function() {
|
||||
sinon.spy(this.client, '_setInterval');
|
||||
this.client.resume();
|
||||
|
||||
assert.equal(this.client._setInterval.callCount, 1);
|
||||
assert.equal(this.client._setInterval.getCall(0).args[0], 30);
|
||||
},
|
||||
|
||||
'resume() calls disableEmergency': function() {
|
||||
sinon.spy(this.client, 'disableEmergency');
|
||||
this.client.resume();
|
||||
|
||||
assert.equal(this.client.disableEmergency.callCount, 1);
|
||||
},
|
||||
|
||||
'navdata "data" events are proxied': function() {
|
||||
var fakeNavdata = {fake: 'navdata'};
|
||||
this.client.resume();
|
||||
|
||||
assert.equal(this.fakeUdpNavdataStream.resume.callCount, 1);
|
||||
|
||||
var gotNavdata;
|
||||
this.client.on('navdata', function(navdata) {
|
||||
gotNavdata = navdata;
|
||||
});
|
||||
|
||||
this.fakeUdpNavdataStream.emit('data', fakeNavdata);
|
||||
|
||||
assert.strictEqual(gotNavdata, fakeNavdata);
|
||||
},
|
||||
|
||||
'navdata custom controlState events are triggered': function() {
|
||||
// takeoff event
|
||||
var fakeNavdataTakeoff = {
|
||||
droneState: 'navdata',
|
||||
demo: {controlState: 'CTRL_TRANS_TAKEOFF'}
|
||||
};
|
||||
this.client.resume();
|
||||
|
||||
var gotEventTakeoff;
|
||||
this.client.on('takeoff', function() {
|
||||
gotEventTakeoff = true;
|
||||
});
|
||||
|
||||
this.fakeUdpNavdataStream.emit('data', fakeNavdataTakeoff);
|
||||
assert.equal(gotEventTakeoff, true);
|
||||
|
||||
// hovering event
|
||||
var fakeNavdataHovering = {
|
||||
droneState: 'navdata',
|
||||
demo: {controlState: 'CTRL_HOVERING'}
|
||||
};
|
||||
|
||||
var gotEventHovering;
|
||||
this.client.on('hovering', function() {
|
||||
gotEventHovering = true;
|
||||
});
|
||||
|
||||
this.fakeUdpNavdataStream.emit('data', fakeNavdataHovering);
|
||||
assert.equal(gotEventHovering, true);
|
||||
|
||||
// flying event
|
||||
var fakeNavdataFlying = {
|
||||
droneState: 'navdata',
|
||||
demo: {controlState: 'CTRL_FLYING'}
|
||||
};
|
||||
|
||||
var gotEventFlying;
|
||||
this.client.on('flying', function() {
|
||||
gotEventFlying = true;
|
||||
});
|
||||
|
||||
this.fakeUdpNavdataStream.emit('data', fakeNavdataFlying);
|
||||
assert.equal(gotEventFlying, true);
|
||||
|
||||
// landing event
|
||||
var fakeNavdataLanding = {
|
||||
droneState: 'navdata',
|
||||
demo: {controlState: 'CTRL_TRANS_LANDING'}
|
||||
};
|
||||
|
||||
var gotEventLanding;
|
||||
this.client.on('landing', function() {
|
||||
gotEventLanding = true;
|
||||
});
|
||||
|
||||
this.fakeUdpNavdataStream.emit('data', fakeNavdataLanding);
|
||||
assert.equal(gotEventLanding, true);
|
||||
|
||||
// landed event
|
||||
var fakeNavdataLanded = {
|
||||
droneState: 'navdata',
|
||||
demo: {controlState: 'CTRL_LANDED'}
|
||||
};
|
||||
|
||||
var gotEventLanded;
|
||||
this.client.on('landed', function() {
|
||||
gotEventLanded = true;
|
||||
});
|
||||
|
||||
this.fakeUdpNavdataStream.emit('data', fakeNavdataLanded);
|
||||
assert.equal(gotEventLanded, true);
|
||||
},
|
||||
|
||||
'navdata custom battery events are triggered': function() {
|
||||
// takeoff event
|
||||
var fakeNavdata = {
|
||||
droneState: {lowBattery:1},
|
||||
demo: {batteryPercentage: 23}
|
||||
};
|
||||
this.client.resume();
|
||||
|
||||
var gotEventLow;
|
||||
this.client.on('lowBattery', function(level) {
|
||||
assert.equal(level, fakeNavdata.demo.batteryPercentage);
|
||||
gotEventLow = true;
|
||||
});
|
||||
|
||||
var gotEventLevel;
|
||||
this.client.on('batteryChange', function(level) {
|
||||
assert.equal(level, fakeNavdata.demo.batteryPercentage);
|
||||
gotEventLevel = true;
|
||||
});
|
||||
|
||||
this.fakeUdpNavdataStream.emit('data', fakeNavdata);
|
||||
assert.equal(gotEventLow, true);
|
||||
assert.equal(gotEventLevel, true);
|
||||
},
|
||||
|
||||
'navdata custom altitude events are triggered': function() {
|
||||
// takeoff event
|
||||
var fakeNavdata = {
|
||||
droneState: 'navdata',
|
||||
demo: {altitudeMeters: 23.5}
|
||||
};
|
||||
this.client.resume();
|
||||
|
||||
var gotEvent;
|
||||
this.client.on('altitudeChange', function(level) {
|
||||
assert.equal(level, fakeNavdata.demo.altitudeMeters);
|
||||
gotEvent = true;
|
||||
});
|
||||
|
||||
this.fakeUdpNavdataStream.emit('data', fakeNavdata);
|
||||
assert.equal(gotEvent, true);
|
||||
},
|
||||
|
||||
'navdata "error" events are ignored by default': function() {
|
||||
this.client.resume();
|
||||
|
||||
this.fakeUdpNavdataStream.emit('error', new Error('bad'));
|
||||
},
|
||||
|
||||
'navdata "error" events are proxied if there is an error listener': function() {
|
||||
var errorStub = sinon.stub();
|
||||
this.client.on('error', errorStub);
|
||||
this.client.resume();
|
||||
|
||||
var fakeErr = new Error('bad');
|
||||
this.fakeUdpNavdataStream.emit('error', fakeErr);
|
||||
|
||||
assert.equal(errorStub.callCount, 1);
|
||||
assert.strictEqual(errorStub.getCall(0).args[0], fakeErr);
|
||||
},
|
||||
|
||||
'resume() is idempotent': function() {
|
||||
var fakeNavdata = {fake: 'navdata'};
|
||||
this.client.resume();
|
||||
this.client.resume();
|
||||
|
||||
var eventCount = 0;
|
||||
this.client.on('navdata', function(navdata) {
|
||||
eventCount++;
|
||||
});
|
||||
|
||||
this.fakeUdpNavdataStream.emit('data', fakeNavdata);
|
||||
|
||||
assert.strictEqual(eventCount, 1);
|
||||
},
|
||||
|
||||
'resume() is idempotent': function() {
|
||||
var fakeNavdata = {fake: 'navdata'};
|
||||
this.client.resume();
|
||||
this.client.resume();
|
||||
|
||||
var eventCount = 0;
|
||||
this.client.on('navdata', function(navdata) {
|
||||
eventCount++;
|
||||
});
|
||||
|
||||
this.fakeUdpNavdataStream.emit('data', fakeNavdata);
|
||||
|
||||
assert.strictEqual(eventCount, 1);
|
||||
},
|
||||
|
||||
'options are passed to internal UdpControl': function() {
|
||||
var options = {fake: 'options'};
|
||||
|
||||
var gotOptions;
|
||||
Client.UdpControl = function(options) {
|
||||
gotOptions = options;
|
||||
};
|
||||
|
||||
var client = new Client(options);
|
||||
assert.strictEqual(gotOptions, options);
|
||||
},
|
||||
|
||||
'_setInterval caused period ref / pcmd commands': function() {
|
||||
this.client._setInterval(30);
|
||||
assert.equal(this.fakeUdpControl.ref.callCount, 0);
|
||||
assert.equal(this.fakeUdpControl.pcmd.callCount, 0);
|
||||
assert.equal(this.fakeUdpControl.flush.callCount, 0);
|
||||
|
||||
this.clock.tick(30);
|
||||
assert.equal(this.fakeUdpControl.ref.callCount, 1);
|
||||
assert.equal(this.fakeUdpControl.pcmd.callCount, 1);
|
||||
assert.equal(this.fakeUdpControl.flush.callCount, 1);
|
||||
assert.strictEqual(this.client._pcmd, this.fakeUdpControl.pcmd.getCall(0).args[0]);
|
||||
assert.strictEqual(this.client._ref, this.fakeUdpControl.ref.getCall(0).args[0]);
|
||||
|
||||
this.clock.tick(30);
|
||||
assert.equal(this.fakeUdpControl.ref.callCount, 2);
|
||||
assert.equal(this.fakeUdpControl.pcmd.callCount, 2);
|
||||
},
|
||||
|
||||
'_setInterval clears previous interval if set': function() {
|
||||
this.client._setInterval(30);
|
||||
this.client._setInterval(20);
|
||||
|
||||
this.clock.tick(20);
|
||||
assert.equal(this.fakeUdpControl.ref.callCount, 1);
|
||||
|
||||
this.clock.tick(10);
|
||||
assert.equal(this.fakeUdpControl.ref.callCount, 1);
|
||||
},
|
||||
|
||||
|
||||
'ref options are exposed as methods': function() {
|
||||
this.client.takeoff();
|
||||
assert.equal(this.client._ref.fly, true);
|
||||
|
||||
this.client.land();
|
||||
assert.equal(this.client._ref.fly, false);
|
||||
},
|
||||
|
||||
'pcmd options are exposed as methods': function() {
|
||||
this.client.up(0.5);
|
||||
assert.equal(this.client._pcmd.up, 0.5);
|
||||
assert.equal(this.client._pcmd.down, undefined);
|
||||
|
||||
this.client.down(0.5);
|
||||
assert.equal(this.client._pcmd.down, 0.5);
|
||||
assert.equal(this.client._pcmd.up, undefined);
|
||||
|
||||
this.client.left(0.5);
|
||||
assert.equal(this.client._pcmd.left, 0.5);
|
||||
assert.equal(this.client._pcmd.right, undefined);
|
||||
|
||||
this.client.right(0.5);
|
||||
assert.equal(this.client._pcmd.right, 0.5);
|
||||
assert.equal(this.client._pcmd.left, undefined);
|
||||
|
||||
this.client.front(0.5);
|
||||
assert.equal(this.client._pcmd.front, 0.5);
|
||||
assert.equal(this.client._pcmd.back, undefined);
|
||||
|
||||
this.client.back(0.5);
|
||||
assert.equal(this.client._pcmd.back, 0.5);
|
||||
assert.equal(this.client._pcmd.front, undefined);
|
||||
|
||||
this.client.clockwise(0.5);
|
||||
assert.equal(this.client._pcmd.clockwise, 0.5);
|
||||
assert.equal(this.client._pcmd.counterClockwise, undefined);
|
||||
|
||||
this.client.counterClockwise(0.5);
|
||||
assert.equal(this.client._pcmd.counterClockwise, 0.5);
|
||||
assert.equal(this.client._pcmd.clockwise, undefined);
|
||||
},
|
||||
|
||||
'pcmd methods conver strings to floats': function() {
|
||||
this.client.up('-0.5');
|
||||
assert.strictEqual(this.client._pcmd.up, -0.5);
|
||||
|
||||
this.client.down('-0.5');
|
||||
assert.strictEqual(this.client._pcmd.down, -0.5);
|
||||
},
|
||||
|
||||
'stop resets pcmd commands': function() {
|
||||
this.client.up(0.5);
|
||||
|
||||
this.client.stop();
|
||||
assert.deepEqual(this.client._pcmd, {});
|
||||
},
|
||||
|
||||
'config(): sends config command 10 times': function() {
|
||||
return console.log('skipped - currently broken, needs investigation');
|
||||
// Skip broken test below, see issue #47 for discussion
|
||||
this.client.resume();
|
||||
this.client.config('foo', 'bar');
|
||||
|
||||
for (var i = 1; i <= 10; i++) {
|
||||
this.clock.tick(30);
|
||||
assert.equal(this.fakeUdpControl.config.callCount, i);
|
||||
}
|
||||
|
||||
// Stop repeating after 10 intervals
|
||||
this.clock.tick(30);
|
||||
assert.equal(this.fakeUdpControl.config.callCount, 10);
|
||||
|
||||
// Check that the arguments were right
|
||||
var args = this.fakeUdpControl.config.getCall(0).args;
|
||||
assert.equal(args.length, 2);
|
||||
assert.equal(args[0], 'foo');
|
||||
assert.equal(args[1], 'bar');
|
||||
},
|
||||
|
||||
|
||||
'animateLeds(): sends config command 10 times': function() {
|
||||
this.client.resume();
|
||||
this.client.animateLeds('blinkGreen', 2, 5);
|
||||
|
||||
for (var i = 1; i <= 10; i++) {
|
||||
this.clock.tick(30);
|
||||
assert.equal(this.fakeUdpControl.animateLeds.callCount, i);
|
||||
}
|
||||
|
||||
// Stop repeating after 10 intervals
|
||||
this.clock.tick(30);
|
||||
assert.equal(this.fakeUdpControl.animateLeds.callCount, 10);
|
||||
|
||||
// Check that the arguments were right
|
||||
var args = this.fakeUdpControl.animateLeds.getCall(0).args;
|
||||
assert.equal(args.length, 3);
|
||||
assert.equal(args[0], 'blinkGreen');
|
||||
assert.equal(args[1], 2);
|
||||
assert.equal(args[2], 5);
|
||||
},
|
||||
|
||||
'animate(): sends config 10 times': function() {
|
||||
this.client.resume();
|
||||
this.client.animate('yawShake', 2000);
|
||||
|
||||
for (var i = 1; i <= 10; i++) {
|
||||
this.clock.tick(30);
|
||||
assert.equal(this.fakeUdpControl.animate.callCount, i);
|
||||
}
|
||||
|
||||
// Stop repeating after 10 intervals
|
||||
this.clock.tick(30);
|
||||
assert.equal(this.fakeUdpControl.animate.callCount, 10);
|
||||
|
||||
// Check that the arguments were right
|
||||
var args = this.fakeUdpControl.animate.getCall(0).args;
|
||||
assert.equal(args.length, 2);
|
||||
assert.equal(args[0], 'yawShake');
|
||||
assert.equal(args[1], 2000);
|
||||
},
|
||||
|
||||
'getPngStream creates and resume a single stream': function() {
|
||||
sinon.stub(this.tcpVideoStream, 'connect');
|
||||
|
||||
var pngStream1 = this.client.getPngStream();
|
||||
var pngStream2 = this.client.getPngStream();
|
||||
|
||||
// check that the underlying TcpVideoStream and PngEncoder were constructed only once
|
||||
assert.equal(Client.PngEncoder.callCount, 1);
|
||||
assert.equal(Client.TcpVideoStream.callCount, 1);
|
||||
assert.strictEqual(Client.TcpVideoStream.getCall(0).args[0], this.options);
|
||||
assert.equal(this.tcpVideoStream.connect.callCount, 1);
|
||||
|
||||
// check returned streams are always the same
|
||||
assert.strictEqual(pngStream1, this.pngEncoder);
|
||||
assert.strictEqual(pngStream2, this.pngEncoder);
|
||||
},
|
||||
|
||||
'getTcpVideoStream creates and resume a single stream': function() {
|
||||
sinon.stub(this.tcpVideoStream, 'connect');
|
||||
|
||||
var videoStream1 = this.client.getVideoStream();
|
||||
var videoStream2 = this.client.getVideoStream();
|
||||
|
||||
// check that the PngStream was constructed only once
|
||||
assert.equal(Client.TcpVideoStream.callCount, 1);
|
||||
assert.strictEqual(Client.TcpVideoStream.getCall(0).args[0], this.options);
|
||||
assert.equal(this.tcpVideoStream.connect.callCount, 1);
|
||||
|
||||
// check returned streams are always the same
|
||||
assert.strictEqual(videoStream1, this.tcpVideoStream);
|
||||
assert.strictEqual(videoStream2, this.tcpVideoStream);
|
||||
},
|
||||
|
||||
'after methods are called in client context': function() {
|
||||
var after = sinon.spy();
|
||||
|
||||
this.client.after(1000, after);
|
||||
|
||||
this.clock.tick(1000);
|
||||
assert.equal(after.callCount, 1);
|
||||
assert.equal(after.getCall(0).thisValue, this.client);
|
||||
},
|
||||
|
||||
'after enqueues methods to run after each other': function() {
|
||||
var after1 = sinon.spy();
|
||||
var after2 = sinon.spy();
|
||||
var after3 = sinon.spy();
|
||||
|
||||
this.client
|
||||
.after(1000, after1)
|
||||
.after(2000, after2)
|
||||
.after(3000, after3);
|
||||
|
||||
assert.equal(after1.callCount, 0);
|
||||
assert.equal(after2.callCount, 0);
|
||||
assert.equal(after3.callCount, 0);
|
||||
|
||||
// Nothing should be triggered yet
|
||||
this.clock.tick(500);
|
||||
assert.equal(after1.callCount, 0);
|
||||
assert.equal(after2.callCount, 0);
|
||||
assert.equal(after3.callCount, 0);
|
||||
|
||||
// First after callback should trigger
|
||||
this.clock.tick(500);
|
||||
assert.equal(after1.callCount, 1);
|
||||
assert.equal(after2.callCount, 0);
|
||||
assert.equal(after3.callCount, 0);
|
||||
|
||||
// Second after callback should trigger
|
||||
this.clock.tick(2000);
|
||||
assert.equal(after1.callCount, 1);
|
||||
assert.equal(after2.callCount, 1);
|
||||
assert.equal(after3.callCount, 0);
|
||||
|
||||
// Third after callback should trigger
|
||||
this.clock.tick(3000);
|
||||
assert.equal(after1.callCount, 1);
|
||||
assert.equal(after2.callCount, 1);
|
||||
assert.equal(after3.callCount, 1);
|
||||
},
|
||||
|
||||
'disableEmergency sets emergency bit to true until navdata confirms': function() {
|
||||
// disable implicit disableEmergency for this test
|
||||
sinon.stub(this.client, 'disableEmergency');
|
||||
this.client.resume();
|
||||
this.client.disableEmergency.restore();
|
||||
|
||||
// Initially emergency bit should be set to false
|
||||
var navdata = {droneState: {emergencyLanding: true}};
|
||||
this.fakeUdpNavdataStream.emit('data', navdata);
|
||||
assert.equal(this.client._ref.emergency, false);
|
||||
|
||||
// But calling disableEmergency should flip it on
|
||||
this.client.disableEmergency();
|
||||
this.fakeUdpNavdataStream.emit('data', navdata);
|
||||
assert.equal(this.client._ref.emergency, true);
|
||||
|
||||
// And make it stay on
|
||||
this.fakeUdpNavdataStream.emit('data', navdata);
|
||||
assert.equal(this.client._ref.emergency, true);
|
||||
|
||||
// Until the emergencyLanding status goes to false
|
||||
navdata.droneState.emergencyLanding = false;
|
||||
this.fakeUdpNavdataStream.emit('data', navdata);
|
||||
assert.equal(this.client._ref.emergency, false);
|
||||
|
||||
// But this should only happen once
|
||||
navdata.droneState.emergencyLanding = true;
|
||||
this.fakeUdpNavdataStream.emit('data', navdata);
|
||||
assert.equal(this.client._ref.emergency, false);
|
||||
},
|
||||
});
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
var common = require('../common');
|
||||
var assert = require('assert');
|
||||
var utest = require('utest');
|
||||
var sinon = require('sinon');
|
||||
var arDrone = require(common.root);
|
||||
|
||||
utest('main api', {
|
||||
before: function() {
|
||||
sinon.stub(arDrone.PngStream.prototype, 'resume');
|
||||
sinon.stub(arDrone.UdpNavdataStream.prototype, 'resume');
|
||||
sinon.stub(arDrone.Client.prototype, 'resume');
|
||||
},
|
||||
|
||||
after: function() {
|
||||
arDrone.PngStream.prototype.resume.restore();
|
||||
arDrone.UdpNavdataStream.prototype.resume.restore();
|
||||
arDrone.Client.prototype.resume.restore();
|
||||
},
|
||||
|
||||
'createClient': function() {
|
||||
var client = arDrone.createClient();
|
||||
assert.ok(client instanceof arDrone.Client);
|
||||
assert.equal(client.resume.callCount, 1);
|
||||
},
|
||||
|
||||
'createPngStream': function() {
|
||||
var pngStream = arDrone.createPngStream();
|
||||
assert.ok(pngStream instanceof arDrone.PngStream);
|
||||
assert.equal(pngStream.resume.callCount, 1);
|
||||
},
|
||||
|
||||
'createUdpControl': function() {
|
||||
var control = arDrone.createUdpControl();
|
||||
assert.ok(control instanceof arDrone.UdpControl);
|
||||
},
|
||||
|
||||
'createUdpNavdataStream': function() {
|
||||
var stream = arDrone.createUdpNavdataStream();
|
||||
assert.ok(stream instanceof arDrone.UdpNavdataStream);
|
||||
assert.equal(stream.resume.callCount, 1);
|
||||
},
|
||||
});
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
var common = require('../../common');
|
||||
var assert = require('assert');
|
||||
var test = require('utest');
|
||||
var PaVEParser = require(common.lib + '/video/PaVEParser');
|
||||
var fs = require('fs');
|
||||
var sinon = require('sinon');
|
||||
var fixture = fs.readFileSync(common.fixtures + '/pave.bin');
|
||||
var fixture68 = fs.readFileSync(common.fixtures + '/pave-68.bin');
|
||||
|
||||
test('PaVEParser', {
|
||||
before: function() {
|
||||
this.parser = new PaVEParser();
|
||||
},
|
||||
|
||||
'parses fixture properly': function() {
|
||||
var frames = [];
|
||||
this.parser.on('data', function(frame) {
|
||||
frames.push(frame);
|
||||
});
|
||||
|
||||
this.parser.write(fixture);
|
||||
|
||||
assert.equal(frames.length, 20);
|
||||
|
||||
var first = frames[0];
|
||||
|
||||
assert.equal(first.signature, 'PaVE');
|
||||
assert.equal(first.version, 2);
|
||||
assert.equal(first.video_codec, 4);
|
||||
assert.equal(first.header_size, 64);
|
||||
assert.equal(first.payload_size, 2632);
|
||||
assert.equal(first.encoded_stream_width, 640);
|
||||
assert.equal(first.encoded_stream_height, 368);
|
||||
assert.equal(first.display_width, 640);
|
||||
assert.equal(first.display_height, 360);
|
||||
assert.equal(first.frame_number, 17368);
|
||||
assert.equal(first.timestamp, 1792570814);
|
||||
assert.equal(first.total_chunks, 1);
|
||||
assert.equal(first.chunk_index, 0);
|
||||
assert.equal(first.frame_type, 3);
|
||||
assert.equal(first.control, 0);
|
||||
assert.equal(first.stream_byte_position_lw, 72366960);
|
||||
assert.equal(first.stream_byte_position_uw, 0);
|
||||
assert.equal(first.stream_id, 5);
|
||||
assert.equal(first.total_slices, 1);
|
||||
assert.equal(first.slice_index, 0);
|
||||
assert.equal(first.header1_size, 0);
|
||||
assert.equal(first.header2_size, 0);
|
||||
assert.equal(first.reserved2.length, 2);
|
||||
assert.equal(first.advertised_size, 2632);
|
||||
assert.equal(first.reserved3.length, 12);
|
||||
assert.equal(first.payload.length, 2632);
|
||||
},
|
||||
|
||||
'parses 68 byte frames properly': function() {
|
||||
var frames = [];
|
||||
this.parser.on('data', function(frame) {
|
||||
frames.push(frame);
|
||||
});
|
||||
|
||||
this.parser.write(fixture68);
|
||||
|
||||
assert.equal(frames.length, 5);
|
||||
|
||||
var first = frames[0];
|
||||
|
||||
assert.equal(first.signature, 'PaVE');
|
||||
assert.equal(first.version, 2);
|
||||
assert.equal(first.video_codec, 4);
|
||||
assert.equal(first.header_size, 68);
|
||||
assert.equal(first.payload_size, 10180);
|
||||
assert.equal(first.encoded_stream_width, 640);
|
||||
assert.equal(first.encoded_stream_height, 368);
|
||||
assert.equal(first.display_width, 640);
|
||||
assert.equal(first.display_height, 360);
|
||||
assert.equal(first.frame_number, 245401);
|
||||
assert.equal(first.timestamp, 8015353);
|
||||
assert.equal(first.total_chunks, 1);
|
||||
assert.equal(first.chunk_index, 0);
|
||||
assert.equal(first.frame_type, 1);
|
||||
assert.equal(first.control, 0);
|
||||
assert.equal(first.stream_byte_position_lw, 1009151980);
|
||||
assert.equal(first.stream_byte_position_uw, 0);
|
||||
assert.equal(first.stream_id, 1);
|
||||
assert.equal(first.total_slices, 1);
|
||||
assert.equal(first.slice_index, 0);
|
||||
assert.equal(first.header1_size, 14);
|
||||
assert.equal(first.header2_size, 10);
|
||||
assert.equal(first.reserved2.length, 2);
|
||||
assert.equal(first.advertised_size, 10180);
|
||||
assert.equal(first.reserved3.length, 12);
|
||||
assert.equal(first.payload.length, 10180);
|
||||
},
|
||||
|
||||
|
||||
'emits error on bad signature': function() {
|
||||
var buffer = new Buffer(68);
|
||||
|
||||
// should be PaVE, not fuck
|
||||
buffer.write('fuck');
|
||||
|
||||
var errorStub = sinon.stub();
|
||||
this.parser.on('error', errorStub);
|
||||
|
||||
this.parser.write(buffer);
|
||||
|
||||
assert.equal(errorStub.callCount, 1);
|
||||
assert.equal(/signature/i.test(errorStub.getCall(0).args[0]), true);
|
||||
},
|
||||
|
||||
'end method exists, but does nothing': function() {
|
||||
this.parser.end();
|
||||
},
|
||||
});
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
var common = require('../../common');
|
||||
var assert = require('assert');
|
||||
var test = require('utest');
|
||||
var sinon = require('sinon');
|
||||
var PngEncoder = require(common.lib + '/video/PngEncoder');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
|
||||
test('PngEncoder', {
|
||||
before: function() {
|
||||
this.fakeFfmpeg = new EventEmitter();
|
||||
|
||||
this.fakeFfmpeg.stdin = new EventEmitter();
|
||||
this.fakeFfmpeg.stdin.write = sinon.stub();
|
||||
this.fakeFfmpeg.stdin.end = sinon.stub();
|
||||
|
||||
this.fakeFfmpeg.stdout = {pipe: sinon.spy()};
|
||||
this.fakeFfmpeg.stderr = {pipe: sinon.spy()};
|
||||
|
||||
this.fakeSpawn = sinon.stub();
|
||||
this.fakeSpawn.returns(this.fakeFfmpeg);
|
||||
|
||||
this.fakePngSplitter = new EventEmitter();
|
||||
|
||||
this.fakeFrameRate = 23;
|
||||
|
||||
this.fakeBuffer1 = new Buffer('123');
|
||||
this.fakeBuffer2 = new Buffer('456');
|
||||
|
||||
this.fakeLog = {};
|
||||
|
||||
this.encoder = new PngEncoder({
|
||||
spawn : this.fakeSpawn,
|
||||
frameRate : this.fakeFrameRate,
|
||||
pngSplitter : this.fakePngSplitter,
|
||||
log : this.fakeLog,
|
||||
});
|
||||
},
|
||||
|
||||
'is a writable stream': function() {
|
||||
assert.equal(this.encoder.writable, true);
|
||||
assert.equal(typeof this.encoder.write, 'function');
|
||||
},
|
||||
|
||||
'is a readable stream': function() {
|
||||
assert.equal(this.encoder.readable, true);
|
||||
assert.equal(typeof this.encoder.pipe, 'function');
|
||||
},
|
||||
|
||||
'first write() spawns ffmpeg': function() {
|
||||
this.encoder.write(new Buffer('foo'));
|
||||
|
||||
assert.equal(this.fakeSpawn.callCount, 1);
|
||||
assert.equal(this.fakeSpawn.getCall(0).args[0], 'ffmpeg');
|
||||
|
||||
// Another write does not spawn another ffmpeg
|
||||
this.encoder.write(new Buffer('bar'));
|
||||
assert.equal(this.fakeSpawn.callCount, 1);
|
||||
},
|
||||
|
||||
'write() spawn ffmpeg with the right arguments': function() {
|
||||
this.encoder.write(new Buffer('foo'));
|
||||
|
||||
var args = this.fakeSpawn.getCall(0).args[1];
|
||||
|
||||
// Read from stdin
|
||||
var input = args.indexOf('-i');
|
||||
assert.equal(args[input + 1], '-');
|
||||
|
||||
// Use the image2pipe format
|
||||
var format = args.indexOf('-f');
|
||||
assert.equal(args[format + 1], 'image2pipe');
|
||||
|
||||
// Use the png video codec
|
||||
var vcodec = args.indexOf('-vcodec');
|
||||
assert.equal(args[vcodec + 1], 'png');
|
||||
|
||||
// Sets the right framerate
|
||||
var frameRate = args.indexOf('-r');
|
||||
assert.equal(args[frameRate + 1], this.fakeFrameRate);
|
||||
|
||||
// Pipe to stdout
|
||||
assert.equal(args[args.length - 1], '-');
|
||||
},
|
||||
|
||||
'write() pipes ffmpeg.stdout into PngSplitter': function() {
|
||||
this.encoder.write(new Buffer('foo'));
|
||||
|
||||
var stdoutPipe = this.fakeFfmpeg.stdout.pipe;
|
||||
assert.equal(stdoutPipe.callCount, 1);
|
||||
assert.strictEqual(stdoutPipe.getCall(0).args[0], this.fakePngSplitter);
|
||||
},
|
||||
|
||||
'proxies all pngSplitter "data"': function() {
|
||||
var dataSpy = sinon.spy();
|
||||
this.encoder.on('data', dataSpy);
|
||||
|
||||
this.encoder.write(new Buffer('foo'));
|
||||
this.fakePngSplitter.emit('data', this.fakeBuffer1);
|
||||
|
||||
assert.equal(dataSpy.callCount, 1);
|
||||
assert.strictEqual(dataSpy.getCall(0).args[0], this.fakeBuffer1);
|
||||
|
||||
this.fakePngSplitter.emit('data', this.fakeBuffer2);
|
||||
assert.equal(dataSpy.callCount, 2);
|
||||
assert.strictEqual(dataSpy.getCall(1).args[0], this.fakeBuffer2);
|
||||
},
|
||||
|
||||
'handles ffmpeg spawn error': function() {
|
||||
var errorSpy = sinon.spy();
|
||||
this.encoder.on('error', errorSpy);
|
||||
|
||||
this.encoder.write(new Buffer('foo'));
|
||||
|
||||
// simulate ffmpeg not spawning correctly
|
||||
var error = new Error('ENOENT');
|
||||
error.code = 'ENOENT';
|
||||
this.fakeFfmpeg.stdin.emit('error', new Error('EPIPE'));
|
||||
this.fakeFfmpeg.emit('error', error);
|
||||
|
||||
assert.equal(errorSpy.callCount, 1);
|
||||
assert.equal(/ffmpeg.*not found/i.test(errorSpy.getCall(0).args[0]), true);
|
||||
},
|
||||
|
||||
'handles ffmpeg not existing': function() {
|
||||
var errorSpy = sinon.spy();
|
||||
this.encoder.on('error', errorSpy);
|
||||
|
||||
this.encoder.write(new Buffer('foo'));
|
||||
|
||||
// simulate ffmpeg not existing
|
||||
this.fakeFfmpeg.stdin.emit('error', new Error('EPIPE'));
|
||||
this.fakeFfmpeg.emit('exit', 127);
|
||||
|
||||
assert.equal(errorSpy.callCount, 1);
|
||||
assert.equal(/ffmpeg.*not found/i.test(errorSpy.getCall(0).args[0]), true);
|
||||
},
|
||||
|
||||
'handles ffmpeg exit code > 0': function() {
|
||||
var errorSpy = sinon.spy();
|
||||
this.encoder.on('error', errorSpy);
|
||||
|
||||
this.encoder.write(new Buffer('foo'));
|
||||
|
||||
// simulate an ffmpeg error
|
||||
this.fakeFfmpeg.emit('exit', 1);
|
||||
|
||||
assert.equal(errorSpy.callCount, 1);
|
||||
assert.equal(/ffmpeg.*error/i.test(errorSpy.getCall(0).args[0]), true);
|
||||
},
|
||||
|
||||
'handles expected ffmpeg shutdown': function() {
|
||||
var endSpy = sinon.spy();
|
||||
this.encoder.on('end', endSpy);
|
||||
|
||||
this.encoder.write(new Buffer('foo'));
|
||||
this.encoder.end();
|
||||
this.fakeFfmpeg.emit('exit', 0);
|
||||
|
||||
assert.equal(endSpy.callCount, 1);
|
||||
},
|
||||
|
||||
'handles unexpected ffmpeg shutdown with exit code 0': function() {
|
||||
var errorSpy = sinon.spy();
|
||||
this.encoder.on('error', errorSpy);
|
||||
|
||||
this.encoder.write(new Buffer('foo'));
|
||||
this.fakeFfmpeg.emit('exit', 0);
|
||||
|
||||
assert.equal(errorSpy.callCount, 1);
|
||||
assert.equal(/unexpected.*ffmpeg/i.test(errorSpy.getCall(0).args[0].message), true);
|
||||
},
|
||||
|
||||
'write() passes all data into ffmpeg.stdin': function() {
|
||||
this.encoder.write(this.fakeBuffer1);
|
||||
|
||||
var stdin = this.fakeFfmpeg.stdin;
|
||||
assert.equal(stdin.write.callCount, 1);
|
||||
assert.strictEqual(stdin.write.getCall(0).args[0], this.fakeBuffer1);
|
||||
|
||||
this.encoder.write(this.fakeBuffer2);
|
||||
assert.equal(stdin.write.callCount, 2);
|
||||
assert.strictEqual(stdin.write.getCall(1).args[0], this.fakeBuffer2);
|
||||
},
|
||||
|
||||
'write() handles ffmpeg backpressure': function() {
|
||||
this.fakeFfmpeg.stdin.write.returns(true);
|
||||
var r = this.encoder.write(new Buffer('abc'));
|
||||
assert.equal(r, true);
|
||||
|
||||
this.fakeFfmpeg.stdin.write.returns(false);
|
||||
r = this.encoder.write(new Buffer('abc'));
|
||||
assert.equal(r, false);
|
||||
|
||||
var drainCalled = false;
|
||||
this.encoder.on('drain', function () {
|
||||
drainCalled = true;
|
||||
});
|
||||
this.fakeFfmpeg.stdin.emit('drain');
|
||||
assert.ok(drainCalled);
|
||||
},
|
||||
|
||||
'write() pipes ffmpeg stderr to log': function() {
|
||||
this.encoder.write(new Buffer('abc'));
|
||||
|
||||
var stderrPipe = this.fakeFfmpeg.stderr.pipe;
|
||||
assert.equal(stderrPipe.callCount, 1);
|
||||
assert.strictEqual(stderrPipe.getCall(0).args[0], this.fakeLog);
|
||||
},
|
||||
|
||||
'write() does not pipe to log if not set': function() {
|
||||
this.encoder = new PngEncoder({spawn: this.fakeSpawn});
|
||||
|
||||
this.encoder.write(new Buffer('abc'));
|
||||
assert.equal(this.fakeFfmpeg.stderr.pipe.callCount, 0);
|
||||
},
|
||||
|
||||
'end() closes ffmpeg.stdin': function() {
|
||||
this.encoder.write(new Buffer('abc'));
|
||||
this.encoder.end();
|
||||
|
||||
assert.equal(this.fakeFfmpeg.stdin.end.callCount, 1);
|
||||
},
|
||||
|
||||
'end() does not do anything if there is no ffmpeg yet': function() {
|
||||
this.encoder.end();
|
||||
},
|
||||
});
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
var common = require('../../common');
|
||||
var assert = require('assert');
|
||||
var test = require('utest');
|
||||
var sinon = require('sinon');
|
||||
var PngStream = require(common.lib + '/video/PngStream');
|
||||
var TcpVideoStream = PngStream.TcpVideoStream;
|
||||
var PngEncoder = PngStream.PngEncoder;
|
||||
|
||||
test('PngStream', {
|
||||
before: function() {
|
||||
PngStream.TcpVideoStream = sinon.stub();
|
||||
PngStream.PngEncoder = sinon.stub();
|
||||
|
||||
this.tcpVideoStream = new TcpVideoStream();
|
||||
this.pngEncoder = new PngEncoder();
|
||||
|
||||
PngStream.TcpVideoStream.returns(this.tcpVideoStream);
|
||||
PngStream.PngEncoder.returns(this.pngEncoder);
|
||||
|
||||
this.fakeOptions = {};
|
||||
this.stream = new PngStream(this.fakeOptions);
|
||||
},
|
||||
|
||||
'is a readable stream': function() {
|
||||
assert.equal(this.stream.readable, true);
|
||||
assert.equal(typeof this.stream.pipe, 'function');
|
||||
},
|
||||
|
||||
'resume connects new TcpVideoStream': function() {
|
||||
sinon.stub(this.tcpVideoStream, 'connect');
|
||||
|
||||
this.stream.resume();
|
||||
|
||||
assert.equal(PngStream.TcpVideoStream.callCount, 1);
|
||||
assert.strictEqual(PngStream.TcpVideoStream.getCall(0).args[0], this.fakeOptions);
|
||||
assert.equal(this.tcpVideoStream.connect.callCount, 1);
|
||||
},
|
||||
|
||||
'resume passes TcpVideoStream data through PngEncoder': function() {
|
||||
sinon.stub(this.tcpVideoStream, 'connect');
|
||||
|
||||
this.stream.resume();
|
||||
|
||||
assert.equal(PngStream.TcpVideoStream.callCount, 1);
|
||||
assert.equal(PngStream.PngEncoder.callCount, 1);
|
||||
|
||||
assert.strictEqual(PngStream.TcpVideoStream.getCall(0).args[0], this.fakeOptions);
|
||||
assert.strictEqual(PngStream.PngEncoder.getCall(0).args[0], this.fakeOptions);
|
||||
|
||||
sinon.stub(this.pngEncoder, 'write');
|
||||
|
||||
var fakeData = new Buffer([1]);
|
||||
this.tcpVideoStream.emit('data', fakeData);
|
||||
|
||||
assert.equal(this.pngEncoder.write.callCount, 1);
|
||||
assert.strictEqual(this.pngEncoder.write.getCall(0).args[0], fakeData);
|
||||
|
||||
var dataStub = sinon.stub();
|
||||
this.stream.on('data', dataStub);
|
||||
|
||||
var fakePng = new Buffer([2]);
|
||||
this.pngEncoder.emit('data', fakePng);
|
||||
|
||||
assert.equal(dataStub.callCount, 1);
|
||||
assert.strictEqual(dataStub.getCall(0).args[0], fakePng);
|
||||
},
|
||||
|
||||
'TcpVideoStream stream errors cause a new tcpVideoStream to be created': function() {
|
||||
sinon.stub(this.tcpVideoStream, 'connect');
|
||||
sinon.stub(this.pngEncoder, 'end');
|
||||
|
||||
this.stream.resume();
|
||||
|
||||
var tcpVideoStream2 = new TcpVideoStream();
|
||||
sinon.stub(tcpVideoStream2, 'connect');
|
||||
PngStream.TcpVideoStream.returns(tcpVideoStream2);
|
||||
|
||||
var fakeErr = new Error('bad shit');
|
||||
this.tcpVideoStream.emit('error', fakeErr);
|
||||
|
||||
assert.equal(tcpVideoStream2.connect.callCount, 1);
|
||||
assert.equal(this.pngEncoder.end.callCount, 1);
|
||||
},
|
||||
|
||||
'emits "error" events on tcpVideoStream errors, if there is a listener': function() {
|
||||
sinon.stub(this.tcpVideoStream, 'connect');
|
||||
sinon.stub(this.pngEncoder, 'end');
|
||||
|
||||
var errStub = sinon.stub();
|
||||
this.stream.on('error', errStub);
|
||||
this.stream.resume();
|
||||
|
||||
var fakeErr = new Error('bad shit');
|
||||
this.tcpVideoStream.emit('error', fakeErr);
|
||||
|
||||
assert.equal(errStub.callCount, 1);
|
||||
assert.strictEqual(errStub.getCall(0).args[0], fakeErr);
|
||||
},
|
||||
|
||||
'emits "error" events on pngEncoder errors, if there is a listener': function() {
|
||||
sinon.stub(this.tcpVideoStream, 'connect');
|
||||
sinon.stub(this.pngEncoder, 'end');
|
||||
|
||||
var errStub = sinon.stub();
|
||||
this.stream.on('error', errStub);
|
||||
this.stream.resume();
|
||||
|
||||
var fakeErr = new Error('bad shit');
|
||||
this.pngEncoder.emit('error', fakeErr);
|
||||
|
||||
assert.equal(errStub.callCount, 1);
|
||||
assert.strictEqual(errStub.getCall(0).args[0], fakeErr);
|
||||
},
|
||||
});
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
var common = require('../../common');
|
||||
var assert = require('assert');
|
||||
var test = require('utest');
|
||||
var sinon = require('sinon');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var TcpVideoStream = require(common.lib + '/video/TcpVideoStream');
|
||||
|
||||
test('TcpVideoStream', {
|
||||
before: function() {
|
||||
this.fakeSocket = new EventEmitter();
|
||||
this.fakeSocket.connect = sinon.spy();
|
||||
this.fakeSocket.setTimeout = sinon.spy();
|
||||
this.fakeSocket.end = sinon.spy();
|
||||
this.fakeSocket.destroy = sinon.spy();
|
||||
|
||||
this.fakePort = 93321;
|
||||
this.fakeIp = '255.0.124.24';
|
||||
this.fakeTimeout = 23 * 1000;
|
||||
|
||||
this.stream = new TcpVideoStream({
|
||||
ip : this.fakeIp,
|
||||
port : this.fakePort,
|
||||
timeout : this.fakeTimeout,
|
||||
socket : this.fakeSocket
|
||||
});
|
||||
},
|
||||
|
||||
'is a readable stream': function() {
|
||||
assert.equal(this.stream.readable, true);
|
||||
assert.equal(typeof this.stream.pipe, 'function');
|
||||
},
|
||||
|
||||
'connect() calls socket.connect': function() {
|
||||
this.stream.connect();
|
||||
assert.equal(this.fakeSocket.connect.callCount, 1);
|
||||
|
||||
var args = this.fakeSocket.connect.getCall(0).args;
|
||||
assert.equal(args.shift(), this.fakePort);
|
||||
assert.equal(args.shift(), this.fakeIp);
|
||||
},
|
||||
|
||||
'connect() calls socket.setTimeout': function() {
|
||||
this.stream.connect();
|
||||
|
||||
var setTimeout = this.fakeSocket.setTimeout;
|
||||
assert.equal(setTimeout.callCount, 1);
|
||||
assert.equal(setTimeout.getCall(0).args[0], this.fakeTimeout);
|
||||
},
|
||||
|
||||
'socket "timeout" events trigger "error", "close" and destroy()': function() {
|
||||
var errorSpy = sinon.spy();
|
||||
var closeSpy = sinon.spy();
|
||||
this.stream.on('error', errorSpy);
|
||||
this.stream.on('close', closeSpy);
|
||||
|
||||
this.stream.connect();
|
||||
this.fakeSocket.emit('timeout');
|
||||
|
||||
assert.equal(errorSpy.callCount, 1);
|
||||
var err = errorSpy.getCall(0).args[0];
|
||||
assert.equal(err instanceof Error, true);
|
||||
assert.equal(/timeout/i.test(err), true);
|
||||
|
||||
assert.equal(closeSpy.callCount, 1);
|
||||
assert.strictEqual(closeSpy.getCall(0).args[0], err);
|
||||
|
||||
assert.equal(this.fakeSocket.destroy.callCount, 1);
|
||||
},
|
||||
|
||||
'connect() calls back on success': function() {
|
||||
var fakeCb = sinon.spy();
|
||||
this.stream.connect(fakeCb);
|
||||
|
||||
assert.equal(fakeCb.callCount, 0);
|
||||
|
||||
this.fakeSocket.emit('connect');
|
||||
assert.equal(fakeCb.callCount, 1);
|
||||
assert.equal(fakeCb.getCall(0).args[0], null);
|
||||
},
|
||||
|
||||
'connect() calls back on error': function() {
|
||||
var fakeCb = sinon.spy();
|
||||
this.stream.connect(fakeCb);
|
||||
|
||||
assert.equal(fakeCb.callCount, 0);
|
||||
|
||||
var fakeErr = new Error('something bad');
|
||||
this.fakeSocket.emit('error', fakeErr);
|
||||
assert.equal(fakeCb.callCount, 1);
|
||||
assert.strictEqual(fakeCb.getCall(0).args[0], fakeErr);
|
||||
},
|
||||
|
||||
'connect() callback is optional': function() {
|
||||
this.stream.connect();
|
||||
this.fakeSocket.emit('connect');
|
||||
},
|
||||
|
||||
'proxies "data" events': function() {
|
||||
var fakeData1 = new Buffer('abc');
|
||||
var fakeData2 = new Buffer('efg');
|
||||
|
||||
var dataSpy = sinon.spy();
|
||||
this.stream.on('data', dataSpy);
|
||||
this.stream.connect();
|
||||
|
||||
this.fakeSocket.emit('data', fakeData1);
|
||||
assert.strictEqual(dataSpy.callCount, 1);
|
||||
assert.strictEqual(dataSpy.getCall(0).args[0], fakeData1);
|
||||
|
||||
this.fakeSocket.emit('data', fakeData2);
|
||||
assert.strictEqual(dataSpy.callCount, 2);
|
||||
assert.strictEqual(dataSpy.getCall(1).args[0], fakeData2);
|
||||
},
|
||||
|
||||
'end() ends the socket gracefully': function() {
|
||||
this.stream.end();
|
||||
assert.equal(this.fakeSocket.end.callCount, 1);
|
||||
},
|
||||
|
||||
'emits an "error" on unexpected server FIN': function() {
|
||||
var errorSpy = sinon.spy();
|
||||
var closeSpy = sinon.spy();
|
||||
|
||||
this.stream.on('error', errorSpy);
|
||||
this.stream.on('close', closeSpy);
|
||||
|
||||
this.stream.connect();
|
||||
|
||||
this.fakeSocket.emit('end');
|
||||
|
||||
assert.equal(errorSpy.callCount, 1);
|
||||
var err = errorSpy.getCall(0).args[0];
|
||||
assert.equal(err instanceof Error, true);
|
||||
assert.equal(/FIN/.test(err.message), true);
|
||||
|
||||
assert.equal(closeSpy.callCount, 1);
|
||||
assert.strictEqual(closeSpy.getCall(0).args[0], err);
|
||||
},
|
||||
|
||||
'proxies close event when expecting it': function() {
|
||||
var closeSpy = sinon.spy();
|
||||
this.stream.on('close', closeSpy);
|
||||
|
||||
this.stream.connect();
|
||||
this.stream.end();
|
||||
this.fakeSocket.emit('end');
|
||||
|
||||
assert.equal(closeSpy.callCount, 1);
|
||||
assert.equal(closeSpy.getCall(0).args[0], null);
|
||||
},
|
||||
});
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
support
|
||||
test
|
||||
examples
|
||||
*.sock
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
0.9.1 / 2013-04-29
|
||||
==================
|
||||
|
||||
* Update ECT version
|
||||
* Added support for Handlebars helpers with test.
|
||||
* Invalidates built-in dust cache if caching disabled
|
||||
|
||||
0.9.0 / 2013-03-28
|
||||
==================
|
||||
|
||||
* dust-helpers support, latest version of dust
|
||||
* Re-add doT - global leaks fixed
|
||||
* improving templayed support
|
||||
|
||||
0.8.0 / 2013-01-23
|
||||
==================
|
||||
|
||||
* add templayed support
|
||||
* add `then-jade` as an alternative to `jade`
|
||||
|
||||
0.7.0 / 2012-12-28
|
||||
==================
|
||||
|
||||
* add atpl support
|
||||
|
||||
0.6.0 2012-12-22
|
||||
==================
|
||||
|
||||
* add partials support
|
||||
* add support for toffee templates
|
||||
* remove dot it still leaks and the author has not fixed it
|
||||
|
||||
0.5.0 / 2012-10-29
|
||||
==================
|
||||
|
||||
* add `mote` support
|
||||
* add support to `dust` partials
|
||||
* add support for `ECT`
|
||||
* add support for rendering without file
|
||||
* add support for `JUST`
|
||||
* improve Haml-Coffee caching.
|
||||
|
||||
0.4.0 / 2012-07-30
|
||||
==================
|
||||
|
||||
* add doT support [sannis]
|
||||
* add mustache support [ForbesLindesay]
|
||||
* add walrus support [kagd]
|
||||
|
||||
0.3.1 / 2012-06-28
|
||||
==================
|
||||
|
||||
* add QEJS support
|
||||
* add underscore support
|
||||
* change whiskers to use pre-defined `.__express`
|
||||
* remove engines. Closes #37
|
||||
* remove kernel, cannot comply with our caching
|
||||
|
||||
0.3.0 / 2012-04-18
|
||||
==================
|
||||
|
||||
* Added partials loading for whiskers [gsf]
|
||||
* Added dustjs-linkedin support
|
||||
|
||||
0.2.0 / 2012-04-04
|
||||
==================
|
||||
|
||||
* Added support for dust [fatjonny]
|
||||
* Added handlebars support [jstewmon]
|
||||
|
||||
0.1.0 / 2012-01-03
|
||||
==================
|
||||
|
||||
* Added support for several more engines
|
||||
|
||||
0.0.1 / 2010-01-03
|
||||
==================
|
||||
|
||||
* Initial release
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
|
||||
REPORTER = spec
|
||||
|
||||
test:
|
||||
@./node_modules/.bin/mocha \
|
||||
--slow 30 \
|
||||
--reporter $(REPORTER)
|
||||
|
||||
.PHONY: test
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
# Consolidate.js
|
||||
|
||||
Template engine consolidation library.
|
||||
|
||||
## Installation
|
||||
|
||||
$ npm install consolidate
|
||||
|
||||
## Supported template engines
|
||||
|
||||
- [atpl](https://github.com/soywiz/atpl.js)
|
||||
- [dust](https://github.com/akdubya/dustjs) [(website)](http://akdubya.github.com/dustjs/)
|
||||
- [eco](https://github.com/sstephenson/eco)
|
||||
- [ect](https://github.com/baryshev/ect) [(website)](http://ectjs.com/)
|
||||
- [ejs](https://github.com/visionmedia/ejs)
|
||||
- [haml](https://github.com/visionmedia/haml.js) [(website)](http://haml-lang.com/)
|
||||
- [haml-coffee](https://github.com/9elements/haml-coffee) [(website)](http://haml-lang.com/)
|
||||
- [handlebars](https://github.com/wycats/handlebars.js/) [(website)](http://handlebarsjs.com/)
|
||||
- [hogan](https://github.com/twitter/hogan.js) [(website)](http://twitter.github.com/hogan.js/)
|
||||
- [jade](https://github.com/visionmedia/jade) [(website)](http://jade-lang.com/)
|
||||
- [jazz](https://github.com/shinetech/jazz)
|
||||
- [jqtpl](https://github.com/kof/node-jqtpl) [(website)](http://api.jquery.com/category/plugins/templates/)
|
||||
- [JUST](https://github.com/baryshev/just)
|
||||
- [liquor](https://github.com/chjj/liquor)
|
||||
- [mustache](https://github.com/janl/mustache.js)
|
||||
- [QEJS](https://github.com/jepso/QEJS)
|
||||
- [swig](https://github.com/paularmstrong/swig) [(website)](http://paularmstrong.github.com/swig/)
|
||||
- [templayed](http://archan937.github.com/templayed.js/)
|
||||
- [toffee](https://github.com/malgorithms/toffee)
|
||||
- [underscore](https://github.com/documentcloud/underscore) [(website)](http://documentcloud.github.com/underscore/)
|
||||
- [walrus](https://github.com/jeremyruppel/walrus) [(website)](http://documentup.com/jeremyruppel/walrus/)
|
||||
- [whiskers](https://github.com/gsf/whiskers.js/tree/)
|
||||
|
||||
__NOTE__: you must still install the engines you wish to use, add them to your package.json dependencies.
|
||||
|
||||
## API
|
||||
|
||||
All templates supported by this library may be rendered using the signature `(path[, locals], callback)` as shown below, which happens to be the signature that Express 3.x supports so any of these engines may be used within Express.
|
||||
|
||||
__NOTE__: All this example code uses cons.swig for the swig template engine. Replace swig with whatever templating you are using. For exmaple, use cons.hogan for hogan.js, cons.jade for jade, etc. `console.log(cons)` for the full list of identifiers.
|
||||
|
||||
```js
|
||||
var cons = require('consolidate');
|
||||
cons.swig('views/page.html', { user: 'tobi' }, function(err, html){
|
||||
if (err) throw err;
|
||||
console.log(html);
|
||||
});
|
||||
```
|
||||
|
||||
Or without options / local variables:
|
||||
|
||||
```js
|
||||
var cons = require('consolidate');
|
||||
cons.swig('views/page.html', function(err, html){
|
||||
if (err) throw err;
|
||||
console.log(html);
|
||||
});
|
||||
```
|
||||
|
||||
To dynamically pass the engine, simply use the subscript operator and a variable:
|
||||
|
||||
```js
|
||||
var cons = require('consolidate')
|
||||
, name = 'swig';
|
||||
|
||||
cons[name]('views/page.html', { user: 'tobi' }, function(err, html){
|
||||
if (err) throw err;
|
||||
console.log(html);
|
||||
});
|
||||
```
|
||||
|
||||
## Caching
|
||||
|
||||
To enable caching simply pass `{ cache: true }`. Engines _may_ use this option to cache things reading the file contents, compiled `Function`s etc. Engines which do _not_ support this may simply ignore it. All engines that consolidate.js implements I/O for will cache the file contents, ideal for production environments.
|
||||
|
||||
```js
|
||||
var cons = require('consolidate');
|
||||
cons.swig('views/page.html', { user: 'tobi' }, function(err, html){
|
||||
if (err) throw err;
|
||||
console.log(html);
|
||||
});
|
||||
```
|
||||
|
||||
## Express 3.x example
|
||||
|
||||
```js
|
||||
var express = require('express')
|
||||
, cons = require('consolidate')
|
||||
, app = express();
|
||||
|
||||
// assign the swig engine to .html files
|
||||
app.engine('html', cons.swig);
|
||||
|
||||
// set .html as the default extension
|
||||
app.set('view engine', 'html');
|
||||
app.set('views', __dirname + '/views');
|
||||
|
||||
var users = [];
|
||||
users.push({ name: 'tobi' });
|
||||
users.push({ name: 'loki' });
|
||||
users.push({ name: 'jane' });
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.render('index', {
|
||||
title: 'Consolidate.js'
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/users', function(req, res){
|
||||
res.render('users', {
|
||||
title: 'Users',
|
||||
users: users
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express server listening on port 3000');
|
||||
```
|
||||
|
||||
## Running tests
|
||||
|
||||
Install dev deps:
|
||||
|
||||
$ npm install -d
|
||||
|
||||
Run the tests:
|
||||
|
||||
$ make test
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+1
@@ -0,0 +1 @@
|
||||
module.exports = require('./lib/consolidate');
|
||||
+704
@@ -0,0 +1,704 @@
|
||||
/*!
|
||||
* consolidate
|
||||
* Copyright(c) 2012 TJ Holowaychuk <tj@vision-media.ca>
|
||||
* MIT Licensed
|
||||
*
|
||||
* Engines which do not support caching of their file contents
|
||||
* should use the `read()` function defined in consolidate.js
|
||||
* On top of this, when an engine compiles to a `Function`,
|
||||
* these functions should either be cached within consolidate.js
|
||||
* or the engine itself via `options.cache`. This will allow
|
||||
* users and frameworks to pass `options.cache = true` for
|
||||
* `NODE_ENV=production`, however edit the file(s) without
|
||||
* re-loading the application in development.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var fs = require('fs')
|
||||
, path = require('path')
|
||||
, join = path.join
|
||||
, extname = path.extname
|
||||
, dirname = path.dirname;
|
||||
|
||||
var readCache = {};
|
||||
|
||||
/**
|
||||
* Require cache.
|
||||
*/
|
||||
|
||||
var cacheStore = {};
|
||||
|
||||
/**
|
||||
* Require cache.
|
||||
*/
|
||||
|
||||
var requires = {};
|
||||
|
||||
/**
|
||||
* Clear the cache.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.clearCache = function(){
|
||||
cacheStore = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Conditionally cache `compiled` template based
|
||||
* on the `options` filename and `.cache` boolean.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Function} compiled
|
||||
* @return {Function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function cache(options, compiled) {
|
||||
// cachable
|
||||
if (compiled && options.filename && options.cache) {
|
||||
delete readCache[options.filename];
|
||||
cacheStore[options.filename] = compiled;
|
||||
return compiled;
|
||||
}
|
||||
|
||||
// check cache
|
||||
if (options.filename && options.cache) {
|
||||
return cacheStore[options.filename];
|
||||
}
|
||||
|
||||
return compiled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read `path` with `options` with
|
||||
* callback `(err, str)`. When `options.cache`
|
||||
* is true the template string will be cached.
|
||||
*
|
||||
* @param {String} options
|
||||
* @param {Function} fn
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function read(path, options, fn) {
|
||||
var str = readCache[path];
|
||||
var cached = options.cache && str && 'string' == typeof str;
|
||||
|
||||
// cached (only if cached is a string and not a compiled template function)
|
||||
if (cached) return fn(null, str);
|
||||
|
||||
// read
|
||||
fs.readFile(path, 'utf8', function(err, str){
|
||||
if (err) return fn(err);
|
||||
if (options.cache) readCache[path] = str;
|
||||
fn(null, str);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Read `path` with `options` with
|
||||
* callback `(err, str)`. When `options.cache`
|
||||
* is true the partial string will be cached.
|
||||
*
|
||||
* @param {String} options
|
||||
* @param {Function} fn
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function readPartials(path, options, fn) {
|
||||
if (!options.partials) return fn();
|
||||
var partials = options.partials;
|
||||
var keys = Object.keys(partials);
|
||||
|
||||
function next(index) {
|
||||
if (index == keys.length) return fn(null);
|
||||
var key = keys[index];
|
||||
var file = join(dirname(path), partials[key] + extname(path));
|
||||
read(file, options, function(err, str){
|
||||
if (err) return fn(err);
|
||||
options.partials[key] = str;
|
||||
next(++index);
|
||||
});
|
||||
}
|
||||
|
||||
next(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* fromStringRenderer
|
||||
*/
|
||||
|
||||
function fromStringRenderer(name) {
|
||||
return function(path, options, fn){
|
||||
options.filename = path;
|
||||
readPartials(path, options, function (err) {
|
||||
if (err) return fn(err);
|
||||
if (cache(options)) {
|
||||
exports[name].render('', options, fn);
|
||||
} else {
|
||||
read(path, options, function(err, str){
|
||||
if (err) return fn(err);
|
||||
exports[name].render(str, options, fn);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Jade support.
|
||||
*/
|
||||
|
||||
exports.jade = function(path, options, fn){
|
||||
var engine = requires.jade;
|
||||
if (!engine) {
|
||||
try {
|
||||
engine = requires.jade = require('jade');
|
||||
} catch (err) {
|
||||
engine = requires.jade = require('then-jade');
|
||||
}
|
||||
}
|
||||
engine.renderFile(path, options, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Jade string support.
|
||||
*/
|
||||
|
||||
exports.jade.render = function(str, options, fn){
|
||||
var engine = requires.jade;
|
||||
if (!engine) {
|
||||
try {
|
||||
engine = requires.jade = require('jade');
|
||||
} catch (err) {
|
||||
engine = requires.jade = require('then-jade');
|
||||
}
|
||||
}
|
||||
engine.render(str, options, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Dust support.
|
||||
*/
|
||||
|
||||
exports.dust = fromStringRenderer('dust');
|
||||
|
||||
/**
|
||||
* Dust string support.
|
||||
*/
|
||||
|
||||
exports.dust.render = function(str, options, fn){
|
||||
var engine = requires.dust;
|
||||
if (!engine) {
|
||||
try {
|
||||
engine = requires.dust = require('dust');
|
||||
} catch (err) {
|
||||
try {
|
||||
engine = requires.dust = require('dustjs-helpers');
|
||||
} catch (err) {
|
||||
engine = requires.dust = require('dustjs-linkedin');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ext = 'dust'
|
||||
, views = '.';
|
||||
|
||||
if (options) {
|
||||
if (options.ext) ext = options.ext;
|
||||
if (options.views) views = options.views;
|
||||
if (options.settings && options.settings.views) views = options.settings.views;
|
||||
}
|
||||
if (!options || (options && !options.cache)) engine.cache = {};
|
||||
|
||||
engine.onLoad = function(path, callback){
|
||||
if ('' == extname(path)) path += '.' + ext;
|
||||
if ('/' !== path[0]) path = views + '/' + path;
|
||||
read(path, options, callback);
|
||||
};
|
||||
|
||||
try {
|
||||
var tmpl = cache(options) || cache(options, engine.compileFn(str));
|
||||
tmpl(options, fn);
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Swig support.
|
||||
*/
|
||||
|
||||
exports.swig = fromStringRenderer('swig');
|
||||
|
||||
/**
|
||||
* Swig string support.
|
||||
*/
|
||||
|
||||
exports.swig.render = function(str, options, fn){
|
||||
var engine = requires.swig || (requires.swig = require('swig'));
|
||||
try {
|
||||
var tmpl = cache(options) || cache(options, engine.compile(str, options));
|
||||
fn(null, tmpl(options));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Atpl support.
|
||||
*/
|
||||
|
||||
exports.atpl = fromStringRenderer('atpl');
|
||||
|
||||
/**
|
||||
* Atpl string support.
|
||||
*/
|
||||
|
||||
exports.atpl.render = function(str, options, fn){
|
||||
var engine = requires.atpl || (requires.atpl = require('atpl'));
|
||||
try {
|
||||
var tmpl = cache(options) || cache(options, engine.compile(str, options));
|
||||
fn(null, tmpl(options));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Liquor support,
|
||||
*/
|
||||
|
||||
exports.liquor = fromStringRenderer('liquor');
|
||||
|
||||
/**
|
||||
* Liquor string support.
|
||||
*/
|
||||
|
||||
exports.liquor.render = function(str, options, fn){
|
||||
var engine = requires.liquor || (requires.liquor = require('liquor'));
|
||||
try {
|
||||
var tmpl = cache(options) || cache(options, engine.compile(str, options));
|
||||
fn(null, tmpl(options));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* EJS support.
|
||||
*/
|
||||
|
||||
exports.ejs = fromStringRenderer('ejs');
|
||||
|
||||
/**
|
||||
* EJS string support.
|
||||
*/
|
||||
|
||||
exports.ejs.render = function(str, options, fn){
|
||||
var engine = requires.ejs || (requires.ejs = require('ejs'));
|
||||
try {
|
||||
var tmpl = cache(options) || cache(options, engine.compile(str, options));
|
||||
fn(null, tmpl(options));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Eco support.
|
||||
*/
|
||||
|
||||
exports.eco = fromStringRenderer('eco');
|
||||
|
||||
/**
|
||||
* Eco string support.
|
||||
*/
|
||||
|
||||
exports.eco.render = function(str, options, fn){
|
||||
var engine = requires.eco || (requires.eco = require('eco'));
|
||||
try {
|
||||
fn(null, engine.render(str, options));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Jazz support.
|
||||
*/
|
||||
|
||||
exports.jazz = fromStringRenderer('jazz');
|
||||
|
||||
/**
|
||||
* Jazz string support.
|
||||
*/
|
||||
|
||||
exports.jazz.render = function(str, options, fn){
|
||||
var engine = requires.jazz || (requires.jazz = require('jazz'));
|
||||
try {
|
||||
var tmpl = cache(options) || cache(options, engine.compile(str, options));
|
||||
tmpl.eval(options, function(str){
|
||||
fn(null, str);
|
||||
});
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* JQTPL support.
|
||||
*/
|
||||
|
||||
exports.jqtpl = fromStringRenderer('jqtpl');
|
||||
|
||||
/**
|
||||
* JQTPL string support.
|
||||
*/
|
||||
|
||||
exports.jqtpl.render = function(str, options, fn){
|
||||
var engine = requires.jqtpl || (requires.jqtpl = require('jqtpl'));
|
||||
try {
|
||||
engine.template(str, str);
|
||||
fn(null, engine.tmpl(str, options));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Haml support.
|
||||
*/
|
||||
|
||||
exports.haml = fromStringRenderer('haml');
|
||||
|
||||
/**
|
||||
* Haml string support.
|
||||
*/
|
||||
|
||||
exports.haml.render = function(str, options, fn){
|
||||
var engine = requires.hamljs || (requires.hamljs = require('hamljs'));
|
||||
try {
|
||||
options.locals = options;
|
||||
fn(null, engine.render(str, options).trimLeft());
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Whiskers support.
|
||||
*/
|
||||
|
||||
exports.whiskers = function(path, options, fn){
|
||||
var engine = requires.whiskers || (requires.whiskers = require('whiskers'));
|
||||
engine.__express(path, options, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Whiskers string support.
|
||||
*/
|
||||
|
||||
exports.whiskers.render = function(str, options, fn){
|
||||
var engine = requires.whiskers || (requires.whiskers = require('whiskers'));
|
||||
try {
|
||||
fn(null, engine.render(str, options));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Coffee-HAML support.
|
||||
*/
|
||||
|
||||
exports['haml-coffee'] = fromStringRenderer('haml-coffee');
|
||||
|
||||
/**
|
||||
* Coffee-HAML string support.
|
||||
*/
|
||||
|
||||
exports['haml-coffee'].render = function(str, options, fn){
|
||||
var engine = requires.HAMLCoffee || (requires.HAMLCoffee = require('haml-coffee'));
|
||||
try {
|
||||
var tmpl = cache(options) || cache(options, engine.compile(str, options));
|
||||
fn(null, tmpl(options));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Hogan support.
|
||||
*/
|
||||
|
||||
exports.hogan = fromStringRenderer('hogan');
|
||||
|
||||
/**
|
||||
* Hogan string support.
|
||||
*/
|
||||
|
||||
exports.hogan.render = function(str, options, fn){
|
||||
var engine = requires.hogan || (requires.hogan = require('hogan.js'));
|
||||
try {
|
||||
var tmpl = cache(options) || cache(options, engine.compile(str, options));
|
||||
fn(null, tmpl.render(options, options.partials));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* templayed.js support.
|
||||
*/
|
||||
|
||||
exports.templayed = fromStringRenderer('templayed');
|
||||
|
||||
/**
|
||||
* templayed.js string support.
|
||||
*/
|
||||
|
||||
exports.templayed.render = function(str, options, fn){
|
||||
var engine = requires.templayed || (requires.templayed = require('templayed'));
|
||||
try {
|
||||
var tmpl = cache(options) || cache(options, engine(str));
|
||||
fn(null, tmpl(options));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handlebars support.
|
||||
*/
|
||||
|
||||
exports.handlebars = fromStringRenderer('handlebars');
|
||||
|
||||
/**
|
||||
* Handlebars string support.
|
||||
*/
|
||||
|
||||
exports.handlebars.render = function(str, options, fn) {
|
||||
var engine = requires.handlebars || (requires.handlebars = require('handlebars'));
|
||||
try {
|
||||
for (var partial in options.partials) {
|
||||
engine.registerPartial(partial, options.partials[partial]);
|
||||
}
|
||||
for (var helper in options.helpers) {
|
||||
engine.registerHelper(helper, options.helpers[helper]);
|
||||
}
|
||||
var tmpl = cache(options) || cache(options, engine.compile(str, options));
|
||||
fn(null, tmpl(options));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Underscore support.
|
||||
*/
|
||||
|
||||
exports.underscore = fromStringRenderer('underscore');
|
||||
|
||||
/**
|
||||
* Underscore string support.
|
||||
*/
|
||||
|
||||
exports.underscore.render = function(str, options, fn) {
|
||||
var engine = requires.underscore || (requires.underscore = require('underscore'));
|
||||
try {
|
||||
var tmpl = cache(options) || cache(options, engine.template(str, null, options));
|
||||
fn(null, tmpl(options).replace(/\n$/, ''));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* QEJS support.
|
||||
*/
|
||||
|
||||
exports.qejs = function (path, options, fn) {
|
||||
try {
|
||||
var engine = requires.qejs || (requires.qejs = require('qejs'));
|
||||
engine.renderFile(path, options).then(function (result) {
|
||||
fn(null, result);
|
||||
}, function (err) {
|
||||
fn(err);
|
||||
}).end();
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* QEJS string support.
|
||||
*/
|
||||
|
||||
exports.qejs.render = function (str, options, fn) {
|
||||
try {
|
||||
var engine = requires.qejs || (requires.qejs = require('qejs'));
|
||||
engine.render(str, options).then(function (result) {
|
||||
fn(null, result);
|
||||
}, function (err) {
|
||||
fn(err);
|
||||
}).end();
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Walrus support.
|
||||
*/
|
||||
|
||||
exports.walrus = fromStringRenderer('walrus');
|
||||
|
||||
/**
|
||||
* Walrus string support.
|
||||
*/
|
||||
|
||||
exports.walrus.render = function (str, options, fn) {
|
||||
var engine = requires.walrus || (requires.walrus = require('walrus'));
|
||||
try {
|
||||
var tmpl = cache(options) || cache(options, engine.parse(str));
|
||||
fn(null, tmpl.compile(options));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mustache support.
|
||||
*/
|
||||
|
||||
exports.mustache = fromStringRenderer('mustache');
|
||||
|
||||
/**
|
||||
* Mustache string support.
|
||||
*/
|
||||
|
||||
exports.mustache.render = function(str, options, fn) {
|
||||
var engine = requires.mustache || (requires.mustache = require('mustache'));
|
||||
try {
|
||||
fn(null, engine.to_html(str, options, options.partials));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Just support.
|
||||
*/
|
||||
|
||||
exports.just = function(path, options, fn){
|
||||
var engine = requires.just;
|
||||
if (!engine) {
|
||||
var JUST = require('just');
|
||||
engine = requires.just = new JUST();
|
||||
}
|
||||
engine.configure({ useCache: options.cache });
|
||||
engine.render(path, options, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Just string support.
|
||||
*/
|
||||
|
||||
exports.just.render = function(str, options, fn){
|
||||
var JUST = require('just');
|
||||
var engine = new JUST({ root: { page: str }});
|
||||
engine.render('page', options, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* ECT support.
|
||||
*/
|
||||
|
||||
exports.ect = function(path, options, fn){
|
||||
var engine = requires.ect;
|
||||
if (!engine) {
|
||||
var ECT = require('ect');
|
||||
engine = requires.ect = new ECT();
|
||||
}
|
||||
engine.configure({ cache: options.cache });
|
||||
engine.render(path, options, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* ECT string support.
|
||||
*/
|
||||
|
||||
exports.ect.render = function(str, options, fn){
|
||||
var ECT = require('ect');
|
||||
var engine = new ECT({ root: { page: str }});
|
||||
engine.render('page', options, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* mote support.
|
||||
*/
|
||||
|
||||
exports.mote = fromStringRenderer('mote');
|
||||
|
||||
/**
|
||||
* mote string support.
|
||||
*/
|
||||
|
||||
exports.mote.render = function(str, options, fn){
|
||||
var engine = requires.mote || (requires.mote = require('mote'));
|
||||
try {
|
||||
var tmpl = cache(options) || cache(options, engine.compile(str));
|
||||
fn(null, tmpl(options));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Toffee support.
|
||||
*/
|
||||
|
||||
exports.toffee = function(path, options, fn){
|
||||
var toffee = requires.toffee || (requires.toffee = require('toffee'));
|
||||
toffee.__consolidate_engine_render(path, options, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Toffee string support.
|
||||
*/
|
||||
|
||||
exports.toffee.render = function(str, options, fn) {
|
||||
var engine = requires.toffee || (requires.toffee = require('toffee'));
|
||||
try {
|
||||
engine.str_render(str, options,fn);
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* doT support.
|
||||
*/
|
||||
|
||||
exports.dot = fromStringRenderer('dot');
|
||||
|
||||
/**
|
||||
* doT string support.
|
||||
*/
|
||||
|
||||
exports.dot.render = function (str, options, fn) {
|
||||
var engine = requires.dot || (requires.dot = require('dot'));
|
||||
try {
|
||||
var tmpl = cache(options) || cache(options, engine.compile(str));
|
||||
fn(null, tmpl(options));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
};
|
||||
+56
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"browser" : false,
|
||||
"boss" : true,
|
||||
"curly": true,
|
||||
"debug": false,
|
||||
"devel": true,
|
||||
"eqeqeq": true,
|
||||
"evil": false,
|
||||
"forin": true,
|
||||
"immed": true,
|
||||
"indent": 4,
|
||||
"jquery": true,
|
||||
"laxbreak": false,
|
||||
"laxcomma": true,
|
||||
"newcap": true,
|
||||
"noarg": false,
|
||||
"noempty": true,
|
||||
"nonew": true,
|
||||
"nomen": false,
|
||||
"onevar": true,
|
||||
"plusplus": false,
|
||||
"regexp": false,
|
||||
"trailing": true,
|
||||
"undef": true,
|
||||
"sub": false,
|
||||
"strict": true,
|
||||
"globalstrict": true,
|
||||
"white": true,
|
||||
"node" : true
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
dist/broadway.js
|
||||
.*.swp
|
||||
.DS_Store
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
module.exports = function (grunt) {
|
||||
'use strict';
|
||||
// load all grunt tasks
|
||||
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
|
||||
|
||||
grunt.initConfig({
|
||||
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
jshint: {
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
},
|
||||
all: [
|
||||
'Gruntfile.js',
|
||||
'lib/{,*/}*.js',
|
||||
'dist/nodecopter-stream.js'
|
||||
]
|
||||
},
|
||||
|
||||
uglify: {
|
||||
dist: {
|
||||
files: {
|
||||
'dist/broadway.js' : [
|
||||
'dist/vendor/broadway/glUtils.js',
|
||||
'dist/vendor/broadway/util.js',
|
||||
'dist/vendor/broadway/avc.js',
|
||||
'dist/vendor/broadway/canvas.js',
|
||||
'dist/nodecopter-stream.js'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
concat: {
|
||||
options: {
|
||||
banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
|
||||
},
|
||||
dist: {
|
||||
src: [
|
||||
'dist/vendor/broadway/sylvester.js',
|
||||
'dist/vendor/broadway/avc-codec.js',
|
||||
'dist/broadway.js'
|
||||
],
|
||||
dest: 'dist/nodecopter-client.js'
|
||||
}
|
||||
}
|
||||
});
|
||||
grunt.registerTask('default', ['jshint', 'uglify', 'concat']);
|
||||
};
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
0.1.0 / 2012-10-20
|
||||
==================
|
||||
|
||||
* Initial version, unreleased
|
||||
|
||||
0.2.0 / 2012-10-20
|
||||
==================
|
||||
|
||||
* released as npm package dronestream
|
||||
|
||||
1.0.0 / 2012-12-15
|
||||
==================
|
||||
|
||||
* add support for multiple browser clients
|
||||
* add support for browser reloads
|
||||
* reconnect to drone on failure
|
||||
* add support for bare http sockers
|
||||
* remove express dependency
|
||||
* export as a module
|
||||
* added examples for using in other applications
|
||||
|
||||
1.0.1 / 2012-12-16
|
||||
==================
|
||||
|
||||
* update documentation
|
||||
* add changelog
|
||||
|
||||
1.0.2 / 2012-12-27
|
||||
==================
|
||||
|
||||
* use requestAnimationFrame() for rendering video frames
|
||||
* cleaned up express example
|
||||
* update ar-drone and buffy dependencies to latest versions
|
||||
|
||||
1.0.3 / 2013-03-19
|
||||
==================
|
||||
|
||||
* add custom drone ip to server options
|
||||
* fix static files on webserver
|
||||
|
||||
1.1.0 / 2013-08-07
|
||||
==================
|
||||
|
||||
* add support for custom host and port to client
|
||||
* support passing an existing video stream
|
||||
* switch to grunt for building
|
||||
* express fixes
|
||||
* remove constants for strict mode
|
||||
* bump ws and ar-drone dependencies
|
||||
* add hook for postprocessors
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2012 Bernhard K. Weisshuhn (bkw@codingforce.com) and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
This code includes files from the Boradway.js project:
|
||||
https://github.com/mbebenita/Broadway .
|
||||
|
||||
License and author information in included in the directory
|
||||
public/js/vendor/broadway.
|
||||
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
# node-dronestream
|
||||
|
||||
Get a realtime live video stream from your
|
||||
[Parrot AR Drone 2.0](http://ardrone2.parrot.com/) straight to your browser.
|
||||
|
||||
## Requirements
|
||||
|
||||
You'll need a decent and current browser and some cpu horsepower.
|
||||
This code uses web-sockets and the incredibly awesome
|
||||
[Broadway.js](https://github.com/mbebenita/Broadway) to render the video frames
|
||||
in your browser using a WebGL canvas.
|
||||
|
||||
## How to use
|
||||
|
||||
Please see the http.createServer and Express 3.0 examples in the 'examples' dir.
|
||||
You attach the stream to your server like this:
|
||||
```javascript
|
||||
// in node:
|
||||
//
|
||||
// note that the 'server' object points to a server instance and NOT an express app.
|
||||
require("dronestream").listen(server);
|
||||
// if your drone is on a different IP
|
||||
require("dronestream").listen(server, { ip: "192.168.2.155" });
|
||||
```
|
||||
|
||||
We serve the client in the same manner as Socket.IO. Add a reference to
|
||||
**/dronestream/nodecopter-client.js** in your template. Then attach the stream to a DOM node:
|
||||
```html
|
||||
<!-- on the client -->
|
||||
<script src="/dronestream/nodecopter-client.js"></script>
|
||||
<script>
|
||||
// video canvas will auto-size to the DOM-node, or default to 640*360 if no size is set.
|
||||
new NodecopterStream(document.getElementById("droneStream"));
|
||||
</script>
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
The drone sends a proprietary video feed on 192.168.1.1 port 5555. This is
|
||||
mostly a h264 baseline video, but adds custom framing. These frames are parsed
|
||||
and mostly disposed of. The remaining h264 payload is split into NAL units and
|
||||
sent to the browser via web sockets.
|
||||
|
||||
In the browser broadway takes care of the rendering of the WebGL canvas.
|
||||
|
||||
## Status
|
||||
|
||||
Node-dronestream has gained some stability in the last release. It attempts
|
||||
to recover lost connections to the drone, and it handles multiple clients,
|
||||
disconnections, etc. See "How to use" for API.
|
||||
|
||||
## Thanks
|
||||
|
||||
- Triple high fives to Felix 'felixge' Geisendörfer for getting the whole
|
||||
NodeCopter movement started and being extremely helpful in the process of
|
||||
getting this together.
|
||||
|
||||
- André 'zoddy' Kussmann for supplying the drone and allowing me to keep
|
||||
hacking on it, even when he had to cancel the NodeCopter event for himself.
|
||||
|
||||
- Michael Bebenita, Alon Zakai, Andreas Gal and Mathieu 'p01' Henri for the
|
||||
magic of Broadway.js
|
||||
|
||||
- Johann Phillip Strathausen for being a great team mate at NodeCopter 2012
|
||||
Berlin.
|
||||
|
||||
- Brian Leroux for being not content with the original solution and for
|
||||
cleaning up the predecessor, nodecopter-stream.
|
||||
|
||||
- @karlwestin for picking up where I was to lazy to actually make this usable.
|
||||
|
||||
## Demo
|
||||
|
||||
Watch @felixge demoing node-dronestream live at german user group cgnjs:
|
||||
http://www.youtube.com/watch?v=nwGNNMJt4mE&t=19m52
|
||||
+5
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+129
@@ -0,0 +1,129 @@
|
||||
/*jshint browser:true */
|
||||
/*global Avc:true, YUVWebGLCanvas: true, Size: true, requestAnimationFrame:true */
|
||||
|
||||
/* requestAnimationFrame polyfill: */
|
||||
(function (window) {
|
||||
'use strict';
|
||||
var lastTime = 0,
|
||||
vendors = ['ms', 'moz', 'webkit', 'o'],
|
||||
x,
|
||||
length,
|
||||
currTime,
|
||||
timeToCall;
|
||||
|
||||
for (x = 0, length = vendors.length; x < length && !window.requestAnimationFrame; ++x) {
|
||||
window.requestAnimationFrame = window[
|
||||
vendors[x] + 'RequestAnimationFrame'
|
||||
];
|
||||
window.cancelAnimationFrame = window[
|
||||
vendors[x] + 'CancelAnimationFrame'
|
||||
] || window[vendors[x] + 'CancelRequestAnimationFrame'];
|
||||
}
|
||||
|
||||
if (!window.requestAnimationFrame) {
|
||||
window.requestAnimationFrame = function (callback, element) {
|
||||
currTime = new Date().getTime();
|
||||
timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
||||
lastTime = currTime + timeToCall;
|
||||
return window.setTimeout(function () {
|
||||
callback(currTime + timeToCall);
|
||||
}, timeToCall);
|
||||
};
|
||||
}
|
||||
|
||||
if (!window.cancelAnimationFrame) {
|
||||
window.cancelAnimationFrame = function (id) {
|
||||
clearTimeout(id);
|
||||
};
|
||||
}
|
||||
}(window));
|
||||
|
||||
|
||||
/* NodeCopterStream: */
|
||||
(function (window, document, undefined) {
|
||||
'use strict';
|
||||
var NS,
|
||||
socket,
|
||||
avc,
|
||||
webGLCanvas,
|
||||
width,
|
||||
height,
|
||||
callbackOnce = null;
|
||||
|
||||
function setupAvc() {
|
||||
avc = new Avc();
|
||||
avc.configure({
|
||||
filter: 'original',
|
||||
filterHorLuma: 'optimized',
|
||||
filterVerLumaEdge: 'optimized',
|
||||
getBoundaryStrengthsA: 'optimized'
|
||||
});
|
||||
avc.onPictureDecoded = handleDecodedFrame;
|
||||
}
|
||||
|
||||
function handleNalUnits(message) {
|
||||
avc.decode(new Uint8Array(message.data));
|
||||
}
|
||||
|
||||
function handleDecodedFrame(buffer, bufWidth, bufHeight) {
|
||||
var callback;
|
||||
|
||||
requestAnimationFrame(function () {
|
||||
var lumaSize = bufWidth * bufHeight,
|
||||
chromaSize = lumaSize >> 2;
|
||||
|
||||
webGLCanvas.YTexture.fill(buffer.subarray(0, lumaSize));
|
||||
webGLCanvas.UTexture.fill(buffer.subarray(lumaSize, lumaSize + chromaSize));
|
||||
webGLCanvas.VTexture.fill(buffer.subarray(lumaSize + chromaSize, lumaSize + 2 * chromaSize));
|
||||
webGLCanvas.drawScene();
|
||||
});
|
||||
|
||||
// call callback with Y portion (grayscale image)
|
||||
if (null !== callbackOnce && width) {
|
||||
callback = callbackOnce;
|
||||
callbackOnce = null;
|
||||
// decoded buffer size may be larger,
|
||||
// so use subarray with actual dimensions
|
||||
callback(buffer.subarray(0, width * height));
|
||||
}
|
||||
}
|
||||
|
||||
function setupCanvas(div) {
|
||||
var canvas = document.createElement('canvas');
|
||||
|
||||
width = div.attributes.width ? div.attributes.width.value : 640;
|
||||
height = div.attributes.height ? div.attributes.height.value : 360;
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
canvas.style.backgroundColor = "#333333";
|
||||
div.appendChild(canvas);
|
||||
|
||||
webGLCanvas = new YUVWebGLCanvas(canvas, new Size(width, height));
|
||||
}
|
||||
|
||||
|
||||
NS = function (div, options) {
|
||||
var hostname, port;
|
||||
options = options || {};
|
||||
hostname = options.hostname || window.document.location.hostname;
|
||||
port = options.port || window.document.location.port;
|
||||
|
||||
setupCanvas(div);
|
||||
setupAvc();
|
||||
|
||||
socket = new WebSocket(
|
||||
'ws://' + hostname + ':' + port + '/dronestream'
|
||||
);
|
||||
socket.binaryType = 'arraybuffer';
|
||||
socket.onmessage = handleNalUnits;
|
||||
};
|
||||
|
||||
// enqueue callback oto be called with next (black&white) frame
|
||||
NS.prototype.onNextFrame = function (callback) {
|
||||
callbackOnce = callback;
|
||||
};
|
||||
|
||||
window.NodecopterStream = NS;
|
||||
|
||||
}(window, document, undefined));
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
The following authors have all licensed their contributions to the project
|
||||
under the licensing terms detailed in LICENSE.
|
||||
|
||||
Michael Bebenita <mbebenita@gmail.com>
|
||||
Alon Zakai <alonzakai@gmail.com>
|
||||
Andreas Gal <gal@mozilla.com>
|
||||
Mathieu 'p01' Henri <mathieu@p01.org>
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
Copyright (c) 2011, Project Authors (see AUTHORS file)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the names of the Project Authors nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
--
|
||||
|
||||
The 3-clause BSD above applies to all code except for code originating
|
||||
from the Android project (the .cpp files in Avc/). Those files are under
|
||||
the Android project's Apache 2.0 license.
|
||||
|
||||
+1
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+283
@@ -0,0 +1,283 @@
|
||||
/**
|
||||
* Requires: avc-codec.js
|
||||
**/
|
||||
|
||||
assert (Module);
|
||||
|
||||
HEAP8 = Module.HEAP8;
|
||||
HEAPU8 = Module.HEAPU8;
|
||||
HEAP16 = Module.HEAP16;
|
||||
HEAP32 = Module.HEAP32;
|
||||
_h264bsdClip = Module._get_h264bsdClip();
|
||||
|
||||
var Avc = (function avc() {
|
||||
var MAX_STREAM_BUFFER_LENGTH = 1024 * 1024;
|
||||
|
||||
function constructor() {
|
||||
Module._broadwayInit();
|
||||
this.streamBuffer = toU8Array(Module._broadwayCreateStream(MAX_STREAM_BUFFER_LENGTH), MAX_STREAM_BUFFER_LENGTH);
|
||||
this.pictureBuffers = {};
|
||||
|
||||
this.onPictureDecoded = function (buffer, width, height) {
|
||||
// console.info(buffer.length);
|
||||
}
|
||||
|
||||
Module.patch(null, "_broadwayOnHeadersDecoded", function () {
|
||||
|
||||
});
|
||||
|
||||
|
||||
Module.patch(null, "_broadwayOnPictureDecoded", function ($buffer, width, height) {
|
||||
var buffer = this.pictureBuffers[$buffer];
|
||||
if (!buffer) {
|
||||
buffer = this.pictureBuffers[$buffer] = toU8Array($buffer, (width * height * 3) / 2);
|
||||
}
|
||||
this.onPictureDecoded(buffer, width, height);
|
||||
}.bind(this));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a typed array from a HEAP8 pointer.
|
||||
*/
|
||||
function toU8Array(ptr, length) {
|
||||
return HEAPU8.subarray(ptr, ptr + length);
|
||||
}
|
||||
|
||||
constructor.prototype = {
|
||||
/**
|
||||
* Decodes a stream buffer. This may be one single (unframed) NAL unit without the
|
||||
* start code, or a sequence of NAL units with framing start code prefixes. This
|
||||
* function overwrites stream buffer allocated by the codec with the supplied buffer.
|
||||
*/
|
||||
decode: function decode(buffer) {
|
||||
// console.info("Decoding: " + buffer.length);
|
||||
this.streamBuffer.set(buffer);
|
||||
Module._broadwaySetStreamLength(buffer.length);
|
||||
Module._broadwayPlayStream();
|
||||
},
|
||||
configure: function (config) {
|
||||
patchOptimizations(config, patches);
|
||||
console.info("Broadway Configured: " + JSON.stringify(config));
|
||||
}
|
||||
};
|
||||
|
||||
return constructor;
|
||||
})();
|
||||
|
||||
function patchOptimizations(config, patches) {
|
||||
var scope = getGlobalScope();
|
||||
for (var name in patches) {
|
||||
var patch = patches[name];
|
||||
if (patch) {
|
||||
var option = config[name];
|
||||
if (!option) option = "original";
|
||||
console.info(name + ": " + option);
|
||||
assert (option in patch.options);
|
||||
var fn = patch.options[option].fn;
|
||||
if (fn) {
|
||||
scope[patch.original] = Module.patch(null, patch.name, fn);
|
||||
console.info("Patching: " + patch.name + ", with: " + option);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var patches = {
|
||||
"filter": {
|
||||
name: "_h264bsdFilterPicture",
|
||||
display: "Filter Picture",
|
||||
original: "Original_h264bsdFilterPicture",
|
||||
options: {
|
||||
none: {display: "None", fn: function () {}},
|
||||
original: {display: "Original", fn: null},
|
||||
}
|
||||
},
|
||||
"filterHorLuma": {
|
||||
name: "_FilterHorLuma",
|
||||
display: "Filter Hor Luma",
|
||||
original: "OriginalFilterHorLuma",
|
||||
options: {
|
||||
none: {display: "None", fn: function () {}},
|
||||
original: {display: "Original", fn: null},
|
||||
optimized: {display: "Optimized", fn: OptimizedFilterHorLuma}
|
||||
}
|
||||
},
|
||||
"filterVerLumaEdge": {
|
||||
name: "_FilterVerLumaEdge",
|
||||
display: "Filter Ver Luma Edge",
|
||||
original: "OriginalFilterVerLumaEdge",
|
||||
options: {
|
||||
none: {display: "None", fn: function () {}},
|
||||
original: {display: "Original", fn: null},
|
||||
optimized: {display: "Optimized", fn: OptimizedFilterVerLumaEdge}
|
||||
}
|
||||
},
|
||||
"getBoundaryStrengthsA": {
|
||||
name: "_GetBoundaryStrengthsA",
|
||||
display: "Get Boundary Strengths",
|
||||
original: "OriginalGetBoundaryStrengthsA",
|
||||
options: {
|
||||
none: {display: "None", fn: function () {}},
|
||||
original: {display: "Original", fn: null},
|
||||
optimized: {display: "Optimized", fn: OptimizedGetBoundaryStrengthsA}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getGlobalScope() {
|
||||
return function () { return this; }.call(null);
|
||||
}
|
||||
|
||||
/* Optimizations */
|
||||
|
||||
function clip(x, y, z) {
|
||||
return z < x ? x : (z > y ? y : z);
|
||||
}
|
||||
|
||||
function OptimizedGetBoundaryStrengthsA($mb, $bS) {
|
||||
var $totalCoeff = $mb + 28;
|
||||
|
||||
var tc0 = HEAP16[$totalCoeff + 0 >> 1];
|
||||
var tc1 = HEAP16[$totalCoeff + 2 >> 1];
|
||||
var tc2 = HEAP16[$totalCoeff + 4 >> 1];
|
||||
var tc3 = HEAP16[$totalCoeff + 6 >> 1];
|
||||
var tc4 = HEAP16[$totalCoeff + 8 >> 1];
|
||||
var tc5 = HEAP16[$totalCoeff + 10 >> 1];
|
||||
var tc6 = HEAP16[$totalCoeff + 12 >> 1];
|
||||
var tc7 = HEAP16[$totalCoeff + 14 >> 1];
|
||||
var tc8 = HEAP16[$totalCoeff + 16 >> 1];
|
||||
var tc9 = HEAP16[$totalCoeff + 18 >> 1];
|
||||
var tc10 = HEAP16[$totalCoeff + 20 >> 1];
|
||||
var tc11 = HEAP16[$totalCoeff + 22 >> 1];
|
||||
var tc12 = HEAP16[$totalCoeff + 24 >> 1];
|
||||
var tc13 = HEAP16[$totalCoeff + 26 >> 1];
|
||||
var tc14 = HEAP16[$totalCoeff + 28 >> 1];
|
||||
var tc15 = HEAP16[$totalCoeff + 30 >> 1];
|
||||
|
||||
HEAP32[$bS + 32 >> 2] = tc2 || tc0 ? 2 : 0;
|
||||
HEAP32[$bS + 40 >> 2] = tc3 || tc1 ? 2 : 0;
|
||||
HEAP32[$bS + 48 >> 2] = tc6 || tc4 ? 2 : 0;
|
||||
HEAP32[$bS + 56 >> 2] = tc7 || tc5 ? 2 : 0;
|
||||
HEAP32[$bS + 64 >> 2] = tc8 || tc2 ? 2 : 0;
|
||||
HEAP32[$bS + 72 >> 2] = tc9 || tc3 ? 2 : 0;
|
||||
HEAP32[$bS + 80 >> 2] = tc12 || tc6 ? 2 : 0;
|
||||
HEAP32[$bS + 88 >> 2] = tc13 || tc7 ? 2 : 0;
|
||||
HEAP32[$bS + 96 >> 2] = tc10 || tc8 ? 2 : 0;
|
||||
HEAP32[$bS + 104 >> 2] = tc11 || tc9 ? 2 : 0;
|
||||
HEAP32[$bS + 112 >> 2] = tc14 || tc12 ? 2 : 0;
|
||||
HEAP32[$bS + 120 >> 2] = tc15 || tc13 ? 2 : 0;
|
||||
|
||||
HEAP32[$bS + 12 >> 2] = tc1 || tc0 ? 2 : 0;
|
||||
HEAP32[$bS + 20 >> 2] = tc4 || tc1 ? 2 : 0;
|
||||
HEAP32[$bS + 28 >> 2] = tc5 || tc4 ? 2 : 0;
|
||||
HEAP32[$bS + 44 >> 2] = tc3 || tc2 ? 2 : 0;
|
||||
HEAP32[$bS + 52 >> 2] = tc6 || tc3 ? 2 : 0;
|
||||
HEAP32[$bS + 60 >> 2] = tc7 || tc6 ? 2 : 0;
|
||||
HEAP32[$bS + 76 >> 2] = tc9 || tc8 ? 2 : 0;
|
||||
HEAP32[$bS + 84 >> 2] = tc12 || tc9 ? 2 : 0;
|
||||
HEAP32[$bS + 92 >> 2] = tc13 || tc12 ? 2 : 0;
|
||||
HEAP32[$bS + 108 >> 2] = tc11 || tc10 ? 2 : 0;
|
||||
HEAP32[$bS + 116 >> 2] = tc14 || tc11 ? 2 : 0;
|
||||
HEAP32[$bS + 124 >> 2] = tc15 || tc14 ? 2 : 0;
|
||||
}
|
||||
|
||||
function OptimizedFilterVerLumaEdge ($data, bS, $thresholds, imageWidth) {
|
||||
var delta, tc, tmp;
|
||||
var p0, q0, p1, q1, p2, q2;
|
||||
var tmpFlag;
|
||||
var $clp = _h264bsdClip + 512;
|
||||
var alpha = HEAP32[$thresholds + 4 >> 2];
|
||||
var beta = HEAP32[$thresholds + 8 >> 2];
|
||||
var val;
|
||||
|
||||
if (bS < 4) {
|
||||
tmp = tc = HEAPU8[HEAP32[$thresholds >> 2] + (bS - 1)] & 255;
|
||||
for (var i = 4; i > 0; i--) {
|
||||
p1 = HEAPU8[$data + -2] & 255;
|
||||
p0 = HEAPU8[$data + -1] & 255;
|
||||
q0 = HEAPU8[$data] & 255;
|
||||
q1 = HEAPU8[$data + 1] & 255;
|
||||
if ((Math.abs(p0 - q0) < alpha) && (Math.abs(p1 - p0) < beta) && (Math.abs(q1 - q0) < beta)) {
|
||||
p2 = HEAPU8[$data - 3] & 255;
|
||||
if (Math.abs(p2 - p0) < beta) {
|
||||
val = (p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1;
|
||||
HEAP8[$data - 2] = p1 + clip(-tc, tc, val);
|
||||
tmp++;
|
||||
}
|
||||
|
||||
q2 = HEAPU8[$data + 2] & 255;
|
||||
if (Math.abs(q2 - q0) < beta) {
|
||||
val = (q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1;
|
||||
HEAP8[$data + 1] = (q1 + clip(-tc, tc, val));
|
||||
tmp++;
|
||||
}
|
||||
|
||||
val = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3);
|
||||
delta = clip(-tmp, tmp, val);
|
||||
|
||||
p0 = HEAPU8[$clp + (p0 + delta)] & 255;
|
||||
q0 = HEAPU8[$clp + (q0 - delta)] & 255;
|
||||
tmp = tc;
|
||||
HEAP8[$data - 1] = p0;
|
||||
HEAP8[$data] = q0;
|
||||
|
||||
$data += imageWidth;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
OriginalFilterVerLumaEdge($data, bS, $thresholds, imageWidth);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter all four successive horizontal 4-pixel luma edges. This can be done when bS is equal to all four edges.
|
||||
*/
|
||||
function OptimizedFilterHorLuma ($data, bS, $thresholds, imageWidth) {
|
||||
var delta, tc, tmp;
|
||||
var p0, q0, p1, q1, p2, q2;
|
||||
var tmpFlag;
|
||||
var $clp = _h264bsdClip + 512;
|
||||
var alpha = HEAP32[$thresholds + 4 >> 2];
|
||||
var beta = HEAP32[$thresholds + 8 >> 2];
|
||||
var val;
|
||||
|
||||
if (bS < 4) {
|
||||
tmp = tc = HEAPU8[HEAP32[$thresholds >> 2] + (bS - 1)] & 255;
|
||||
for (var i = 16; i > 0; i--) {
|
||||
p1 = HEAPU8[$data + (-imageWidth << 1)] & 255;
|
||||
p0 = HEAPU8[$data + -imageWidth] & 255;
|
||||
q0 = HEAPU8[$data] & 255;
|
||||
q1 = HEAPU8[$data + imageWidth] & 255;
|
||||
|
||||
if ((Math.abs(p0 - q0) < alpha) && (Math.abs(p1 - p0) < beta) && (Math.abs(q1 - q0) < beta)) {
|
||||
p2 = HEAPU8[$data + (-imageWidth * 3)] & 255;
|
||||
if (Math.abs(p2 - p0) < beta) {
|
||||
val = (p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1;
|
||||
HEAP8[$data + (-imageWidth << 1)] = p1 + clip(-tc, tc, val);
|
||||
tmp++;
|
||||
}
|
||||
|
||||
q2 = HEAPU8[$data + (imageWidth << 2)] & 255;
|
||||
if (Math.abs(q2 - q0) < beta) {
|
||||
val = (q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1;
|
||||
HEAP8[$data + imageWidth] = (q1 + clip(-tc, tc, val));
|
||||
tmp++;
|
||||
}
|
||||
|
||||
val = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3);
|
||||
delta = clip(-tmp, tmp, val);
|
||||
|
||||
p0 = HEAPU8[$clp + (p0 + delta)] & 255;
|
||||
q0 = HEAPU8[$clp + (q0 - delta)] & 255;
|
||||
tmp = tc;
|
||||
HEAP8[$data - imageWidth] = p0;
|
||||
HEAP8[$data] = q0;
|
||||
|
||||
$data ++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
OriginalFilterHorLuma($data, bS, $thresholds, imageWidth);
|
||||
}
|
||||
}
|
||||
+553
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
* This file wraps several WebGL constructs and provides a simple, single texture based WebGLCanvas as well as a
|
||||
* specialized YUVWebGLCanvas that can handle YUV->RGB conversion.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a WebGL shader script.
|
||||
*/
|
||||
var Script = (function script() {
|
||||
function constructor() {}
|
||||
|
||||
constructor.createFromElementId = function(id) {
|
||||
var script = document.getElementById(id);
|
||||
|
||||
// Didn't find an element with the specified ID, abort.
|
||||
assert(script , "Could not find shader with ID: " + id);
|
||||
|
||||
// Walk through the source element's children, building the shader source string.
|
||||
var source = "";
|
||||
var currentChild = script .firstChild;
|
||||
while(currentChild) {
|
||||
if (currentChild.nodeType == 3) {
|
||||
source += currentChild.textContent;
|
||||
}
|
||||
currentChild = currentChild.nextSibling;
|
||||
}
|
||||
|
||||
var res = new constructor();
|
||||
res.type = script.type;
|
||||
res.source = source;
|
||||
return res;
|
||||
};
|
||||
|
||||
constructor.createFromSource = function(type, source) {
|
||||
var res = new constructor();
|
||||
res.type = type;
|
||||
res.source = source;
|
||||
return res;
|
||||
}
|
||||
return constructor;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Represents a WebGL shader object and provides a mechanism to load shaders from HTML
|
||||
* script tags.
|
||||
*/
|
||||
var Shader = (function shader() {
|
||||
function constructor(gl, script) {
|
||||
|
||||
// Now figure out what type of shader script we have, based on its MIME type.
|
||||
if (script.type == "x-shader/x-fragment") {
|
||||
this.shader = gl.createShader(gl.FRAGMENT_SHADER);
|
||||
} else if (script.type == "x-shader/x-vertex") {
|
||||
this.shader = gl.createShader(gl.VERTEX_SHADER);
|
||||
} else {
|
||||
error("Unknown shader type: " + script.type);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the source to the shader object.
|
||||
gl.shaderSource(this.shader, script.source);
|
||||
|
||||
// Compile the shader program.
|
||||
gl.compileShader(this.shader);
|
||||
|
||||
// See if it compiled successfully.
|
||||
if (!gl.getShaderParameter(this.shader, gl.COMPILE_STATUS)) {
|
||||
error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(this.shader));
|
||||
return;
|
||||
}
|
||||
}
|
||||
return constructor;
|
||||
})();
|
||||
|
||||
var Program = (function () {
|
||||
function constructor(gl) {
|
||||
this.gl = gl;
|
||||
this.program = this.gl.createProgram();
|
||||
}
|
||||
constructor.prototype = {
|
||||
attach: function (shader) {
|
||||
this.gl.attachShader(this.program, shader.shader);
|
||||
},
|
||||
link: function () {
|
||||
this.gl.linkProgram(this.program);
|
||||
// If creating the shader program failed, alert.
|
||||
assert(this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS),
|
||||
"Unable to initialize the shader program.");
|
||||
},
|
||||
use: function () {
|
||||
this.gl.useProgram(this.program);
|
||||
},
|
||||
getAttributeLocation: function(name) {
|
||||
return this.gl.getAttribLocation(this.program, name);
|
||||
},
|
||||
setMatrixUniform: function(name, array) {
|
||||
var uniform = this.gl.getUniformLocation(this.program, name);
|
||||
this.gl.uniformMatrix4fv(uniform, false, array);
|
||||
}
|
||||
};
|
||||
return constructor;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Represents a WebGL texture object.
|
||||
*/
|
||||
var Texture = (function texture() {
|
||||
function constructor(gl, size, format) {
|
||||
this.gl = gl;
|
||||
this.size = size;
|
||||
this.texture = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
||||
this.format = format ? format : gl.LUMINANCE;
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, this.format, size.w, size.h, 0, this.format, gl.UNSIGNED_BYTE, null);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
}
|
||||
var textureIDs = null;
|
||||
constructor.prototype = {
|
||||
fill: function(textureData, useTexSubImage2D) {
|
||||
var gl = this.gl;
|
||||
assert(textureData.length >= this.size.w * this.size.h,
|
||||
"Texture size mismatch, data:" + textureData.length + ", texture: " + this.size.w * this.size.h);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
||||
if (useTexSubImage2D) {
|
||||
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.size.w , this.size.h, this.format, gl.UNSIGNED_BYTE, textureData);
|
||||
} else {
|
||||
// texImage2D seems to be faster, thus keeping it as the default
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.size.w, this.size.h, 0, this.format, gl.UNSIGNED_BYTE, textureData);
|
||||
}
|
||||
},
|
||||
bind: function(n, program, name) {
|
||||
var gl = this.gl;
|
||||
if (!textureIDs) {
|
||||
textureIDs = [gl.TEXTURE0, gl.TEXTURE1, gl.TEXTURE2];
|
||||
}
|
||||
gl.activeTexture(textureIDs[n]);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
||||
gl.uniform1i(gl.getUniformLocation(program.program, name), n);
|
||||
}
|
||||
};
|
||||
return constructor;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Generic WebGL backed canvas that sets up: a quad to paint a texture on, appropriate vertex/fragment shaders,
|
||||
* scene parameters and other things. Specialized versions of this class can be created by overriding several
|
||||
* initialization methods.
|
||||
*
|
||||
* <code>
|
||||
* var canvas = new WebGLCanvas(document.getElementById('canvas'), new Size(512, 512);
|
||||
* canvas.texture.fill(data);
|
||||
* canvas.drawScene();
|
||||
* </code>
|
||||
*/
|
||||
var WebGLCanvas = (function () {
|
||||
|
||||
var vertexShaderScript = Script.createFromSource("x-shader/x-vertex", text([
|
||||
"attribute vec3 aVertexPosition;",
|
||||
"attribute vec2 aTextureCoord;",
|
||||
"uniform mat4 uMVMatrix;",
|
||||
"uniform mat4 uPMatrix;",
|
||||
"varying highp vec2 vTextureCoord;",
|
||||
"void main(void) {",
|
||||
" gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);",
|
||||
" vTextureCoord = aTextureCoord;",
|
||||
"}"
|
||||
]));
|
||||
|
||||
var fragmentShaderScript = Script.createFromSource("x-shader/x-fragment", text([
|
||||
"precision highp float;",
|
||||
"varying highp vec2 vTextureCoord;",
|
||||
"uniform sampler2D texture;",
|
||||
"void main(void) {",
|
||||
" gl_FragColor = texture2D(texture, vTextureCoord);",
|
||||
"}"
|
||||
]));
|
||||
|
||||
function constructor(canvas, size, useFrameBuffer) {
|
||||
this.canvas = canvas;
|
||||
this.size = size;
|
||||
this.canvas.width = size.w;
|
||||
this.canvas.height = size.h;
|
||||
|
||||
this.onInitWebGL();
|
||||
this.onInitShaders();
|
||||
initBuffers.call(this);
|
||||
if (useFrameBuffer) {
|
||||
initFramebuffer.call(this);
|
||||
}
|
||||
this.onInitTextures();
|
||||
initScene.call(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a frame buffer so that we can render off-screen.
|
||||
*/
|
||||
function initFramebuffer() {
|
||||
var gl = this.gl;
|
||||
|
||||
// Create framebuffer object and texture.
|
||||
this.framebuffer = gl.createFramebuffer();
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
||||
this.framebufferTexture = new Texture(this.gl, this.size, gl.RGBA);
|
||||
|
||||
// Create and allocate renderbuffer for depth data.
|
||||
var renderbuffer = gl.createRenderbuffer();
|
||||
gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
|
||||
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.size.w, this.size.h);
|
||||
|
||||
// Attach texture and renderbuffer to the framebuffer.
|
||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.framebufferTexture.texture, 0);
|
||||
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize vertex and texture coordinate buffers for a plane.
|
||||
*/
|
||||
function initBuffers() {
|
||||
var tmp;
|
||||
var gl = this.gl;
|
||||
|
||||
// Create vertex position buffer.
|
||||
this.quadVPBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVPBuffer);
|
||||
tmp = [
|
||||
1.0, 1.0, 0.0,
|
||||
-1.0, 1.0, 0.0,
|
||||
1.0, -1.0, 0.0,
|
||||
-1.0, -1.0, 0.0];
|
||||
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(tmp), gl.STATIC_DRAW);
|
||||
this.quadVPBuffer.itemSize = 3;
|
||||
this.quadVPBuffer.numItems = 4;
|
||||
|
||||
/*
|
||||
+--------------------+
|
||||
| -1,1 (1) | 1,1 (0)
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| -1,-1 (3) | 1,-1 (2)
|
||||
+--------------------+
|
||||
*/
|
||||
|
||||
var scaleX = 1.0;
|
||||
var scaleY = 1.0;
|
||||
|
||||
// Create vertex texture coordinate buffer.
|
||||
this.quadVTCBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVTCBuffer);
|
||||
tmp = [
|
||||
scaleX, 0.0,
|
||||
0.0, 0.0,
|
||||
scaleX, scaleY,
|
||||
0.0, scaleY,
|
||||
];
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(tmp), gl.STATIC_DRAW);
|
||||
}
|
||||
|
||||
function mvIdentity() {
|
||||
this.mvMatrix = Matrix.I(4);
|
||||
}
|
||||
|
||||
function mvMultiply(m) {
|
||||
this.mvMatrix = this.mvMatrix.x(m);
|
||||
}
|
||||
|
||||
function mvTranslate(m) {
|
||||
mvMultiply.call(this, Matrix.Translation($V([m[0], m[1], m[2]])).ensure4x4());
|
||||
}
|
||||
|
||||
function setMatrixUniforms() {
|
||||
this.program.setMatrixUniform("uPMatrix", new Float32Array(this.perspectiveMatrix.flatten()));
|
||||
this.program.setMatrixUniform("uMVMatrix", new Float32Array(this.mvMatrix.flatten()));
|
||||
}
|
||||
|
||||
function initScene() {
|
||||
var gl = this.gl;
|
||||
|
||||
// Establish the perspective with which we want to view the
|
||||
// scene. Our field of view is 45 degrees, with a width/height
|
||||
// ratio of 640:480, and we only want to see objects between 0.1 units
|
||||
// and 100 units away from the camera.
|
||||
|
||||
this.perspectiveMatrix = makePerspective(45, 1, 0.1, 100.0);
|
||||
|
||||
// Set the drawing position to the "identity" point, which is
|
||||
// the center of the scene.
|
||||
mvIdentity.call(this);
|
||||
|
||||
// Now move the drawing position a bit to where we want to start
|
||||
// drawing the square.
|
||||
mvTranslate.call(this, [0.0, 0.0, -2.4]);
|
||||
|
||||
// Draw the cube by binding the array buffer to the cube's vertices
|
||||
// array, setting attributes, and pushing it to GL.
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVPBuffer);
|
||||
gl.vertexAttribPointer(this.vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Set the texture coordinates attribute for the vertices.
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVTCBuffer);
|
||||
gl.vertexAttribPointer(this.textureCoordAttribute, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
this.onInitSceneTextures();
|
||||
|
||||
setMatrixUniforms.call(this);
|
||||
|
||||
if (this.framebuffer) {
|
||||
console.log("Bound Frame Buffer");
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
||||
}
|
||||
}
|
||||
|
||||
constructor.prototype = {
|
||||
toString: function() {
|
||||
return "WebGLCanvas Size: " + this.size;
|
||||
},
|
||||
checkLastError: function (operation) {
|
||||
var err = this.gl.getError();
|
||||
if (err != this.gl.NO_ERROR) {
|
||||
var name = this.glNames[err];
|
||||
name = (name !== undefined) ? name + "(" + err + ")":
|
||||
("Unknown WebGL ENUM (0x" + value.toString(16) + ")");
|
||||
if (operation) {
|
||||
console.log("WebGL Error: %s, %s", operation, name);
|
||||
} else {
|
||||
console.log("WebGL Error: %s", name);
|
||||
}
|
||||
console.trace();
|
||||
}
|
||||
},
|
||||
onInitWebGL: function () {
|
||||
try {
|
||||
this.gl = this.canvas.getContext("experimental-webgl");
|
||||
} catch(e) {}
|
||||
|
||||
if (!this.gl) {
|
||||
error("Unable to initialize WebGL. Your browser may not support it.");
|
||||
}
|
||||
if (this.glNames) {
|
||||
return;
|
||||
}
|
||||
this.glNames = {};
|
||||
for (var propertyName in this.gl) {
|
||||
if (typeof this.gl[propertyName] == 'number') {
|
||||
this.glNames[this.gl[propertyName]] = propertyName;
|
||||
}
|
||||
}
|
||||
},
|
||||
onInitShaders: function() {
|
||||
this.program = new Program(this.gl);
|
||||
this.program.attach(new Shader(this.gl, vertexShaderScript));
|
||||
this.program.attach(new Shader(this.gl, fragmentShaderScript));
|
||||
this.program.link();
|
||||
this.program.use();
|
||||
this.vertexPositionAttribute = this.program.getAttributeLocation("aVertexPosition");
|
||||
this.gl.enableVertexAttribArray(this.vertexPositionAttribute);
|
||||
this.textureCoordAttribute = this.program.getAttributeLocation("aTextureCoord");;
|
||||
this.gl.enableVertexAttribArray(this.textureCoordAttribute);
|
||||
},
|
||||
onInitTextures: function () {
|
||||
var gl = this.gl;
|
||||
this.texture = new Texture(gl, this.size, gl.RGBA);
|
||||
},
|
||||
onInitSceneTextures: function () {
|
||||
this.texture.bind(0, this.program, "texture");
|
||||
},
|
||||
drawScene: function() {
|
||||
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
||||
},
|
||||
readPixels: function(buffer) {
|
||||
var gl = this.gl;
|
||||
gl.readPixels(0, 0, this.size.w, this.size.h, gl.RGBA, gl.UNSIGNED_BYTE, buffer);
|
||||
}
|
||||
};
|
||||
return constructor;
|
||||
})();
|
||||
|
||||
var YUVWebGLCanvas = (function () {
|
||||
var vertexShaderScript = Script.createFromSource("x-shader/x-vertex", text([
|
||||
"attribute vec3 aVertexPosition;",
|
||||
"attribute vec2 aTextureCoord;",
|
||||
"uniform mat4 uMVMatrix;",
|
||||
"uniform mat4 uPMatrix;",
|
||||
"varying highp vec2 vTextureCoord;",
|
||||
"void main(void) {",
|
||||
" gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);",
|
||||
" vTextureCoord = aTextureCoord;",
|
||||
"}"
|
||||
]));
|
||||
|
||||
var fragmentShaderScriptOld = Script.createFromSource("x-shader/x-fragment", text([
|
||||
"precision highp float;",
|
||||
"varying highp vec2 vTextureCoord;",
|
||||
"uniform sampler2D YTexture;",
|
||||
"uniform sampler2D UTexture;",
|
||||
"uniform sampler2D VTexture;",
|
||||
|
||||
"void main(void) {",
|
||||
" vec3 YUV = vec3",
|
||||
" (",
|
||||
" texture2D(YTexture, vTextureCoord).x * 1.1643828125, // premultiply Y",
|
||||
" texture2D(UTexture, vTextureCoord).x,",
|
||||
" texture2D(VTexture, vTextureCoord).x",
|
||||
" );",
|
||||
" gl_FragColor = vec4",
|
||||
" (",
|
||||
" YUV.x + 1.59602734375 * YUV.z - 0.87078515625,",
|
||||
" YUV.x - 0.39176171875 * YUV.y - 0.81296875 * YUV.z + 0.52959375,",
|
||||
" YUV.x + 2.017234375 * YUV.y - 1.081390625,",
|
||||
" 1",
|
||||
" );",
|
||||
"}"
|
||||
]));
|
||||
|
||||
var fragmentShaderScriptSimple = Script.createFromSource("x-shader/x-fragment", text([
|
||||
"precision highp float;",
|
||||
"varying highp vec2 vTextureCoord;",
|
||||
"uniform sampler2D YTexture;",
|
||||
"uniform sampler2D UTexture;",
|
||||
"uniform sampler2D VTexture;",
|
||||
|
||||
"void main(void) {",
|
||||
" gl_FragColor = texture2D(YTexture, vTextureCoord);",
|
||||
"}"
|
||||
]));
|
||||
|
||||
var fragmentShaderScript = Script.createFromSource("x-shader/x-fragment", text([
|
||||
"precision highp float;",
|
||||
"varying highp vec2 vTextureCoord;",
|
||||
"uniform sampler2D YTexture;",
|
||||
"uniform sampler2D UTexture;",
|
||||
"uniform sampler2D VTexture;",
|
||||
"const mat4 YUV2RGB = mat4",
|
||||
"(",
|
||||
" 1.1643828125, 0, 1.59602734375, -.87078515625,",
|
||||
" 1.1643828125, -.39176171875, -.81296875, .52959375,",
|
||||
" 1.1643828125, 2.017234375, 0, -1.081390625,",
|
||||
" 0, 0, 0, 1",
|
||||
");",
|
||||
|
||||
"void main(void) {",
|
||||
" gl_FragColor = vec4( texture2D(YTexture, vTextureCoord).x, texture2D(UTexture, vTextureCoord).x, texture2D(VTexture, vTextureCoord).x, 1) * YUV2RGB;",
|
||||
"}"
|
||||
]));
|
||||
|
||||
|
||||
function constructor(canvas, size) {
|
||||
WebGLCanvas.call(this, canvas, size);
|
||||
}
|
||||
|
||||
constructor.prototype = inherit(WebGLCanvas, {
|
||||
onInitShaders: function() {
|
||||
this.program = new Program(this.gl);
|
||||
this.program.attach(new Shader(this.gl, vertexShaderScript));
|
||||
this.program.attach(new Shader(this.gl, fragmentShaderScript));
|
||||
this.program.link();
|
||||
this.program.use();
|
||||
this.vertexPositionAttribute = this.program.getAttributeLocation("aVertexPosition");
|
||||
this.gl.enableVertexAttribArray(this.vertexPositionAttribute);
|
||||
this.textureCoordAttribute = this.program.getAttributeLocation("aTextureCoord");;
|
||||
this.gl.enableVertexAttribArray(this.textureCoordAttribute);
|
||||
},
|
||||
onInitTextures: function () {
|
||||
console.log("creatingTextures: size: " + this.size);
|
||||
this.YTexture = new Texture(this.gl, this.size);
|
||||
this.UTexture = new Texture(this.gl, this.size.getHalfSize());
|
||||
this.VTexture = new Texture(this.gl, this.size.getHalfSize());
|
||||
},
|
||||
onInitSceneTextures: function () {
|
||||
this.YTexture.bind(0, this.program, "YTexture");
|
||||
this.UTexture.bind(1, this.program, "UTexture");
|
||||
this.VTexture.bind(2, this.program, "VTexture");
|
||||
},
|
||||
fillYUVTextures: function(y, u, v) {
|
||||
this.YTexture.fill(y);
|
||||
this.UTexture.fill(u);
|
||||
this.VTexture.fill(v);
|
||||
},
|
||||
toString: function() {
|
||||
return "YUVCanvas Size: " + this.size;
|
||||
}
|
||||
});
|
||||
|
||||
return constructor;
|
||||
})();
|
||||
|
||||
|
||||
var FilterWebGLCanvas = (function () {
|
||||
var vertexShaderScript = Script.createFromSource("x-shader/x-vertex", text([
|
||||
"attribute vec3 aVertexPosition;",
|
||||
"attribute vec2 aTextureCoord;",
|
||||
"uniform mat4 uMVMatrix;",
|
||||
"uniform mat4 uPMatrix;",
|
||||
"varying highp vec2 vTextureCoord;",
|
||||
"void main(void) {",
|
||||
" gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);",
|
||||
" vTextureCoord = aTextureCoord;",
|
||||
"}"
|
||||
]));
|
||||
|
||||
var fragmentShaderScript = Script.createFromSource("x-shader/x-fragment", text([
|
||||
"precision highp float;",
|
||||
"varying highp vec2 vTextureCoord;",
|
||||
"uniform sampler2D FTexture;",
|
||||
|
||||
"void main(void) {",
|
||||
" gl_FragColor = texture2D(FTexture, vTextureCoord);",
|
||||
"}"
|
||||
]));
|
||||
|
||||
|
||||
function constructor(canvas, size, useFrameBuffer) {
|
||||
WebGLCanvas.call(this, canvas, size, useFrameBuffer);
|
||||
}
|
||||
|
||||
constructor.prototype = inherit(WebGLCanvas, {
|
||||
onInitShaders: function() {
|
||||
this.program = new Program(this.gl);
|
||||
this.program.attach(new Shader(this.gl, vertexShaderScript));
|
||||
this.program.attach(new Shader(this.gl, fragmentShaderScript));
|
||||
this.program.link();
|
||||
this.program.use();
|
||||
this.vertexPositionAttribute = this.program.getAttributeLocation("aVertexPosition");
|
||||
this.gl.enableVertexAttribArray(this.vertexPositionAttribute);
|
||||
this.textureCoordAttribute = this.program.getAttributeLocation("aTextureCoord");
|
||||
this.gl.enableVertexAttribArray(this.textureCoordAttribute);
|
||||
},
|
||||
onInitTextures: function () {
|
||||
console.log("creatingTextures: size: " + this.size);
|
||||
this.FTexture = new Texture(this.gl, this.size, this.gl.RGBA);
|
||||
},
|
||||
onInitSceneTextures: function () {
|
||||
this.FTexture.bind(0, this.program, "FTexture");
|
||||
},
|
||||
process: function(buffer, output) {
|
||||
this.FTexture.fill(buffer);
|
||||
this.drawScene();
|
||||
this.readPixels(output);
|
||||
},
|
||||
toString: function() {
|
||||
return "FilterWebGLCanvas Size: " + this.size;
|
||||
}
|
||||
});
|
||||
|
||||
return constructor;
|
||||
})();
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
// augment Sylvester some
|
||||
Matrix.Translation = function (v)
|
||||
{
|
||||
if (v.elements.length == 2) {
|
||||
var r = Matrix.I(3);
|
||||
r.elements[2][0] = v.elements[0];
|
||||
r.elements[2][1] = v.elements[1];
|
||||
return r;
|
||||
}
|
||||
|
||||
if (v.elements.length == 3) {
|
||||
var r = Matrix.I(4);
|
||||
r.elements[0][3] = v.elements[0];
|
||||
r.elements[1][3] = v.elements[1];
|
||||
r.elements[2][3] = v.elements[2];
|
||||
return r;
|
||||
}
|
||||
|
||||
throw "Invalid length for Translation";
|
||||
}
|
||||
|
||||
Matrix.prototype.flatten = function ()
|
||||
{
|
||||
var result = [];
|
||||
if (this.elements.length == 0)
|
||||
return [];
|
||||
|
||||
|
||||
for (var j = 0; j < this.elements[0].length; j++)
|
||||
for (var i = 0; i < this.elements.length; i++)
|
||||
result.push(this.elements[i][j]);
|
||||
return result;
|
||||
}
|
||||
|
||||
Matrix.prototype.ensure4x4 = function()
|
||||
{
|
||||
if (this.elements.length == 4 &&
|
||||
this.elements[0].length == 4)
|
||||
return this;
|
||||
|
||||
if (this.elements.length > 4 ||
|
||||
this.elements[0].length > 4)
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < this.elements.length; i++) {
|
||||
for (var j = this.elements[i].length; j < 4; j++) {
|
||||
if (i == j)
|
||||
this.elements[i].push(1);
|
||||
else
|
||||
this.elements[i].push(0);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = this.elements.length; i < 4; i++) {
|
||||
if (i == 0)
|
||||
this.elements.push([1, 0, 0, 0]);
|
||||
else if (i == 1)
|
||||
this.elements.push([0, 1, 0, 0]);
|
||||
else if (i == 2)
|
||||
this.elements.push([0, 0, 1, 0]);
|
||||
else if (i == 3)
|
||||
this.elements.push([0, 0, 0, 1]);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Matrix.prototype.make3x3 = function()
|
||||
{
|
||||
if (this.elements.length != 4 ||
|
||||
this.elements[0].length != 4)
|
||||
return null;
|
||||
|
||||
return Matrix.create([[this.elements[0][0], this.elements[0][1], this.elements[0][2]],
|
||||
[this.elements[1][0], this.elements[1][1], this.elements[1][2]],
|
||||
[this.elements[2][0], this.elements[2][1], this.elements[2][2]]]);
|
||||
};
|
||||
|
||||
Vector.prototype.flatten = function ()
|
||||
{
|
||||
return this.elements;
|
||||
};
|
||||
|
||||
function mht(m) {
|
||||
var s = "";
|
||||
if (m.length == 16) {
|
||||
for (var i = 0; i < 4; i++) {
|
||||
s += "<span style='font-family: monospace'>[" + m[i*4+0].toFixed(4) + "," + m[i*4+1].toFixed(4) + "," + m[i*4+2].toFixed(4) + "," + m[i*4+3].toFixed(4) + "]</span><br>";
|
||||
}
|
||||
} else if (m.length == 9) {
|
||||
for (var i = 0; i < 3; i++) {
|
||||
s += "<span style='font-family: monospace'>[" + m[i*3+0].toFixed(4) + "," + m[i*3+1].toFixed(4) + "," + m[i*3+2].toFixed(4) + "]</font><br>";
|
||||
}
|
||||
} else {
|
||||
return m.toString();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
//
|
||||
// gluLookAt
|
||||
//
|
||||
function makeLookAt(ex, ey, ez,
|
||||
cx, cy, cz,
|
||||
ux, uy, uz)
|
||||
{
|
||||
var eye = $V([ex, ey, ez]);
|
||||
var center = $V([cx, cy, cz]);
|
||||
var up = $V([ux, uy, uz]);
|
||||
|
||||
var mag;
|
||||
|
||||
var z = eye.subtract(center).toUnitVector();
|
||||
var x = up.cross(z).toUnitVector();
|
||||
var y = z.cross(x).toUnitVector();
|
||||
|
||||
var m = $M([[x.e(1), x.e(2), x.e(3), 0],
|
||||
[y.e(1), y.e(2), y.e(3), 0],
|
||||
[z.e(1), z.e(2), z.e(3), 0],
|
||||
[0, 0, 0, 1]]);
|
||||
|
||||
var t = $M([[1, 0, 0, -ex],
|
||||
[0, 1, 0, -ey],
|
||||
[0, 0, 1, -ez],
|
||||
[0, 0, 0, 1]]);
|
||||
return m.x(t);
|
||||
}
|
||||
|
||||
//
|
||||
// glOrtho
|
||||
//
|
||||
function makeOrtho(left, right,
|
||||
bottom, top,
|
||||
znear, zfar)
|
||||
{
|
||||
var tx = -(right+left)/(right-left);
|
||||
var ty = -(top+bottom)/(top-bottom);
|
||||
var tz = -(zfar+znear)/(zfar-znear);
|
||||
|
||||
return $M([[2/(right-left), 0, 0, tx],
|
||||
[0, 2/(top-bottom), 0, ty],
|
||||
[0, 0, -2/(zfar-znear), tz],
|
||||
[0, 0, 0, 1]]);
|
||||
}
|
||||
|
||||
//
|
||||
// gluPerspective
|
||||
//
|
||||
function makePerspective(fovy, aspect, znear, zfar)
|
||||
{
|
||||
var ymax = znear * Math.tan(fovy * Math.PI / 360.0);
|
||||
var ymin = -ymax;
|
||||
var xmin = ymin * aspect;
|
||||
var xmax = ymax * aspect;
|
||||
|
||||
return makeFrustum(xmin, xmax, ymin, ymax, znear, zfar);
|
||||
}
|
||||
|
||||
//
|
||||
// glFrustum
|
||||
//
|
||||
function makeFrustum(left, right,
|
||||
bottom, top,
|
||||
znear, zfar)
|
||||
{
|
||||
var X = 2*znear/(right-left);
|
||||
var Y = 2*znear/(top-bottom);
|
||||
var A = (right+left)/(right-left);
|
||||
var B = (top+bottom)/(top-bottom);
|
||||
var C = -(zfar+znear)/(zfar-znear);
|
||||
var D = -2*zfar*znear/(zfar-znear);
|
||||
|
||||
return $M([[X, 0, A, 0],
|
||||
[0, Y, B, 0],
|
||||
[0, 0, C, D],
|
||||
[0, 0, -1, 0]]);
|
||||
}
|
||||
|
||||
//
|
||||
// glOrtho
|
||||
//
|
||||
function makeOrtho(left, right, bottom, top, znear, zfar)
|
||||
{
|
||||
var tx = - (right + left) / (right - left);
|
||||
var ty = - (top + bottom) / (top - bottom);
|
||||
var tz = - (zfar + znear) / (zfar - znear);
|
||||
|
||||
return $M([[2 / (right - left), 0, 0, tx],
|
||||
[0, 2 / (top - bottom), 0, ty],
|
||||
[0, 0, -2 / (zfar - znear), tz],
|
||||
[0, 0, 0, 1]]);
|
||||
}
|
||||
|
||||
+1
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+71
@@ -0,0 +1,71 @@
|
||||
'use strict';
|
||||
|
||||
function error(message) {
|
||||
console.error(message);
|
||||
console.trace();
|
||||
}
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
error(message);
|
||||
}
|
||||
}
|
||||
|
||||
function isPowerOfTwo(x) {
|
||||
return (x & (x - 1)) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins a list of lines using a newline separator, not the fastest
|
||||
* thing in the world but good enough for initialization code.
|
||||
*/
|
||||
function text(lines) {
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounds up to the next highest power of two.
|
||||
*/
|
||||
function nextHighestPowerOfTwo(x) {
|
||||
--x;
|
||||
for (var i = 1; i < 32; i <<= 1) {
|
||||
x = x | x >> i;
|
||||
}
|
||||
return x + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a 2-dimensional size value.
|
||||
*/
|
||||
var Size = (function size() {
|
||||
function constructor(w, h) {
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
}
|
||||
constructor.prototype = {
|
||||
toString: function () {
|
||||
return "(" + this.w + ", " + this.h + ")";
|
||||
},
|
||||
getHalfSize: function() {
|
||||
return new Size(this.w >>> 1, this.h >>> 1);
|
||||
},
|
||||
length: function() {
|
||||
return this.w * this.h;
|
||||
}
|
||||
}
|
||||
return constructor;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Creates a new prototype object derived from another objects prototype along with a list of additional properties.
|
||||
*
|
||||
* @param base object whose prototype to use as the created prototype object's prototype
|
||||
* @param properties additional properties to add to the created prototype object
|
||||
*/
|
||||
function inherit(base, properties) {
|
||||
var prot = Object.create(base.prototype);
|
||||
for (var p in properties) {
|
||||
prot[p] = properties[p];
|
||||
}
|
||||
return prot;
|
||||
}
|
||||
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário