Source: robobo.js

/*******************************************************************************
 * Copyright 2018 Mytech Ingenieria Aplicada <http://www.mytechia.com>
 * Copyright 2018 Luis Llamas <luis.llamas@mytechia.com>
 * Copyright 2018 Gervasio Varela <gervasio.varela@mytechia.com>
 * 
 * <p>
 * This file is part of Robobo.js, the Robobo Javascript Programming Library.
 * <p>
 * Robobo.js is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * <p>
 * Robobo.js is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * <p>
 * You should have received a copy of the GNU Lesser General Public License
 * along with Robobo.js.  If not, see <http://www.gnu.org/licenses/lgpl.html>.
 ******************************************************************************/

var Remote = require('./remote-library/remotelib');


/** 
 * Robobo.js is the library used to create programs for the Robobo educational 
 * robot (http://www.theroboboproject.com) in the Javascript language.
 * 
 * For more information and documentation see https://github.com/mytechia/robobo.js
 * 
 */
class Robobo {

    /** Creates a new Robobo.js library instance.
     *  
     * @constructor
     * @param {string} ip The IP address of the Robobo robot
     */
    constructor(ip) {
        this.connectionState = 0;
        this.ip = ip.trim();
        this.rem = new Remote(this.ip,'');
        this.rem.registerCallback('onConnectionChanges', arg => {
            console.log('Connection State: '+arg);
            this.connectionState = arg})

        this.unlockFunction = () => {  };

        
        this.FaceLostFlag = false;
        this.FlingFlag = false;
        this.BlobFlag = false;
        this.NewFaceFlag = false;
        this.NoteFlag = false;
        this.TapFlag = false;

        this.rem.registerCallback('talkCallback',this.unlockFunction);
        this.rem.registerCallback('onNewBlob',this.unlockFunction);
        this.rem.registerCallback('onLowBatt',this.unlockFunction);
        this.rem.registerCallback('onLowOboBatt',this.unlockFunction);
        this.rem.registerCallback('onLostFace',this.unlockFunction);
        this.rem.registerCallback('onNewFace',this.unlockFunction);
        this.rem.registerCallback('onFall',this.unlockFunction);
        this.rem.registerCallback('onGap',this.unlockFunction);
        this.rem.registerCallback('onNewTap',this.unlockFunction);
        this.rem.registerCallback('onError',this.unlockFunction);
        this.rem.registerCallback('onPhrase',this.unlockFunction);
        this.rem.registerCallback('onNewNote',this.unlockFunction);

    }


    /** Establishes a remote connection with the Robobo indicated by
     * the IP address associated to this instance.    
     */
    async connect() {
        this.rem.connect();
        while (this.rem.connectionState == Remote.ConnectionStateEnum.CONNECTING) {
             await this.update();
        }

        console.log('ROBOBO: Connected to robot at '+this.ip);
    }

    /** Disconnects the library from the Robobo robot
     * 
     */
    async disconnect() {
        this.rem.closeConnection(false);
        while (this.rem.connectionState != Remote.ConnectionStateEnum.DISCONNECTED) {
             await this.update();

        }
        console.log('ROBOBO: Disconnected from '+this.ip);
    }


    /** Stops the movement of the wheels
     */
    stopMotors() {
        this.rem.moveWheelsSeparated(0,0,0);
    }

    /** Starts moving the wheels of the robot at the specified speed.
     * 
     * @param {integer} speedR Speed factor for the right wheel [-100 - 100]
     * @param {integer} speedL Speed factor for the right wheel [-100 - 100]
    */
    moveWheels(speedR, speedL) {
        this.rem.moveWheelsSeparated(speedR, speedL, 2147483647);
    }

    /** Moves the wheels of the robot at the specified speeds during the specified time.
     * This functions is blocking, it doesn't returns the control until the movement
     * is finished.
     * 
     * @param {integer} speedR Speed factor for the right wheel [-100..100]
     * @param {integer} speedL Speed factor for the right wheel [-100..100]
     * @param {integer} time Time duration of the movement in seconds
     */
    async moveWheelsByTimeBLK(speedR, speedL, time) {
        let unlock = false
        this.rem.moveWheelsSeparatedWait(speedL, speedR, time,()=>(unlock = true));
        while (!unlock){
                await this.update()
        }
        unlock = false;
    }

    /** Moves the wheels of the robot by some degress at the specified speed.
     * 
     * @param {string} wheel - Wheels to move [left | right | both]
     * @param {integer} degrees - Degress to move the wheel
     * @param {integer} speed  - Speed factor [-100..100]
     */
    async moveWheelsByDegreesBLK(wheel, degrees, speed) {
        let unlock = false
        this.rem.moveWheelsByDegree(wheel,degrees,speed,()=>(unlock = true))
        while (!unlock){
            await this.update()
        }
        unlock=false;
    }

    /** Moves the PAN of the base to the specified position at the specified speed
     * 
     * @param {integer} position Position in degress of the PAN [-160..160]
     * @param {integer} speed  Speed factor [-40..40]
     */
    movePanTo(position, speed) {
        this.rem.movePan(position,speed);
    }

    /** Moves the PAN of the base to the specified position at the specified speed and
     * waits until the movement has finished.
     * 
     * @param {integer} position Position in degress of the PAN [-170..170]
     * @param {integer} speed  Speed factor [-40..40]
     */
    async movePanToBLK(position, speed) {
        let unlock = false
        this.rem.movePanWait(position,speed,()=>(unlock = true))
        while (!unlock){
            await this.update()
        }
        unlock=false;
    }

    /** Moves the TILT of the base to the specified position at the specified speed
     * 
     * @param {integer} position - Position in degress of the TILT [5..105]
     * @param {integer} speed  - Speed factor [-10..10]
     */
    moveTiltTo(position, speed) {
        this.rem.moveTilt(position, speed)
    }

    /** Moves the TILT of the base to the specified position at the specified speed and
     * waits until the movement has finished.
     * 
     * @param {integer} position Position in degress of the TILT [5..105]
     * @param {integer} speed  Speed factor [-10..10]
     */
    async moveTiltToBLK(position, speed, blocking) {
        let unlock = false
        this.rem.moveTiltWait(position,speed,()=>(unlock = true))
        while (!unlock){
            await this.update()
        }
        unlock=false;
    }


    /** Changes the color of a LED of the base
     * 
     * @param {string} led The ID of the led ['Front-C','Front-L','Front-LL','Front-R','Front-RR','Back-L','Back-R','all']
     * @param {string} color The new color ['off','white','red','blue','cyan','magenta','yellow','green','orange']
     */
    setLedColorTo(led, color) {
        this.rem.setLedColor(`${led}`,color);
    }

    /** Changes the emotion of showed by the face of Robobo
     *  
     * @param {string} emotion One of ['happy','laughing','surprised','sad','angry','normal','sleeping','tired','afraid']
     */
    setEmotionTo(emotion) {
        this.rem.changeEmotion(emotion);        
    }

    /** Commands the robot say the specified text 
     * 
     * @param {string} text The text to say
     */
    sayText(text) {
        let unlock = false
        this.rem.talk(text,()=>(unlock = false));
    }

    /** Commands the robot say the specified text and waits until the 
     * robots finishes reading the text
     * 
     * @param {string} text The text to say
     */
    async sayTextBLK(text) {
        let unlock = false
        this.rem.talk(text,()=>(unlock = true));
        while (!unlock){
            await this.update()
        }
        unlock=false;
    }

    /** Commands the robot to play the specified emotion sound
     * 
     * @param {string} sound One of ['moan','purr',"angry","approve","disapprove","discomfort","doubtful","laugh","likes","mumble","ouch","thinking","various"]
     */
    playSound(sound) {
        this.rem.playEmotionSound(sound);    
    }

    /** Commands the robot to play a musical note
     *  
     * @param {integer} note Musical note index [48..72]. Anglo-Saxon notation is used and there are 25 possible notes with the following basic correspondence. Any integer between 48 and 72.
     * @param {integer} time Duration of the note in seconds (decimals can be used to used, like 0.2 or 0.5) 
     */
    playNote(note, time) {
        this.rem.playNote(note,time*1000); //the Robobo remote expects millis        
    }
    
    /** Commands the robot to play a musical note and wait until finishes playing it
     *  
     * @param {integer} note Musical note index [48..72]. Anglo-Saxon notation is used and there are 25 possible notes with the following basic correspondence. Any integer between 48 and 72.
     * @param {float} time Duration of the note in seconds (decimals can be used to used, like 0.2 or 0.5) 
     */
    async playNoteBLK(note, time) {
        this.rem.playNote(note,time*1000); //the Robobo remote expects millis
        await this.pause(time);
    }


    /** Returns the position of the wheel in degrees
     *  
     * @param {string} wheel - One of [left, right]
     * @returns the position of the wheel in degress
     */
    readWheelPosition(wheel) {
        return this.rem.getWheel(wheel,'position');
    }

    /** Returns the current speed of the wheel
     *  
     * @param {string} wheel One of [left, right]
     * @returns the current speed of the wheel in degress
     */
    readWheelSpeed(wheel) {
        return this.rem.getWheel(wheel,'speed');
    }

    /** Returns the current position of the PAN
     * 
     * @returns the current position of the pan
     */
    readPanPosition() {
        return this.rem.getPan();
    }

    /** Returns the current position of the TILT
     * 
     * @returns the current position of the TILT
     */
    readTiltPosition() {
        return this.rem.getTilt();
    }

    /** Returns the current value sensed by the specified IR
     *  
     * @param {string} sensor One of ['Front-C','Front-L','Front-LL','Front-R','Front-RR','Back-C','Back-L','Back-R'] 
     * @returns {integer} the current value of the IR
     */
    readIRSensor(sensor) {
        return this.rem.getIRValue(sensor);
    }

    /** Returns the values of all the IR sensors.
     * 
     * Example of use:
     * let irs = readAllIRSensor();
     * console.log(irs.BackR);
     * console.log(irs.FrontRR);
     * 
     * @returns {integer} The values of all the IR sensors of the base
     */
    readAllIRSensor() {
        return {
            BackR: this.rem.getIRValue('Back-R'),
            BackC: this.rem.getIRValue('Back-C'),
            FrontRR: this.rem.getIRValue('Front-RR'),
            FrontR: this.rem.getIRValue('Front-R'),
            FrontC: this.rem.getIRValue('Front-C'), 
            FrontL: this.rem.getIRValue('Front-L'),
            FrontLL: this.rem.getIRValue('Front-LL'),
            BackL: this.rem.getIRValue('Back-L'),
        }
    }

    /** Returns the battery level of the base or the smartphone
     *  
     * @param {string} device One of 'base' or 'smartphone'
     * @returns {integer} the battery level of the base or the smartphone
     */
    readBatteryLevel(device) {
        if (device == 'base'){
            return this.rem.checkBatt();
        }else{
            return this.rem.checkOboBatt();
        }
    }

    /** Returns the position and distance of the last face detected by the robot
     * 
     * Example of use:
     * let face = robobo.readFaceSensor();
     * console.log(face.distance); //the distance to the person
     * console.log(face.x); //the position of the face in X axis
     * console.log(fase.y); //the position of the face in Y axis
     * 
     * @returns the position and distance of the last face detected by the robot
     */
    readFaceSensor() {
        return {
            distance : this.rem.getFaceDist(),
            x : this.rem.getFaceCoord('x'),
            y : this.rem.getFaceCoord('y')
        }
    }

    /** Resets the face sensor.
     * After this function, and until a new face is detected, the face sensor
     * will return 0 as values for distance, x and y position.
     */
    resetFaceSensor() {
        this.rem.resetFaceSensor();
    }
    /** Returns the number of claps registered since the last reset
     *
     *
     * @returns {Integer} Clap counter
     * @memberof Robobo
     */
    readClapCounter() {
        return this.rem.getClaps();
    }
    /** Resets the clap counter
     *
     *
     * @memberof Robobo
     */
    resetClapCounter() {
        this.rem.resetClapSensor();
    }
    /** Returns the last note detected by the note sensor
     *
     *
     * @returns Name (note) and duration (duration) of the last note
     * @memberof Robobo
     */
    readLastNote() {
        return {
            note : this.rem.getLastNote(),
            duration : this.rem.getLastNoteDuration()
        }
    }
    /** Resets the last note registered by the note sensor
     *
     *
     * @memberof Robobo
     */
    resetLastNote() {
        this.rem.resetNoteSensor();
    }
    /** Reads the last detected blob of color of the indicated color
     *
     *
     * @param {*} color Color of the blob
     * @returns The position of the blob (x,y) and the area (area)
     * @memberof Robobo
     */
    readColorBlob(color) {
        return {
            
            x: this.rem.getBlobCoord(color,'x'),
            y: this.rem.getBlobCoord(color,'y'),
            area: this.rem.getBlobSize(color),
        }

    }
    /** Reads all the color blob data 
     *
     *
     * @returns A map returning the individual blob information (red, green, blue, custom)
     * @memberof Robobo
     */
    readAllColorBlobs() {
        return{
            red : this.readColorBlob('red'),
            green : this.readColorBlob('green'),
            blue : this.readColorBlob('blue'),
            custom : this.readColorBlob('custom'), 
        }
    }

    /**
     * Resets the color blob detector
     *
     * @memberof Robobo
     */
    resetColorBlobs() {
        this.rem.resetBlobSensor();
    }

    /**
     * Activates the individual tracking of each color. 
     * Warning: Color tracking is a computionally intensive task,
     * activating all the colors may impact performance
     *
     * @param {Boolean} red Enables red blob tracking
     * @param {Boolean} green Enables green blob tracking
     * @param {Boolean} blue Enables blue blob tracking
     * @param {Boolean} custom Enables custom blob tracking
     * @memberof Robobo
     */
    setColorBlobDetectionActive(red, green, blue, custom) {
        this.rem.configureBlobDetection(red,green,blue,custom);
    }
    
    /**
     * Reads the data of the fling sensor
     *
     * @returns Lasta angle detected on the sensor
     * @memberof Robobo
     */
    readFlingSensor() {
        return this.rem.checkFlingAngle();
    }

    /**
     * Resets the state of the Fling sensor
     *
     * @memberof Robobo
     */
    resetFlingSensor() {
        this.rem.resetFlingSensor();
    }

    /**
     * Reads the data on the tap sensor
     *
     * @returns Coordinates of the last registered tap (x, y) and the zone of the face
     * @memberof Robobo
     */
    readTapSensor() {
        return {
            x: this.rem.getTapCoord('x'),
            y: this.rem.getTapCoord('y'),
            zone: this.rem.getTapZone()
        }
    }

    /**
     * Resets the tap sensor value
     *
     * @memberof Robobo
     */
    resetTapSensor() {
        this.rem.resetTapSensor();
    }

    /**
     * Reads the orientation sensor
     * Warning: This sensor may not be available on all the devices
     *
     * @returns The orientation of the smartphone (yaw, pitch and roll)
     * @memberof Robobo
     */
    readOrientationSensor() {
        return {
            yaw: this.rem.getOrientation('yaw'),
            pitch: this.rem.getOrientation('pitch'),
            roll: this.rem.getOrientation('roll')
        }
    }
    /**
     * Reads the acceleration sensor
     *
     * @returns The current acceleration of the smartphone (x, y, z)
     * @memberof Robobo
     */
    readAccelerationSensor() {
        return {
            x: this.rem.getAcceleration('x'),
            y: this.rem.getAcceleration('y'),
            z: this.rem.getAcceleration('z')
        }
    }
    /**
     * Reads the brightness detected by the smartphone light sensor
     *
     * @returns The current brightness value
     * @memberof Robobo
     */
    readBrightnessSensor() {
        return this.rem.getLightBrightness();
    }

    /** Forces an update of the robot sensors.
     * This functiona must be call any time the robot sensors are used in a conditional loop.
     */
    async update() {
        return this.pause(0.01);
        
    }

    /** Pauses the program for the specified time (in seconds)
     * This functions requires 'await' to work properly
     * 
     * @param {float} time - Time in seconds (accepts decimals like 0.2)
     */
    async pause(time) {
        return new Promise(r => setTimeout(r, time*1000));
    }

    /** Writes the specified text in the Robobo log (console by default)
     * 
     * @param {string} text - The text to log
     */
    log(text) {
        console.log(text);
    }

    /**
     * Configures the callback that is called when a new note is detected
     *
     * @param {Function} fun The callback to be called
     * @memberof Robobo
     */
    whenANoteIsDetected(fun) {
        
        this.rem.registerCallback("onNewNote",()=>{
            if (this.NoteFlag){
                console.log("Warning: Note callback ignored, too much concurrent calls");
            }else{
                this.NoteFlag = true;
                fun();
                this.NoteFlag = false;
            }
        });

        
    }

    /**
     * Configures the callback that is called when a new face is detected
     *
     * @param {Function} fun The callback to be called
     * @memberof Robobo
     */
    whenANewFaceIsDetected(fun) {
        
        this.rem.registerCallback("onNewFace",()=>{
            if (this.NewNoteFlag){
                console.log("Warning:  callback ignored, too much concurrent calls");
            }else{
                this.NewNoteFlag = true;
                fun();
                this.NewNoteFlag = false;
            }
        });

        
    
    }
    /**
     * Configures the callback that is called when a face is lost
     *
     * @param {Function} fun The callback to be called
     * @memberof Robobo
     */
    whenAFaceIsLost(fun) {
       
        this.rem.registerCallback("onLostFace",()=>{
            if (this.FaceLostFlag){
                console.log("Warning: Face Lost callback ignored, too much concurrent calls");
            }else{
                this.FaceLostFlag = true;
                fun();
                this.FaceLostFlag = false;
            }
        })

        
    }

    /**
     * Configures the callback that is called when a new color blob is detected
     *
     * @param {Function} fun The callback to be called
     * @memberof Robobo
     */
    whenANewColorBlobIsDetected(fun) {
        
        this.rem.registerCallback("onNewBlob",()=>{
            if (this.BlobFlag){
                console.log("Warning: New Blob callback ignored, too much concurrent calls");
            }else{
                this.BlobFlag = true;
                fun();
                this.BlobFlag = false;
            }
        });

        
    }

    /**
     * Configures the callback that is called when a new tap is detected
     *
     * @param {Function} fun The callback to be called
     * @memberof Robobo
     */
    whenATapIsDetected(fun) {
    
        this.rem.registerCallback("onNewTap",()=>{
            if (this.TapFlag){
                console.log("Warning: New Tap callback ignored, too much concurrent calls");
            }else{            
            this.TapFlag = true;
            fun();
            this.TapFlag = false;
            }
        }
        );
        
    }

    /**
     * Configures the callback that is called when a new fling is detected
     *
     * @param {Function} fun The callback to be called
     * @memberof Robobo
     */
    whenAFlingIsDetected(fun) {
        
        this.rem.registerCallback("onNewFling",()=>{
            if (this.FlingFlag){
                console.log("Warning: New Fling callback ignored, too much concurrent calls");
            }else{
                this.FlingFlag = true;
                fun();
                this.FlingFlag = false;
            }
        });

        
    }
    /**
     * Changes the frequency of the status (LOW, NORMAL,HIGH, MAX)
     * @param {String} freq 
     */
    changeStatusFrequency(freq){
        this.rem.changeStatusFrequency(freq);
    }

    /**
     * Prints a message in the console
     * @param {String} message The text to output in the console
     */
    print(message){
        console.log(message);
    }
}

module.exports = Robobo;