KMMotorOneBLE.js

/***
 *KMMotorOneBLE.js
 * Created by Harada Hiroshi on 2017/12/07.
 *
 * 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';

let EventEmitter = require("events").EventEmitter;
let KMComBLE = require('./KMComBLE');
let KMMotorCommandKMOne=require('./KMMotorCommandKMOne.js');

let noble = require('@abandonware/noble');
/**
 * @typedef {Object< motorUUID,KMMotorOneBLE >} _motorsByUUID
 * @ignore
 */
let _motorsByUUID={};
/**
 * @typedef {Object< motorName,KMMotorOneBLE >} _motorsByName
 * @ignore
 */
let _motorsByName={};
let _startScanTimeoutID=0;
let _KMMotorOneBLEStaticEventEmitter=new EventEmitter(null);

process.on('exit', function() {
    KMMotorOneBLE.allDisConnect();
});


noble.once('stateChange', function(){
    _KMMotorOneBLEStaticEventEmitter.emit("adapterStateChange",noble.state);
});

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


    /**
     * イベントタイプ定数
     * @readonly
     * @enum {string}
     * @property {string} adapterStateChange - BLEアダプターの有効無効の変化
     * <br>return:"poweredOn"||"unknown"
     * @property {string} discoverMotor - モーター発見時(毎回)
     * <br>return:function({@link KMMotorOneBLE})
     * @property {string} discoverNewMotor - 新規モーターの発見時(1回のみ)
     * <br>return:function({@link KMMotorOneBLE})
     * @property {string} scanTimeout - スキャンのタイムアウトで終了時
     * <br>return:function()
     */
    static get EVENT_TYPE(){
          return {
             adapterStateChange:"adapterStateChange",
             discoverMotor:"discoverMotor",
             discoverNewMotor:"discoverNewMotor",
             scanTimeout:"scanTimeout"
          };
    }

    /********************************************
     * static Scanning
     ********************************************/
    /**
     * BLEアダプターの状態
     * @readonly
     * @returns {*}
     */
    static get bleState(){
        return noble.state;
    }
    /**
     * APIが認識しているモーターのインスタンスリスト(プロパティはモーター名)
     * <br>ex) {<motorName1>:{@link KMMotorOneBLE},<motorName2>:{@link KMMotorOneBLE},,,}
     * @readonly
     * @type {_motorsByName}
     */
    static get motors(){
        return _motorsByName;
    }

    /**
     * APIが認識しているモーターのインスタンスリスト(プロパティはUUID)
     * <br>ex) {<motorUUID1>:{@link KMMotorOneBLE},<motorUUID2>:{@link KMMotorOneBLE},,,}
     * @readonly
     * @type {_motorsByUUID}
     */
    static get motorsByUUID(){
        return _motorsByUUID;
    }

    /**
     * BLEデバイスのスキャンを開始し、検出したモーターを全てインスタンス化する
     * @param {number} time スキャンの継続時間(ms)
     * @param {boolean} allowDuplicates 重複検出を許可
     */
    static startScanToCreateInstance(time = 2000,allowDuplicates=false){
        clearTimeout(_startScanTimeoutID);
        if(noble.state === 'poweredOn'){
            noble.removeListener('discover',KMMotorOneBLE._discoverLis);
            noble.on('discover',KMMotorOneBLE._discoverLis);
            console.log('startScanToCreateInstance');
            noble.startScanning([KMComBLE.MOTOR_BLE_SERVICE_UUID],allowDuplicates);//info::UUIDを指定しないとスキャン時にadvertisement.serviceUuidsが取得出来ない
            _startScanTimeoutID=setTimeout(()=>{
                KMMotorOneBLE.stopScan();
                _KMMotorOneBLEStaticEventEmitter.emit(KMMotorOneBLE.EVENT_TYPE.scanTimeout);
            },time);
        }else if(noble.state === 'unknown') {
            noble.once('stateChange', function(){
                KMMotorOneBLE.startScanToCreateInstance(time,allowDuplicates);
            });
        }else{
            //BLE無効時
            console.log('Could not start scanning, state is:'+noble.state);
        }
    }

    /**
     * BLEデバイスのスキャンの停止
     */
    static stopScan(){
        clearTimeout(_startScanTimeoutID);
        noble.removeListener('discover',KMMotorOneBLE._discoverLis);
        if(noble.state === 'poweredOn') {
            noble.stopScanning();
            console.log('stopScan');
        }
    }

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

    /**
     *  全ての仮想モーターを消去
     */
    static clearMotors(){
        KMMotorOneBLE.allDisConnect();
        _motorsByUUID={};
        KMMotorOneBLE._restructMotorsByName();
    }

    /**
     * 検出時リスナー
     * 既に同じpheralのデバイスが存在する場合は、それを返す
     * @param nobleperipheral
     * @private
     */
    static _discoverLis(nobleperipheral){
        //info::デバイスを選別する。startScanToCreateInstance中に別のクラスからnoble.startScanningした場合、Motor以外のデバイスも拾う可能性がある為。
        if(!(nobleperipheral.advertisement&&nobleperipheral.advertisement.serviceUuids&&nobleperipheral.advertisement.serviceUuids[0]===KMComBLE.MOTOR_BLE_SERVICE_UUID)){
            return;
        }

        console.log('Discover device:'+nobleperipheral.uuid);

        if(!_motorsByUUID[nobleperipheral.uuid]){
           let motor= new KMMotorOneBLE(nobleperipheral);//info::検出したモーターは全てインスタンス化
            _KMMotorOneBLEStaticEventEmitter.emit(KMMotorOneBLE.EVENT_TYPE.discoverNewMotor,_motorsByUUID[nobleperipheral.uuid]);
        }
        if(_motorsByUUID[nobleperipheral.uuid]){
            _KMMotorOneBLEStaticEventEmitter.emit(KMMotorOneBLE.EVENT_TYPE.discoverMotor,_motorsByUUID[nobleperipheral.uuid]);
        }

    }

    /**
     * モーターのリストを再インデックス
     * @private
     */
    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
     ********************************************/
    /**
     * KMMotorOneBLE
     * @constructor
     * @extends KMMotorCommandKMOne
     * @param {object} noblePeripheral noble Peripheralオブジェクト
     * @description 通常はstartScanToCreateInstance()メソッドから自動生成される為、単体では使用しない。<br>
     * モーター以外のデバイスの接続等でnoble APIをプログラム全体で使用している場合にnoblePeripheralと仮想モーターを関連付ける場合に使用。
     */
    constructor(noblePeripheral){
        super(KMMotorCommandKMOne.KM_CONNECT_TYPE.BLE,new KMComBLE(noblePeripheral));
        if(!_motorsByUUID[noblePeripheral.uuid]){
            _motorsByUUID[noblePeripheral.uuid]=this;
            KMMotorOneBLE._restructMotorsByName();
        }
    }

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

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

module.exports =KMMotorOneBLE;