Source: devices/device.js

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Device = void 0;
const events_1 = require("events");
const portoutputcommand_1 = require("../portoutputcommand");
const portoutputsleep_1 = require("../portoutputsleep");
const Consts = __importStar(require("../consts"));
const Debug = require("debug");
const debug = Debug("device");
/**
 * @class Device
 * @extends EventEmitter
 */
class Device extends events_1.EventEmitter {
    constructor(hub, portId, modeMap = {}, type = Consts.DeviceType.UNKNOWN) {
        super();
        this.autoSubscribe = true;
        this.values = {};
        this._bufferLength = 0;
        this._nextPortOutputCommands = [];
        this._transmittedPortOutputCommands = [];
        this._connected = true;
        this._modeMap = {};
        this._isVirtualPort = false;
        this._hub = hub;
        this._portId = portId;
        this._type = type;
        this._modeMap = modeMap;
        this._isWeDo2SmartHub = (this.hub.type === Consts.HubType.WEDO2_SMART_HUB);
        this._isVirtualPort = this.hub.isPortVirtual(portId);
        const eventAttachListener = (event) => {
            if (event === "detach") {
                return;
            }
            if (this.autoSubscribe) {
                if (this._modeMap[event] !== undefined) {
                    this.subscribe(this._modeMap[event]);
                }
            }
        };
        const deviceDetachListener = (device) => {
            if (device.portId === this.portId) {
                this._connected = false;
                this.hub.removeListener("detach", deviceDetachListener);
                this.emit("detach");
            }
        };
        for (const event in this._modeMap) {
            if (this.hub.listenerCount(event) > 0) {
                eventAttachListener(event);
            }
        }
        this.hub.on("newListener", eventAttachListener);
        this.on("newListener", eventAttachListener);
        this.hub.on("detach", deviceDetachListener);
    }
    /**
     * @readonly
     * @property {boolean} connected Check if the device is still attached.
     */
    get connected() {
        return this._connected;
    }
    /**
     * @readonly
     * @property {Hub} hub The Hub the device is attached to.
     */
    get hub() {
        return this._hub;
    }
    get portId() {
        return this._portId;
    }
    /**
     * @readonly
     * @property {string} portName The port the device is attached to.
     */
    get portName() {
        return this.hub.getPortNameForPortId(this.portId);
    }
    /**
     * @readonly
     * @property {number} type The type of the device
     */
    get type() {
        return this._type;
    }
    get typeName() {
        return Consts.DeviceTypeNames[this.type];
    }
    /**
     * @readonly
     * @property {number} mode The mode the device is currently in
     */
    get mode() {
        return this._mode;
    }
    get isWeDo2SmartHub() {
        return this._isWeDo2SmartHub;
    }
    /**
     * @readonly
     * @property {boolean} isVirtualPort Is this device attached to a virtual port (ie. a combined device)
     */
    get isVirtualPort() {
        return this._isVirtualPort;
    }
    writeDirect(mode, data, interrupt = false) {
        if (this.isWeDo2SmartHub) {
            return this.send(Buffer.concat([Buffer.from([this.portId, 0x01, 0x02]), data]), Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE).then(() => { return Consts.CommandFeedback.FEEDBACK_DISABLED; });
        }
        else {
            return this.sendPortOutputCommand(Buffer.concat([Buffer.from([0x51, mode]), data]), interrupt);
        }
    }
    send(data, characteristic = Consts.BLECharacteristic.LPF2_ALL) {
        this._ensureConnected();
        return this.hub.send(data, characteristic);
    }
    subscribe(mode) {
        this._ensureConnected();
        if (mode !== this._mode) {
            this.hub.subscribe(this.portId, this.type, mode);
        }
    }
    unsubscribe(mode) {
        this._ensureConnected();
    }
    receive(message) {
        this.notify("receive", { message });
    }
    notify(event, values) {
        this.values[event] = values;
        this.emit(event, values);
        if (this.hub.listenerCount(event) > 0) {
            this.hub.emit(event, this, values);
        }
    }
    requestUpdate() {
        this.send(Buffer.from([0x21, this.portId, 0x00]));
    }
    transmitNextPortOutputCommand() {
        if (!this.connected) {
            this._transmittedPortOutputCommands.forEach(command => command.resolve(Consts.CommandFeedback.FEEDBACK_MISSING));
            this._transmittedPortOutputCommands = [];
            this._nextPortOutputCommands.forEach(command => command.resolve(Consts.CommandFeedback.TRANSMISSION_DISCARDED));
            this._nextPortOutputCommands = [];
            return;
        }
        if (!this._nextPortOutputCommands.length)
            return;
        const nextCommand = this._nextPortOutputCommands[0];
        if (nextCommand instanceof portoutputsleep_1.PortOutputSleep) {
            if (nextCommand.state === Consts.CommandFeedback.EXECUTION_PENDING) {
                nextCommand.state = Consts.CommandFeedback.EXECUTION_BUSY;
                debug("sleep command ", nextCommand.duration);
                setTimeout(() => {
                    if (nextCommand.state !== Consts.CommandFeedback.EXECUTION_BUSY)
                        return;
                    const command = this._nextPortOutputCommands.shift();
                    if (command)
                        command.resolve(Consts.CommandFeedback.EXECUTION_COMPLETED);
                    this.transmitNextPortOutputCommand();
                }, nextCommand.duration);
            }
            return;
        }
        if (this._bufferLength !== this._transmittedPortOutputCommands.length)
            return;
        if (this._bufferLength < 2 || nextCommand.interrupt) {
            if (nextCommand.state === Consts.CommandFeedback.TRANSMISSION_PENDING) {
                nextCommand.state = Consts.CommandFeedback.TRANSMISSION_BUSY;
                debug("transmit command ", nextCommand.startupAndCompletion, nextCommand.data);
                this.send(Buffer.concat([Buffer.from([0x81, this.portId, nextCommand.startupAndCompletion]), nextCommand.data])).then(() => {
                    if (nextCommand.state !== Consts.CommandFeedback.TRANSMISSION_BUSY)
                        return;
                    const command = this._nextPortOutputCommands.shift();
                    if (command instanceof portoutputcommand_1.PortOutputCommand)
                        this._transmittedPortOutputCommands.push(command);
                });
                this.transmitNextPortOutputCommand(); // if the next command is PortOutputSleep this starts sleep timeout
                // one could start a timer here to ensure finish function is called
            }
        }
    }
    sendPortOutputCommand(data, interrupt = false) {
        if (this.isWeDo2SmartHub) {
            throw new Error("PortOutputCommands are not available on the WeDo 2.0 Smart Hub");
            return;
        }
        const command = new portoutputcommand_1.PortOutputCommand(data, interrupt);
        if (interrupt) {
            this._nextPortOutputCommands.forEach(command => {
                if (command.state !== Consts.CommandFeedback.TRANSMISSION_BUSY) {
                    command.resolve(Consts.CommandFeedback.TRANSMISSION_DISCARDED);
                }
            });
            this._nextPortOutputCommands = this._nextPortOutputCommands.filter(command => command.state === Consts.CommandFeedback.TRANSMISSION_BUSY);
        }
        this._nextPortOutputCommands.push(command);
        process.nextTick(() => this.transmitNextPortOutputCommand());
        return command.promise;
    }
    setMode(message) {
        this._mode = message;
    }
    addPortOutputSleep(duration) {
        const command = new portoutputsleep_1.PortOutputSleep(duration);
        this._nextPortOutputCommands.push(command);
        return command.promise;
    }
    finish(message) {
        debug("recieved command feedback ", message);
        if ((message & 0x08) === 0x08)
            this._bufferLength = 0;
        else if ((message & 0x01) === 0x01)
            this._bufferLength = 1;
        else if ((message & 0x10) === 0x10)
            this._bufferLength = 2;
        const completed = ((message & 0x02) === 0x02);
        const discarded = ((message & 0x04) === 0x04);
        switch (this._transmittedPortOutputCommands.length) {
            case 0:
                break;
            case 1:
                if (!this._bufferLength && completed && !discarded) {
                    this._complete();
                }
                else if (!this._bufferLength && !completed && discarded) {
                    this._discard();
                }
                else if (this._bufferLength && !completed && !discarded) {
                    this._busy();
                }
                else {
                    this._missing();
                }
                break;
            case 2:
                if (!this._bufferLength && completed && discarded) {
                    this._discard();
                    this._complete();
                }
                else if (!this._bufferLength && completed && !discarded) {
                    this._complete();
                    this._complete();
                }
                else if (!this._bufferLength && !completed && discarded) {
                    this._discard();
                    this._discard();
                }
                else if (this._bufferLength === 1 && completed && !discarded) {
                    this._complete();
                    this._busy();
                }
                else if (this._bufferLength === 1 && !completed && discarded) {
                    this._discard();
                    this._busy();
                }
                else if (this._bufferLength === 1 && completed && discarded) {
                    this._missing();
                    this._busy();
                }
                else if (this._bufferLength === 2 && !completed && !discarded) {
                    this._busy();
                    this._pending();
                }
                else {
                    this._missing();
                    this._missing();
                }
                break;
            case 3:
                if (!this._bufferLength && completed && discarded) {
                    this._discard();
                    this._discard();
                    this._complete();
                }
                else if (!this._bufferLength && completed && !discarded) {
                    this._complete();
                    this._complete();
                    this._complete();
                }
                else if (!this._bufferLength && !completed && discarded) {
                    this._discard();
                    this._discard();
                    this._discard();
                }
                else if (this._bufferLength === 1 && completed && discarded) {
                    this._discard();
                    this._complete();
                    this._busy();
                }
                else if (this._bufferLength === 1 && completed && !discarded) {
                    this._complete();
                    this._complete();
                    this._busy();
                }
                else if (this._bufferLength === 1 && !completed && discarded) {
                    this._discard();
                    this._discard();
                    this._busy();
                }
                else if (this._bufferLength === 1 && !completed && !discarded) {
                    this._missing();
                    this._missing();
                    this._busy();
                }
                // third command can only be interrupt, if this._bufferLength === 2 it was queued
                else {
                    this._missing();
                    this._missing();
                    this._missing();
                }
                break;
        }
        this.transmitNextPortOutputCommand();
    }
    _ensureConnected() {
        if (!this.connected) {
            throw new Error("Device is not connected");
        }
    }
    _complete() {
        const command = this._transmittedPortOutputCommands.shift();
        if (command)
            command.resolve(Consts.CommandFeedback.EXECUTION_COMPLETED);
    }
    _discard() {
        const command = this._transmittedPortOutputCommands.shift();
        if (command)
            command.resolve(Consts.CommandFeedback.EXECUTION_DISCARDED);
    }
    _missing() {
        const command = this._transmittedPortOutputCommands.shift();
        if (command)
            command.resolve(Consts.CommandFeedback.FEEDBACK_MISSING);
    }
    _busy() {
        const command = this._transmittedPortOutputCommands[0];
        if (command)
            command.state = Consts.CommandFeedback.EXECUTION_BUSY;
    }
    _pending() {
        const command = this._transmittedPortOutputCommands[1];
        if (command)
            command.state = Consts.CommandFeedback.EXECUTION_PENDING;
    }
}
exports.Device = Device;
//# sourceMappingURL=device.js.map