Source: org/terraswarm/accessor/accessors/web/cameras/Foscam.js

// Copyright (c) 2015-2016 The Regents of the University of California.
// All rights reserved.
//
// Permission is hereby granted, without written agreement and without
// license or royalty fees, to use, copy, modify, and distribute this
// software and its documentation for any purpose, provided that the above
// copyright notice and the following two paragraphs appear in all copies
// of this software.
//
// IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
// ENHANCEMENTS, OR MODIFICATIONS.
//

/** An accessor for a Foscam camera. The commands that this accessor supports are:
 *  1.  *snapshot*: Take a picture and produce it on the '''response''' output.
 *  1.  *videostream*: Output a video stream.
 *  1.  *center*: Center the camera. This will pan to find the center.
 *  1.  *down*: Move the camera down.
 *  1.  *left*: Move the camera left.
 *  1.  *right*: Move the camera left.
 *  1.  *up*: Move the camera left.
 *  For the last four motion command commands, you can specify an argument
 *  'degree' in the arguments object to move a specified number of degrees.
 *  If no degree argument is given, the camera will move as far as it can in
 *  the specified direction, or until you give it a stop command:
 *  1.  *stop down*
 *  1.  *stop left*
 *  1.  *stop right*
 *  1.  *stop up*
 *  The following commands start and stop continual panning of the camera:
 *  1.  *horizontal patrol*
 *  1.  *vertical patrol*
 *  1.  *stop horizontal patrol*
 *  1.  *stop vertical patrol*
 *
 *
 *  If you select *videostream* then you can specify 'resolution' and 'rate'
 *  options.  FIXME: Make more user friendly versions of these.
 *  resolution = 8 specifies 320*240. rate=15 specifies 1fps.
 *  NOTE: The videostream selection appears to not actually work.
 *  It has been known to produce a frame or two, but it needs further work.
 *
 *  The <i>options</i> input can be a string URL
 *  or an object with the following fields:
 *
 *  1. '''headers''': An object containing request headers. By default this
 *    is an empty object. Items may have a value that is an array of values,
 *    for headers with more than one value.
 *  1. '''keepAlive''': A boolean that specified whether to keep sockets around
 *    in a pool to be used by other requests in the future. This defaults to false.
 *  1. '''method''': A string specifying the HTTP request method.
 *    This defaults to 'GET', but can also be 'PUT', 'POST', 'DELETE', etc.
 *  1. '''url''': A string that can be parsed as a URL, or an object containing
 *    the following fields:
 *    * '''host''': A string giving the domain name or IP address of
 *    the server to issue the request to. This defaults to 'localhost'.
 *    * '''protocol''': The protocol. This is a string that defaults to 'http'.
 *    * '''port''': Port of remote server. This defaults to 80.
 *
 *
 *  This accessor has been tested with a Foscam NVision F18910W only.
 *  The (rather poor) documentation for the camera API can be found here:
 *  http://www.foscam.es/descarga/ipcam_cgi_sdk.pdf.
 *  The design of this accessor is inspired by the "foscam" module for
 *  Node.js, found at https://github.com/fvdm/nodejs-foscam.
 *
 *  @accessor cameras/Foscam
 *  @author Edward A. Lee (eal@eecs.berkeley.edu)
 *  @input {JSON} options The specification for the URL, as defined above.
 *  @input {string} command The command.
 *  @input {JSON} arguments Arguments to the command.
 *  @input trigger An input to trigger the command.
 *  @output {string} response The server's response.
 *  @output {int} status The status code of the response.
 *  @output {JSON} headers The headers sent with the response.
 *  @extends net.REST
 *  @version $$Id$$
 */

// Stop extra messages from jslint and jshint.  Note that there should be no
// space between the / and the * and global. See https://chess.eecs.berkeley.edu/ptexternal/wiki/Main/JSHint */
/*global console, exports, extend, get, input, parameter, require */
/*jshint globalstrict: true*/
"use strict";

var querystring = require('querystring');

/** Define inputs and outputs. */
exports.setup = function () {
    this.extend('net/REST');
    this.input('command', {
        'type': 'string',
        'value': 'snapshot',
        'options': [
            'snapshot',
            'videostream',
            'center',
            'down',
            'left',
            'right',
            'up',
            'stop down',
            'stop left',
            'stop right',
            'stop up',
            'horizontal patrol',
            'vertical patrol',
            'stop vertical patrol',
            'stop horizontal patrol'
        ]
    });
    // Provide parameters for username and password.
    this.parameter('username', {
        'value': 'admin',
        'type': 'string'
    });
    this.parameter('password', {
        'value': '',
        'type': 'string'
    });
};

// Alternate command to use, for example to stop the camera.
var alternateCommand;

/** Override the base class to construct the path for the URL from.
 *  more user-friendly descriptions of the command.
 */
exports.encodePath = function () {
    var command;
    if (!alternateCommand) {
        command = this.get('command');
    }
    var code = -1;
    switch (command) {
    case 'up':
        code = 0;
        break;
    case 'stop up':
        code = 1;
        break;
    case 'down':
        code = 2;
        break;
    case 'stop down':
        code = 3;
        break;
    case 'left':
        code = 4;
        break;
    case 'stop left':
        code = 5;
        break;
    case 'right':
        code = 6;
        break;
    case 'stop right':
        code = 7;
        break;
    case 'center':
        code = 25;
        break;
    case 'vertical patrol':
        code = 26;
        break;
    case 'stop vertical patrol':
        code = 27;
        break;
    case 'horizontal patrol':
        code = 28;
        break;
    case 'stop horizontal patrol':
        code = 29;
        break;
        // FIXME: No idea what the following mean, so not offerred above.
    case 'io output high':
        code = 94;
        break;
    case 'io output low':
        code = 95;
        break;
    }
    var encodedArgs = 'user=' + this.get('username') + '&pwd=' + this.get('password');
    if (code >= 0) {
        command = 'decoder_control';
        encodedArgs += '&command=' + code;
    }
    var additionalArgs = querystring.stringify(this.get('arguments')).trim();
    if (additionalArgs !== "") {
        encodedArgs += '&' + additionalArgs;
    }
    var result = command + '.cgi?' + encodedArgs;
    console.log(result);
    return result;
};

/** Upon wrapup, attempt to stop the camera videostream.  */
exports.wrapup = function () {
    exports.ssuper.wrapup.call(this);
    // Assume any command will work to stop the stream.
    alternateCommand = 'stop up';
    // No need to specify a callback.
    this.exports.issueCommand.call(this);
};