Home Manual Reference Source Test Repository

src/drivers/infrared/InfraredDriver.js

'use strict';

/** @namespace drivers.infrared */

const EventEmitter = require('events').EventEmitter;
const Point = Mep.require('misc/Point');
const Polygon = Mep.require('misc/Polygon');

const TAG = 'InfraredDriver';

/**
 * Uses data from infrared sensors to determine where is an enemy robot and other obstacles.
 *
 * @memberOf drivers.infrared
 * @author Darko Lukic <[email protected]>
 * @fires drivers.infrared.InfraredDriver#obstacleDetected
 */
class InfraredDriver extends EventEmitter {
    /**
     * Make instance of InfraredDriver.
     *
     * <pre>
     * Check image bellow to understand `sensorAngle`, `sensorX` & `sensorY`
     *  s1   s2   s3
     *   \   |   /
     *  |---------|
     *  |  Robot  |
     *  |_________|
     *
     *  Sensor s1 params: sensorAngle~=110, sensorY~10, sensorX~=10
     *  Sensor s2 params: sensorAngle~=90, sensorY~=0, sensorX~=10
     *  Sensor s3 params: sensorAngle~=60, sensorY~=-10, sensorX~=10
     *  </pre>
     *
     * @param name {String} - Unique driver name
     * @param config.infraredMaxDistance {Number} - Maximum distance when driver detects an object
     * @param config.sensorAngle {Number} - Angle relative to the robot (look at the picture above)
     * @param config.sensorX {Number} - Sensor translated on x coordinate
     * @param config.sensorY {Number} - Sensor translated on y coordinate
     * @param config.deviceId {Number} - Function ID for CAN driver
     * @param config.objectSize {Number} - Approximation coefficient for obstacle size. Distance between edges and point of interest,
     * @param config['@dependencies'] {String} - ID of Driver can provide communication between core and electronics
     */
    constructor(name, config) {
        super();

        // Check arguments
        if (typeof config.infraredMaxDistance === 'undefined') {
            throw '`config.infraredMaxDistance` is not defined';
        }
        if (typeof config.deviceId === 'undefined') {
            throw '`config.deviceId` is not defined';
        }
        if (typeof config['@dependencies'].communicator === 'undefined') {
            throw 'Infrared driver requires driver which enables communication with electronics board (eg. CanDriver)';
        }
        this.config = Object.assign({
            objectSize: 150,
            sensorX: 0,
            sensorY: 0,
            sensorAngle: 90
        }, config);
        this.name = name;
        this._detected = false;
        this._timeoutHandle = null;
        this._enabled = true;

        // Subscribe on communicator
        this.canDriver = Mep.getDriver(this.config['@dependencies'].communicator);
        this.canDriver.on('data_' + this.config.deviceId, this.processDetection.bind(this));


        // Approximation of detected object
        this.poi = new Point(this.config.sensorX, this.config.sensorY + this.config.infraredMaxDistance);
        this.polygon = new Polygon(name, Mep.Config.get('obstacleMaxPeriod'), [
            new Point(this.poi.getX() - this.config.objectSize, this.poi.getY() - this.config.objectSize),
            new Point(this.poi.getX() + this.config.objectSize, this.poi.getY() - this.config.objectSize),
            new Point(this.poi.getX() + this.config.objectSize, this.poi.getY() + this.config.objectSize),
            new Point(this.poi.getX() - this.config.objectSize, this.poi.getY() + this.config.objectSize)
        ]);

        // We are using inverted (`-`) angle because X and Y coordinate are replaced
        this.poi.rotate(new Point(this.config.sensorX, this.config.sensorY), - this.config.sensorAngle);
        this.polygon.rotate(new Point(this.config.sensorX, this.config.sensorY), - this.config.sensorAngle);

        Mep.Log.debug(TAG, name, 'Detects at', this.poi);
    }

    /**
     * Enable sensor
     */
    enable() {
        this._enabled = true;
    }

    /**
     * Disable sensor
     */
    disable() {
        this._enabled = false;
    }

    /**
     * Process detected obstacle
     * @private
     * @param buffer {Boolean} - Object is detected or not
     */
    processDetection(buffer) {
        if (this._enabled === false) return;

        this._detected = !!(buffer.readInt8(0));

        /**
         * Obstacle detected event.
         * @event drivers.infrared.InfraredDriver#obstacleDetected
         * @property {String} - Source name
         * @property {misc.Point} - Center of detected obstacle
         * @property {misc.Polygon} - Obstacle is approximated with a polygon
         * @property {Boolean} - Is objected detected or not
         * @property {Object} - Additional information about detected object
         */
        this.emit(
            'obstacleDetected',
            this.name,
            this.poi.clone(),
            this.polygon.clone(),
            this._detected
        );

        // After `duration` publish obstacle detection again if object is still there
        if (this._timeoutHandle !== null) {
            clearTimeout(this._timeoutHandle);
        }
        if (this._detected === true) {
            let infraredDriver = this;
            this._timeoutHandle = setTimeout(() => {
                infraredDriver.processDetection(buffer);
            }, Mep.Config.get('obstacleMaxPeriod'));
        }

        Mep.Log.debug(TAG, this.name, 'Detected at', this.poi);
    }

    getGroups() {
        return ['terrain'];
    }
}

module.exports = InfraredDriver;