Initial hacky commit

Esse commit está contido em:
Craig Rowe
2013-08-10 13:15:52 +01:00
commit 641ffac406
635 arquivos alterados com 68575 adições e 0 exclusões
+65
Ver Arquivo
@@ -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);
+8
Ver Arquivo
@@ -0,0 +1,8 @@
<html>
<head>
</head>
<body>
<h1>Test</h1>
<img src="/image"/>
</body>
</html>
gerado externo Link simbólico
+1
Ver Arquivo
@@ -0,0 +1 @@
../express/bin/express
+3
Ver Arquivo
@@ -0,0 +1,3 @@
*.swp
*.un~
/node_modules
+3
Ver Arquivo
@@ -0,0 +1,3 @@
language: node_js
node_js:
- 0.8
+109
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -0,0 +1,6 @@
SHELL := /bin/bash
test:
npm test
.PHONY: test
+53
Ver Arquivo
@@ -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
Ver Arquivo
@@ -0,0 +1,409 @@
# ar-drone
[![Build Status](https://secure.travis-ci.org/felixge/node-ar-drone.png)](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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -0,0 +1,4 @@
*.un~
/node_modules
/benchmarks/fixtures/*.bin
/benchmarks/node_modules
+31
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -0,0 +1,6 @@
SHELL := /bin/bash
test:
node test/run.js
.PHONY: test
+111
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -0,0 +1,3 @@
# Benchmarks
Work in progress ...
+28
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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);
});
});
@@ -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');
@@ -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');
};
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -0,0 +1,6 @@
var buffy = exports;
buffy.Reader = require('./lib/Reader');
buffy.createReader = function(options) {
return new buffy.Reader(options);
};
+181
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -0,0 +1,5 @@
var common = exports;
var path = require('path');
common.root = path.join(__dirname, '..');
common.lib = path.join(common.root, 'lib');
+2
Ver Arquivo
@@ -0,0 +1,2 @@
var urun = require('urun');
urun(__dirname);
+302
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+13
Ver Arquivo
@@ -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);
};
Arquivo binário não exibido.
Arquivo binário não exibido.
Arquivo binário não exibido.
@@ -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);
});
@@ -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]);
}
});
@@ -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']);
});
@@ -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
Ver Arquivo
@@ -0,0 +1,2 @@
var urun = require('urun');
urun(__dirname);
+11
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -0,0 +1,4 @@
support
test
examples
*.sock
+79
Ver Arquivo
@@ -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
Ver Arquivo
@@ -0,0 +1,9 @@
REPORTER = spec
test:
@./node_modules/.bin/mocha \
--slow 30 \
--reporter $(REPORTER)
.PHONY: test
+153
Ver Arquivo
@@ -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 &lt;tj@vision-media.ca&gt;
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
Ver Arquivo
@@ -0,0 +1 @@
module.exports = require('./lib/consolidate');
+704
Ver Arquivo
@@ -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
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+30
Ver Arquivo
@@ -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
Ver Arquivo
@@ -0,0 +1,4 @@
node_modules
dist/broadway.js
.*.swp
.DS_Store
+49
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+129
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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.
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+283
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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]]);
}
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+71
Ver Arquivo
@@ -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