KMMotorOneUSBSerial.js

/***
 *KMMotorOneUSBSerial.js
 * Created by Harada Hiroshi on 2018/02/27.
 *
 * Copyright (c) 2017 Keigan Inc. https://keigan-motor.com/
 * This software is released under the MIT License.
 * http://opensource.org/licenses/mit-license.php
 */

'use strict';

const EventEmitter = require("events").EventEmitter;
const KMUtl= require('./KMUtl');
const KMComUSBSerial = require('./KMComUSBSerial');
const KMMotorCommandKMOne=require('./KMMotorCommandKMOne.js');
const serialport = require('serialport');
const {performance} = require('perf_hooks');
/**
 * @typedef {Object< motorUUID,KMMotorOneUSBSerial >} _motorsByUUID
 * @ignore
 */
let _motorsByUUID={};
/**
 * @typedef {Object< motorName,KMMotorOneUSBSerial >} _motorsByName
 * @ignore
 */
let _motorsByName={};
let _KMMotorOneUSBSerialStaticEventEmitter=new EventEmitter(null);

process.on('exit', function() {
    //todo::プロセス終了時のUSBデバイス切断 必要なら
});

/**
 * @classdesc KM-1のUSBシリアル接続用 仮想デバイス
 */
class KMMotorOneUSBSerial extends KMMotorCommandKMOne{
    /********************************************
     * static events
     ********************************************/
    /**
     * イベントリスナを追加する. Synonym for EventEmitter
     * @param {KMMotorOneUSBSerial.EVENT_TYPE} type イベントのタイプ.
     * @param {function(object)} listener 追加するイベントリスナ.
     */
    static on(type, listener){
        _KMMotorOneUSBSerialStaticEventEmitter.on(type,listener);
    }
    /**
     * 1回限りのイベントリスナを追加する. Synonym for EventEmitter
     * @param {KMMotorOneUSBSerial.EVENT_TYPE} type イベントのタイプ.
     * @param {function(object)} listener 追加するイベントリスナ.
     */
    static once(type, listener){
        _KMMotorOneUSBSerialStaticEventEmitter.once(type,listener);
    }
    /**
     * イベントリスナを削除する Synonym for EventEmitter
     * @param {KMMotorOneUSBSerial.EVENT_TYPE} type イベントのタイプ
     * @param {function(object)} listener 削除するイベントリスナ
     */
    static removeListener(type, listener){
        _KMMotorOneUSBSerialStaticEventEmitter.removeListener(type,listener);
    }
    /**
     * 全てのイベントリスナを削除する Synonym for EventEmitter
     * @param {KMMotorOneUSBSerial.EVENT_TYPE} type イベントのタイプ.
     */
    static removeAllListeners(type){
        _KMMotorOneUSBSerialStaticEventEmitter.removeAllListeners(type);
    }

    /**
     * イベントタイプ定数
     * @readonly
     * @enum {string}
     * @property {string} discoverMotor - モーター発見時(毎回)
     * <br>return:function({@link KMMotorOneUSBSerial})
     * @property {string} discoverNewMotor - 新規モーターの発見時(1回のみ)
     * <br>return:function({@link KMMotorOneUSBSerial})
     * @property {string} scanTimeout - スキャンのタイムアウトで終了時
     * <br>return:function()
     */
    static get EVENT_TYPE(){
        return {
            discoverMotor:"discoverMotor",
            discoverNewMotor:"discoverNewMotor",
            scanTimeout:"scanTimeout"
        };
    }
    /********************************************
     * static Method
     ********************************************/
    /**
     * USBデバイスのスキャンを開始し、検出したモーターを全てインスタンス化する
     */
    static startScanToCreateInstance(){
       return new Promise((resolve,reject)=> {
           serialport.list().then((ports)=>{
               let pmar=[];
               ports.forEach((port)=> {
                   let portName=port.comName;
                   if(port.manufacturer!=="FTDI"||(parseInt(port.productId,16)).toString(16)!=="6015"){//KM1 USBchip
                       return;
                   }
                   let serialNumber=port.serialNumber;//info::デバイス固有ID識別用。 UUID的な

                   if(!_motorsByUUID[serialNumber]){
                       let pm=new Promise(function(s_resolve, s_reject) {
                           KMMotorOneUSBSerial._getMotorNameSerial(portName).then((name)=>{
                               let motor= new KMMotorOneUSBSerial(portName,serialNumber,name);//info::検出したモーターは全てインスタンス化

                               _KMMotorOneUSBSerialStaticEventEmitter.emit(KMMotorOneUSBSerial.EVENT_TYPE.discoverNewMotor,_motorsByUUID[serialNumber]);
                               /**
                                * 新規モーターの発見時
                                * @event KMMotorOneUSBSerial#discoverNewMotor
                                * @type {KMMotorOneUSBSerial}
                                * @property {object} KMMotorOneUSBSerial {@link KMMotorOneUSBSerial}
                                * @example
                                *
                                * KMMotorOneUSBSerial.on(KMMotorOneUSBSerial.EVENT_TYPE.discoverNewMotor,function(kMMotorOne){
                                *      console.log('onDiscoverNewMotor:'+kMMotorOne.deviceInfo.name);
                                * });
                                */

                               _KMMotorOneUSBSerialStaticEventEmitter.emit(KMMotorOneUSBSerial.EVENT_TYPE.discoverMotor,_motorsByUUID[serialNumber]);
                               /**
                                * モーターの発見時
                                * @event KMMotorOneUSBSerial#discoverMotor
                                * @type {KMMotorOneUSBSerial}
                                * @property {object} KMMotorOneUSBSerial {@link KMMotorOneUSBSerial}
                                * @example
                                *
                                * KMMotorOneUSBSerial.on(KMMotorOneUSBSerial.EVENT_TYPE.discoverNewMotor,function(kMMotorOne){
                                *      console.log('onDiscoverMotor:'+kMMotorOne.deviceInfo.name);
                                * });
                                */
                               s_resolve(true);
                           }).catch((e)=>{
                               console.log("getMotorName Err",e);
                               let motor= new KMMotorOneUSBSerial(portName,serialNumber);//info::検出したモーターは全てインスタンス化
                               _KMMotorOneUSBSerialStaticEventEmitter.emit(KMMotorOneUSBSerial.EVENT_TYPE.discoverNewMotor,_motorsByUUID[serialNumber]);
                               _KMMotorOneUSBSerialStaticEventEmitter.emit(KMMotorOneUSBSerial.EVENT_TYPE.discoverMotor,_motorsByUUID[serialNumber]);
                               s_resolve(true);
                           });
                       });
                       pmar.push(pm);
                   }
                   if(_motorsByUUID[serialNumber]){
                       _KMMotorOneUSBSerialStaticEventEmitter.emit(KMMotorOneUSBSerial.EVENT_TYPE.discoverMotor,_motorsByUUID[serialNumber]);
                   }
               });

               Promise.all(pmar).then(()=>{
                   KMMotorOneUSBSerial._restructMotorsByName();
                   resolve(_motorsByUUID);
               });
           }).catch((err)=>{
               console.log(err);
               reject(err);
           });
        });
    }

    /**
     * Motor名取得用にコマンド発行->受信
     * @param comName
     * @returns {Promise<any>}
     * @private
     * @ignore
     */
    static _getMotorNameSerial(comName){
        return new Promise((resolve,reject)=> {
            let execTimeoutms=5000;

            //motorNameの取得の為、一度接続する
            let kcom= new KMComUSBSerial(comName);

            let timeOutIntevalID=setTimeout(()=>{
                kcom.disConnect();
                reject("getMotorName timeout");
            },execTimeoutms);

            kcom.onConnect=(connector)=>{
                setTimeout(()=>{
                    kcom._sendMotorCommand('MOTOR_SETTING',kcom._MOTOR_RX_READREGISTER_COMMAND.deviceName);//DeviceName
                },500);
            };
            kcom.onConnectFailure=(connector, err)=>{
                reject("getMotorName connectFailure");
            };

            kcom.onMotorSetting=(registerCmd,res)=>{
                //名前の取得
                if(registerCmd===kcom._MOTOR_RX_READREGISTER_COMMAND.deviceName){
                    let rName=typeof res.deviceName==="string"?res.deviceName.split('#')[0]:null;
                    kcom.disConnect();
                    clearTimeout(timeOutIntevalID);
                    resolve(rName);
                }
            };

            kcom.connect();

        });
    }
    /**
     * 全てのモーターのUSB接続を解除
     */
    static allDisConnect(){
        Object.keys(_motorsByUUID).forEach((key)=> {
            _motorsByUUID[key]._KMCom.disConnect();
        });
    }

    /**
     *  全ての仮想モーターを消去
     */
    static clearMotors(){
        KMMotorOneUSBSerial.allDisConnect();
        _motorsByUUID={};
        KMMotorOneUSBSerial._restructMotorsByName();
    }
    /**
     * APIが認識しているモーターのインスタンスリスト(プロパティはモーター名)
     * <br>ex) {<motorName1>:{@link KMMotorOneUSBSerial},<motorName2>:{@link KMMotorOneUSBSerial},,,}
     * <br>一度作成された仮想モーターは物理的に取り外してもclearMotors()を行うまで消えない
     * @readonly
     * @type {_motorsByName}
     */
    static get motors(){
        return _motorsByName;
    }

    /**
     * APIが認識しているモーターのインスタンスリスト(プロパティはUUID)
     * <br>ex) {<motorUUID1>:{@link KMMotorOneUSBSerial},<motorUUID2>:{@link KMMotorOneUSBSerial},,,}
     * <br>一度作成された仮想モーターは物理的に取り外してもclearMotors()を行うまで消えない
     * @readonly
     * @type {_motorsByUUID}
     */
    static get motorsByUUID(){
        return _motorsByUUID;
    }
    /**
     * モーターのリストを再インデックス
     * @private
     * @ignore
     */
    static _restructMotorsByName(){
        let list={};
        Object.keys(_motorsByUUID).forEach((key)=>{
            let name=  _motorsByUUID[key].deviceInfo.name? _motorsByUUID[key].deviceInfo.name.split('#')[0]:null;
            if(name){
                list[name]=_motorsByUUID[key];
            }else{
                console.log('Err motor name not found');
            }
        });
        _motorsByName=list;
    }
    /********************************************
     * instance Method
     ********************************************/
    /**
     * KMMotorOneUSBSerial
     * @constructor
     * @extends {KMMotorCommandKMOne}
     * @param portName
     * @param id UniqueID デバイスのユニーク名 内部生成用。通常は指定しない (手動接続時は常にnull)
     * @param defaultMotorName string 内部生成用。通常は指定しない (手動接続時は常にnull)
     */
    constructor(portName,id=null,defaultMotorName=null){
        let cid=!id?"id_"+String(performance.now()).split(".").join("")+"_"+portName:id;
        super(KMMotorCommandKMOne.KM_CONNECT_TYPE.SERIAL,new KMComUSBSerial(portName,cid,defaultMotorName));
        if(!_motorsByUUID[cid]){
            _motorsByUUID[cid]=this;
        }

        this.on(this.EVENT_TYPE.init,(kMDeviceInfo)=>{
            KMMotorOneUSBSerial._restructMotorsByName();
        });
    }

    /**
     * モーターと接続する
     */
    connect(){
       this._KMCom.connect();
    }

    /**
     * モーターと切断
     */
    disConnect(){
        this._KMCom.disConnect();
    }


}

module.exports =KMMotorOneUSBSerial;