text: /* eslint-disable no-negated-condition */
  /* eslint-disable no-mixed-operators */
  /* eslint-disable prefer-template */
  /* eslint-disable key-spacing */
  /* eslint-disable max-len */
  /* eslint-disable camelcase */
  /* eslint-disable no-console */
  const ArgumentType = require('../../extension-support/argument-type');
  const BlockType = require('../../extension-support/block-type');
  // const log = require('../../util/log');
  const cast = require('../../util/cast');
  const MathUtil = require('../../util/math-util');
  const formatMessage = require('format-message');
  const BLE = require('../../io/ble');
  const Base64Util = require('../../util/base64-util');
  
  /**
   * Icon png to be displayed at the left edge of each extension block, encoded as a data URI.
   * @type {string}
   */
  // eslint-disable-next-line max-len
  const blockIconURI = 'data:image/png;base64,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';
  
  const BLECommand = {
      CMD_CHECK_VERSION:                  0x01,
      CMD_LIGHT_RING:                     0x18,
      CMD_SENSOR_DETECT:                  0x20,
      CMD_GET_SENSOR_VALUE:               0x28,
      CMD_EVENT_DETECT:                   0x32,
      CMD_BLE_MODE_CONFIG:                0x33,
      CMD_SET_NEW_PROTOCOL:               0x7e,
      CMD_HEARTBEAT:                      0x87,
      CMD_GENERAL_RSP:                    0x88
  };
  
  const LightRingCommand = {
      SINGLE_LIGHT_SPECIFY_COLOR:         0x01,
      ALL_LIGHT_SPECIFY_COLOR:            0x02,
      ALL_LIGHT_RGB_COLOR:                0x03,
      LIGHT_EFFECT:                       0x06,
      LAMP_PANEL_RGB:                     0x07,
      SINGLE_LIGHT_RGB_COLOR:             0x08
  };
  
  const SensorDetectCommand = {
      COLOR_DETECT:                       0x01,
      MOTION_STATUS_DETECT:               0x02,
      SOUND_DETECT:                       0x03,
      OBSTACLE_DETECT:                    0x04,
      LIGHT_DETECT:                       0x05,
      IR_MESSAGE:                         0x06
  };
  
  const GetSensorValueCommand = {
      MOTION_SENSOR:                      0x01,
      LIGHT_SENSOR:                       0x02
  };
  
  const MATATACON_LATEST_FIRMWARE_VERSION = '1.1.0'; // 最新固件在这里设置
  
  /**
   * A time interval to wait (in milliseconds) before reporting to the BLE socket
   * that data has stopped coming from the peripheral.
   */
  const BLETimeout = 4500;
  
  /**
   * A time interval to wait (in milliseconds) while a block that sends a BLE message is running.
   * @type {number}
   */
  const BLESendInterval = 1000;
  
  /**
   * A string to report to the BLE socket when the matataCon has stopped receiving data.
   * @type {string}
   */
  const BLEDataStoppedError = 'matatacon extension stopped receiving data';
  
  const BLEINFO = {
      service: '6e400001-b5a3-f393-e0a9-e50e24dcca9e',
      rxChar:  '6e400003-b5a3-f393-e0a9-e50e24dcca9e',
      txChar:  '6e400002-b5a3-f393-e0a9-e50e24dcca9e',
      name:    'MatataCon',
      namePrefix: 'MatataCon'
  };
  
  /**
   * Manage communication with a MatataCon peripheral over a Scrath Link client socket.
   */
  class MatataCon {
      /**
       * Construct a MatataCon communication object.
       * @param {Runtime} runtime - the Scratch 3.0 runtime
       * @param {string} extensionId - the id of the extension
       */
      constructor (runtime, extensionId) {
  
          /**
           * The Scratch 3.0 runtime used to trigger the green flag button.
           * @type {Runtime}
           * @private
           */
          this._runtime = runtime;
  
          /**
           * The BluetoothLowEnergy connection socket for reading/writing peripheral data.
           * @type {BLE}
           * @private
           */
          this._ble = null;
          this._runtime.registerPeripheralExtension(extensionId, this);
  
          /**
           * The id of the extension this peripheral belongs to.
           */
          this._extensionId = extensionId;
  
          /**
           * Interval ID for data reading timeout.
           * @type {number}
           * @private
           */
          this._timeoutID = null;
  
          /**
           * A flag that is true while we are busy sending data to the BLE socket.
           * @type {boolean}
           * @private
           */
          this._busy = false;
  
          /**
           * ID for a timeout which is used to clear the busy flag if it has been
           * true for a long time.
           */
          this._busyTimeoutID = null;
  
          /**
           * Processing for receiving data frames
           */
          this._receivedCommand = new Array();
          this._receivedCommandStart = false;
          this._receivedCommandLength = 0;
          this._lastFrameReservedData = null;
          this.device = 'MatataCon';
          this.version = new Array(0, 0, 0);
          this.newProtocolSupportCon = false;
          this.newProtocolSupportCar = false;
          this.sensorMode = true;
  
          this.commandSyncFlag = {
              lightRingLedSingleSet1Flag: false,
              lightRingLedSingleSet2Flag: false,
              lightRingLedSingleSet3Flag: false,
              lightRingLedSingleSet4Flag: false,
              lightRingEffectFlag: false,
              lightRingAllLedOffFlag: false,
              sendIRmessageFlag: false,
              waitIRmessageFlag: false,
              sensorDetectMotionStatusFlag: false,
              sensorDetectColorTypeFlag: false,
              buttonEventDetectFlag: false,
              motionEventDetectFlag: false,
              soundEventDetectFlag: false,
              obstaclesAheadFlag: false,
              brightnessDetectFlag: false,
              getMotionPitchFlag: false,
              getMotionRollFlag: false,
              getMotionYawFlag: false,
              getShakingStrengthFlag: false,
              getAmbientLightIntensityFlag: false,
              getRGBColorRedFlag: false,
              getRGBColorGreenFlag: false,
              getRGBColorBlueFlag: false,
              getAccelerationXFlag: false,
              getAccelerationYFlag: false,
              getAccelerationZFlag: false,
              setNewProtocolFlag: false,
              setBleModeFlag: false,
              getVersionFlag: false
          };
          
          this.wait_ir_message = 0;
          this.motion_time = 0;
          this.motion_title_status = 0;
          this.key_value = 0;
          this.sound_flag = false;
          this.obstacles_ahead_flag = false;
          this.brightness_flag = false;
          this.motion_status_match = false;
          this.color_type_match = false;
          this.pitch = 0;
          this.roll = 0;
          this.yaw = 0;
          this.shaking_strength = 0;
          this.ambient_light_intensity = 0;
          this.color_red = 0;
          this.color_green = 0;
          this.color_blue = 0;
          this.acc_x = 0;
          this.acc_y = 0;
          this.acc_z = 0;
  
          this.reset = this.reset.bind(this);
          this._onConnect = this._onConnect.bind(this);
          this._onMessage = this._onMessage.bind(this);
      }
  
      /**
       * Called by the runtime when user wants to scan for a peripheral.
       */
      scan () {
          console.log('matataCon start scan...');
          if (this._ble) {
              this._ble.disconnect();
              console.log('can not search ble device');
          }
          this._ble = new BLE(this._runtime, this._extensionId, {
              filters: [
                  {services: [BLEINFO.service]},
                  {namePrefix: BLEINFO.namePrefix}
                  // {name: BLEINFO.name},
              ]
              // optionalServices: [BLEINFO.service]
          }, this._onConnect, this.reset);
      }
  
      /**
       * Called by the runtime when user wants to connect to a certain peripheral.
       * @param {number} id - the id of the peripheral to connect to.
       */
      connect (id) {
          if (this._ble) {
              this._ble.connectPeripheral(id);
          }
      }
  
      /**
       * Disconnect from the matatacon.
       */
      disconnect () {
          if (this._ble) {
              console.log('matatacon disconnect');
              this._ble.disconnect();
          }
  
          this.reset();
      }
  
      /**
       * Reset all the state and timeout/interval ids.
       */
      reset () {
          if (this._timeoutID) {
              window.clearTimeout(this._timeoutID);
              this._timeoutID = null;
          }
      }
  
      /**
       * Return true if connected to the matatacon.
       * @return {boolean} - whether the matatacon is connected.
       */
      isConnected () {
          let connected = false;
          if (this._ble) {
              connected = this._ble.isConnected();
          }
          return connected;
      }
  
      /**
       * Send a message to the peripheral BLE socket.
       * @param {Uint8Array} message - the message to write
       */
      send (message) {
          if (!this.isConnected()) return;
          if (this._busy) return;
          // Set a busy flag so that while we are sending a message and waiting for
          // the response, additional messages are ignored.
          this._busy = true;
  
          // Set a timeout after which to reset the busy flag. This is used in case
          // a BLE message was sent for which we never received a response, because
          // e.g. the peripheral was turned off after the message was sent. We reset
          // the busy flag after a while so that it is possible to try again later.
          this._busyTimeoutID = window.setTimeout(() => {
              this._busy = false;
          }, 3000);
          const output = new Uint8Array(message.length);
          for (let i = 0; i < message.length; i++) {
              output[i] = message[i];
          }
          const data = Base64Util.uint8ArrayToBase64(output);
  
          this._ble.write(BLEINFO.service, BLEINFO.txChar, data, 'base64', false).then(
              () => {
                  this._busy = false;
                  window.clearTimeout(this._busyTimeoutID);
              }
          );
      }
  
      crc16 (buffer, crc_init) {
          let crc = crc_init & 0xffff;
          for (let i = 0; i < buffer.length; i++) {
              crc = ((crc >> 8) | (crc << 8)) & 0xffff;
              crc ^= buffer[i] & 0xffff;
              crc ^= ((crc & 0xff) >> 4) & 0xffff;
              crc ^= ((crc << 8) << 4) & 0xffff;
              crc ^= (((crc & 0xff) << 4) << 1) & 0xffff;
          }
          return crc;
      }
  
      packCommand (command_data) {
          const command_array = new Array();
          const message_len = command_data.length + 2;
          const message_len_array = new Array();
          message_len_array.push(message_len);
          let crc = this.crc16(message_len_array, 0xffff);
          crc = this.crc16(command_data, crc);
          command_array.push(0xfe);
          command_array.push(message_len);
          for (let i = 0; i < command_data.length; i++) {
              if (command_data[i] === 0xfe) {
                  command_array.push(0xfd);
                  command_array.push(0xde);
              } else if (command_data[i] === 0xfd) {
                  command_array.push(0xfd);
                  command_array.push(0xdd);
              } else {
                  command_array.push(command_data[i]);
              }
          }
          if ((crc >> 8) === 0xfe) {
              command_array.push(0xfd);
              command_array.push(0xde);
          } else if ((crc >> 8) === 0xfd) {
              command_array.push(0xfd);
              command_array.push(0xdd);
          } else {
              command_array.push(crc >> 8);;
          }
          if ((crc & 0xff) === 0xfe) {
              command_array.push(0xfd);
              command_array.push(0xde);
          } else if ((crc & 0xff) === 0xfd) {
              command_array.push(0xfd);
              command_array.push(0xdd);
          } else {
              command_array.push(crc & 0xff);
          }
          let log_string = "send: ";
          for (let i = 0; i < command_array.length; i++) {
              log_string = log_string + "0x" + command_array[i].toString(16) + ",";
          }
          console.log(log_string);
          return command_array;
      }
  
      depackCommand (command_data) {
          const command_data_temp = new Array();
          if (this._lastFrameReservedData !== null) {
              command_data_temp.push(this._lastFrameReservedData);
              this._lastFrameReservedData = null;
          }
          for (let i = 0; i < command_data.length; i++) {
              command_data_temp.push(command_data[i]);
          }
          for (let i = 0; i < command_data_temp.length; i++) {
              if ((command_data_temp[i] === 0xfe) && (this._receivedCommandStart === false)){
                  this._receivedCommand.push(0xfe);
                  this._receivedCommandStart = true;
              } else if (this._receivedCommandStart === true) {
                  if (command_data_temp[i] === 0xfd){
                      if (i === command_data_temp.length) {
                          this._lastFrameReservedData = 0xfd;
                          continue;
                      } else if (command_data_temp[i + 1] === 0xdd) {
                          this._receivedCommand.push(0xfd);
                          i++;
                          continue;
                      } else if (command_data_temp[i + 1] === 0xde) {
                          this._receivedCommand.push(0xfe);
                          i++;
                          continue;
                      }
                      this._receivedCommand.push(0xfd);
                  } else {
                      this._receivedCommand.push(command_data_temp[i]);
                  }
              }
          }
          let log_string = "receive: ";
          for (let i = 0; i < this._receivedCommand.length; i++) {
              log_string = log_string + "0x" + this._receivedCommand[i].toString(16) + ",";
          }
          console.log(log_string);
          if (this._receivedCommand.length > 3) {
              this._receivedCommandLength = this._receivedCommand[1] & 0xff;
              // console.log(this._receivedCommandLength);
          }
          if (this._receivedCommand.length >= this._receivedCommandLength + 2) {
              this.parseCommand();
          }
      }
  
      checkCRC () {
          const crc_data_temp = this._receivedCommand.slice(1, this._receivedCommandLength);
          const crc_calculation = this.crc16(crc_data_temp, 0xffff);
          const crc_received = (this._receivedCommand[this._receivedCommandLength] << 8) & 0xff00 | ((this._receivedCommand[this._receivedCommandLength + 1]) & 0xff);
          if (crc_calculation === crc_received) {
              return true;
          }
          return false;
      }
  
      clearCommandSyncFlag () {
          if (this.commandSyncFlag.lightRingEffectFlag === true) {
              this.commandSyncFlag.lightRingEffectFlag = false;
          }
          if (this.commandSyncFlag.lightRingLedSingleSet1Flag === true) {
              this.commandSyncFlag.lightRingLedSingleSet1Flag = false;
          }
          if (this.commandSyncFlag.lightRingLedSingleSet2Flag === true) {
              this.commandSyncFlag.lightRingLedSingleSet2Flag = false;
          }
          if (this.commandSyncFlag.lightRingLedSingleSet3Flag === true) {
              this.commandSyncFlag.lightRingLedSingleSet3Flag = false;
          }
          if (this.commandSyncFlag.lightRingLedSingleSet4Flag === true) {
              this.commandSyncFlag.lightRingLedSingleSet4Flag = false;
          }
          if (this.commandSyncFlag.sendIRmessageFlag === true) {
              this.commandSyncFlag.sendIRmessageFlag = false;
          }
          if (this.commandSyncFlag.lightRingAllLedOffFlag === true) {
              this.commandSyncFlag.lightRingAllLedOffFlag = false;
          }
          if (this.commandSyncFlag.setBleModeFlag === true) {
              this.commandSyncFlag.setBleModeFlag = false;
          }
      }
  
      eventDetectDataFill (key_value, title_value, other_sensor) {
          this.motion_title_status = title_value;
          this.key_value = key_value;
          if ((other_sensor & 0x01) === 0x01) {
              this.sound_flag = true;
          } else {
              this.sound_flag = false;
          }
      }
  
      getFloat (byte1, byte2, byte3, byte4) {
          const buffer = new Uint8Array([
              byte4,
              byte3,
              byte2,
              byte1
          ]).buffer;
          const view = new DataView(buffer);
          const value = view.getFloat32(0, true);
          return value;
      }
  
      parseCommand () {
          if (this.checkCRC() === false) {
              console.log('checkCRC false!');
              this._receivedCommand = this._receivedCommand.slice(this._receivedCommandLength + 2);
              this._receivedCommandLength = 0;
              return;
          }
          const command_data = this._receivedCommand.slice(1, this._receivedCommandLength);
          switch (command_data[1]) {
          case BLECommand.CMD_SENSOR_DETECT: {
              console.log('receive sensor detect rsp(%d)!', command_data[3]);
              if (command_data[2] === SensorDetectCommand.MOTION_STATUS_DETECT) {
                  if (command_data[4] === 1) {
                      this.motion_status_match = true;
                  } else {
                      this.motion_status_match = false;
                  }
                  this.commandSyncFlag.sensorDetectMotionStatusFlag = false;
              } else if (command_data[2] === SensorDetectCommand.COLOR_DETECT) {
                  if (command_data[4] === 1) {
                      this.color_type_match = true;
                  } else {
                      this.color_type_match = false;
                  }
                  this.commandSyncFlag.sensorDetectColorTypeFlag = false;
              } else if ((command_data[2] === SensorDetectCommand.IR_MESSAGE) && (command_data[3] === 0x02)) {
                  this.wait_ir_message = command_data[4];
                  console.log('wait_ir_message(%d)!', this.wait_ir_message);
                  this.commandSyncFlag.waitIRmessageFlag = false;
              } else if (command_data[2] === SensorDetectCommand.OBSTACLE_DETECT) {
                  if (command_data[4] === 1) {
                      this.obstacles_ahead_flag = true;
                  } else {
                      this.obstacles_ahead_flag = false;
                  }
                  this.commandSyncFlag.obstaclesAheadFlag = false;
              } else if (command_data[2] === SensorDetectCommand.LIGHT_DETECT) {
                  if (command_data[4] === 1) {
                      this.brightness_flag = true;
                  } else {
                      this.brightness_flag = false;
                  }
                  this.commandSyncFlag.brightnessDetectFlag = false;
              }
              break;
          }
          case BLECommand.CMD_GET_SENSOR_VALUE: {
              console.log('get sensor value');
              if (command_data[2] === GetSensorValueCommand.MOTION_SENSOR) {
                  if (command_data[3] === 0x01) {
                      this.acc_x = this.getFloat(command_data[4], command_data[5], command_data[6], command_data[7]);
                      this.commandSyncFlag.getAccelerationXFlag = false;
                      console.log('acc_x = ' + this.acc_x);
                  } else if (command_data[3] === 0x02) {
                      this.acc_y = this.getFloat(command_data[4], command_data[5], command_data[6], command_data[7]);
                      this.commandSyncFlag.getAccelerationYFlag = false;
                      console.log('acc_y = ' + this.acc_y);
                  } else if (command_data[3] === 0x03) {
                      this.acc_z = this.getFloat(command_data[4], command_data[5], command_data[6], command_data[7]);
                      this.commandSyncFlag.getAccelerationZFlag = false;
                      console.log('acc_z = ' + this.acc_z);
                  } else if (command_data[3] === 0x04) {
                      this.roll = this.getFloat(command_data[4], command_data[5], command_data[6], command_data[7]);
                      this.commandSyncFlag.getMotionRollFlag = false;
                      console.log('roll = ' + this.roll);
                  } else if (command_data[3] === 0x05) {
                      this.pitch = this.getFloat(command_data[4], command_data[5], command_data[6], command_data[7]);
                      this.commandSyncFlag.getMotionPitchFlag = false;
                      console.log('pitch = ' + this.pitch);
                  } else if (command_data[3] === 0x06) {
                      this.yaw = this.getFloat(command_data[4], command_data[5], command_data[6], command_data[7]);
                      this.commandSyncFlag.getMotionYawFlag = false;
                      console.log('yaw = ' + this.yaw);
                  } else if (command_data[3] === 0x07) {
                      this.shaking_strength = this.getFloat(command_data[4], command_data[5], command_data[6], command_data[7]);
                      this.commandSyncFlag.getShakingStrengthFlag = false;
                      console.log('shaking_strength = ' + this.shaking_strength);
                  }
              } else if (command_data[2] === GetSensorValueCommand.LIGHT_SENSOR) {
                  if (command_data[3] === 0x04) {
                      this.ambient_light_intensity = command_data[4];
                      this.commandSyncFlag.getAmbientLightIntensityFlag = false;
                      console.log('ambient_light_intensity = ' + this.ambient_light_intensity);
                  } else if (command_data[3] === 0x01) {
                      this.color_red = command_data[4];
                      this.commandSyncFlag.getRGBColorRedFlag = false;
                      console.log('color_red = ' + this.color_red);
                  } else if (command_data[3] === 0x02) {
                      this.color_green = command_data[4];
                      this.commandSyncFlag.getRGBColorGreenFlag = false;
                      console.log('color_green = ' + this.color_green);
                  } else if (command_data[3] === 0x03) {
                      this.color_blue = command_data[4];
                      this.commandSyncFlag.getRGBColorBlueFlag = false;
                      console.log('color_blue = ' + this.color_blue);
                  }
              }
              break;
          }
          case BLECommand.CMD_EVENT_DETECT: {
              if (command_data[2] === 0x00) {
                  console.log('get event response');
                  // console.log(command_data[2], command_data[3], command_data[4]);
                  this.eventDetectDataFill(command_data[3], command_data[4], command_data[5]);
              } else if (command_data[2] === 0x01) {
                  console.log('set button Event Detect response');
                  this.commandSyncFlag.buttonEventDetectFlag = false;
              } else if (command_data[2] === 0x02) {
                  console.log('set motion Event Detect response');
                  this.commandSyncFlag.motionEventDetectFlag = false;
              } else if (command_data[2] === 0x03) {
                  console.log('set sound Event Detect response');
                  this.commandSyncFlag.soundEventDetectFlag = false;
              }
              break;
          }
          case BLECommand.CMD_SET_NEW_PROTOCOL: {
              console.log('set new protocol response!');
              if (command_data[3] === 0x00) {
                  console.log('newProtocolSupportCar');
                  this.newProtocolSupportCar = true;
              }
              if (command_data[4] === 0x00) {
                  console.log('newProtocolSupportCon');
                  this.newProtocolSupportCon = true;
              }
              this.commandSyncFlag.setNewProtocolFlag = false;
              break;
          }
          case BLECommand.CMD_GENERAL_RSP: {
              console.log('get general response!');
              this.clearCommandSyncFlag();
              if (command_data[2] == 0x07) {
                  console.log('matatacon is not in sensor mode!');
                  this.sensorMode = false;
                  matata.showFirmwareModal(this.device, 'notSensorMode');
                  this.disconnect();
              }
              break;
          }
          case BLECommand.CMD_CHECK_VERSION: {
              this.version[0] = command_data[3];
              this.version[1] = command_data[4];
              this.version[2] = command_data[5];
              let version = command_data[3] + '.' + command_data[4] + '.' + command_data[5];
              console.log('version:' + version);
              this.commandSyncFlag.getVersionFlag = false;
              break;
          }
          case BLECommand.CMD_HEARTBEAT: {
              if (command_data[2] === 0x02) {
                  this.device = 'MatataCon';
              } else {
                  this.device = 'MatataBot';
              }
              // console.log('get %s heartbeat', device);
              break;
          }
          default: {
              break;
          }
          }
  
          this._receivedCommand = this._receivedCommand.slice(this._receivedCommandLength + 2);
          this._receivedCommandLength = 0;
          this._receivedCommandStart = false;
      }
  
      setNewProtocol (callback) {
          const setNewProtocolData = [BLECommand.CMD_SET_NEW_PROTOCOL, 0x02,0x02, 0x00, 0x00];
          this.commandSyncFlag.setNewProtocolFlag = true;
          this.newProtocolSupportCon = false;
          this.newProtocolSupportCar = false;
          this.send(this.packCommand(setNewProtocolData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 4000) {
                      console.log('setNewProtocol timeout!');
                      this.commandSyncFlag.setNewProtocolFlag = false;
                      if (this.sensorMode === true) {
                          matata.showFirmwareModal(this.device, 'unknown');
                          this.disconnect();
                      }
                      clearInterval(interval);
                      callback && callback(false);
                      resolve(false);
                  } else if (this.commandSyncFlag.setNewProtocolFlag === false) {
                      if (this.newProtocolSupportCon === false) {
                          if (this.sensorMode === true) {
                              matata.showFirmwareModal(this.device, 'unknown');
                              this.disconnect();
                          }
                          clearInterval(interval);
                          callback && callback(false);
                          resolve(false);
                      } else if (this.newProtocolSupportCar === false) {
                          if (this.sensorMode === true) {
                              matata.showFirmwareModal(this.device, 'carNotSupport');
                              this.disconnect();
                          }
                          clearInterval(interval);
                          callback && callback(false);
                          resolve(false);
                      } else {
                          clearInterval(interval);
                          callback && callback(true);
                          resolve(true);   
                      }
                  }
                  count += 100;
              }, 100);
          });
      }
  
      setBlemode (callback) {
          const setBlemodeData = new Array();
          setBlemodeData.push(BLECommand.CMD_BLE_MODE_CONFIG);
          setBlemodeData.push(0x02);
          this.commandSyncFlag.setBleModeFlag = true;
          this.send(this.packCommand(setBlemodeData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('setBlemode timeout!');
                      clearInterval(interval);
                      this.commandSyncFlag.setBleModeFlag = false;
                      this.disconnect();
                      callback && callback(false);
                      resolve(false);
                  } else if (this.commandSyncFlag.setBleModeFlag === false) {
                      clearInterval(interval);
                      callback && callback(true);
                      resolve(true);
                  }
                  count += 100;
              }, 100);
          });
      }
  
      setButtonEventMonitor (enable, callback) {
          const setButtonEventMonitorData = new Array();
          setButtonEventMonitorData.push(BLECommand.CMD_EVENT_DETECT);
          setButtonEventMonitorData.push(0x01);
          setButtonEventMonitorData.push(enable);
          this.commandSyncFlag.buttonEventDetectFlag = true;
          this.send(this.packCommand(setButtonEventMonitorData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 1000) {
                      console.log('setButtonEventMonitor timeout!');
                      clearInterval(interval);
                      this.commandSyncFlag.buttonEventDetectFlag = false;
                      callback && callback(false);
                      resolve(false);
                  } else if (this.commandSyncFlag.buttonEventDetectFlag === false) {
                      clearInterval(interval);
                      callback && callback(true);
                      resolve(true);
                  }
                  count += 10;
              }, 10);
          });
      }
  
      setMotionEventMonitor (enable, callback) {
          const setMotionEventMonitorData = new Array();
          setMotionEventMonitorData.push(BLECommand.CMD_EVENT_DETECT);
          setMotionEventMonitorData.push(0x02);
          setMotionEventMonitorData.push(enable);
          this.commandSyncFlag.motionEventDetectFlag = true;
          this.send(this.packCommand(setMotionEventMonitorData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 1000) {
                      console.log('setMotionEventMonitor timeout!');
                      clearInterval(interval);
                      this.commandSyncFlag.motionEventDetectFlag = false;
                      callback && callback(false);
                      resolve(false);
                  } else if (this.commandSyncFlag.motionEventDetectFlag === false) {
                      clearInterval(interval);
                      callback && callback(true);
                      resolve(true);
                  }
                  count += 10;
              }, 10);
          });
      }
  
      setSoundEventMonitor (enable, callback) {
          const setSoundEventMonitorData = new Array();
          setSoundEventMonitorData.push(BLECommand.CMD_EVENT_DETECT);
          setSoundEventMonitorData.push(0x03);
          setSoundEventMonitorData.push(enable);
          this.commandSyncFlag.soundEventDetectFlag = true;
          this.send(this.packCommand(setSoundEventMonitorData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 1000) {
                      console.log('setSoundEventMonitor timeout!');
                      clearInterval(interval);
                      this.commandSyncFlag.soundEventDetectFlag = false;
                      callback && callback(false);
                      resolve(false);
                  } else if (this.commandSyncFlag.soundEventDetectFlag === false) {
                      clearInterval(interval);
                      callback && callback(true);
                      resolve(true);
                  }
                  count += 10;
              }, 10);
          });
      }
  
      checkVersion (callback) {
          const cmd = [BLECommand.CMD_CHECK_VERSION, 0x01];
          this.commandSyncFlag.getVersionFlag = true;
          this.send(this.packCommand(cmd));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 1000) {
                      console.log('checkVersion timeout!');
                      this.commandSyncFlag.getVersionFlag = false;
                      callback && callback(false);
                      matata.showFirmwareModal(this.device, 'unknown');
                      this.disconnect();
                      clearInterval(interval);
                      resolve(false);
                  } else if (this.commandSyncFlag.getVersionFlag === false) {
                      const version = this.version[0] + '.' + this.version[1] + '.' + this.version[2];
                      const version_array =  MATATACON_LATEST_FIRMWARE_VERSION.split(".");
                      const latest_version = (parseInt(version_array[0], 10) << 16) + (parseInt(version_array[1], 10) << 8) + parseInt(version_array[2], 10);
                      const current_version = (this.version[0] << 16) + (this.version[1] << 8) + this.version[2];
                      if (latest_version > current_version) {
                          matata.showFirmwareModal(this.device, version);
                          this.disconnect();
                      }
                      callback && callback(true);
                      clearInterval(interval);
                      resolve(true);
                  }
                  count += 10;
              }, 10);
          });
      }
  
      /**
       * Starts reading data from peripheral after BLE has connected to it.
       * @private
       */
      _onConnect () {
          this._ble.startNotifications(BLEINFO.service, BLEINFO.rxChar, this._onMessage);
          this.setNewProtocol((state) => {
              if(state) {
                  this.checkVersion((state) => {
                      if (state) {
                          this.setBlemode((state)=> {
                              if (state) {
                                  this.setButtonEventMonitor(0x01, (state)=> {
                                      if (state) {
                                          this.setMotionEventMonitor(0x01, (state) => {
                                              if (state) {
                                                  this.setSoundEventMonitor(0x01);
                                              }
                                          });
                                      }
                                  });
                              }
                          })
                      }
                  })
              }
          });
      }
  
      /**
       * Process the sensor data from the incoming BLE characteristic.
       * @param {object} base64 - the incoming BLE data.
       * @private
       */
      _onMessage (base64) {
          // parse data
          const dataReceived = Base64Util.base64ToUint8Array(base64);
          // console.log('matataCon recv:');
          // console.log(dataReceived);
          this.depackCommand(dataReceived);
      }
  }
  
  const LightEffectMenu = {
      SPOONDRIFT:    'spoondrift',
      METEOR:        'meteor',
      RAINBOW:       'rainbow',
      FIREFLY:       'firefly',
      COLORWIPE:     'colorwipe',
      BREATHE:       'breathe'
  };
  
  const ButtonKeyMenu = {
      PLAY:          'play',
      DELETE:        'delete',
      MUSIC:         'music',
      FORWARD:       'forward',
      BACKWARD:      'backward',
      LEFT:          'left',
      RIGHT:         'right'
  };
  
  const MotinStatusMenu = {
      SHAKEN:        'shaken',
      UP:            'face up',
      DOWN:          'face down',
      LEFT:          'tilted left',
      RIGHT:         'tilted right',
      FRONT:         'tilted down',
      BACK:          'tilted up',
      FREE_FALL:     'free fall'
  };
  
  const ColorTypeMenu = {
      WHITE:         'white',
      RED:           'red',
      YELLOW:        'yellow',
      GREEN:         'green',
      BLUE:          'blue',
      PURPLE:        'purple',
      BLACK:         'black'
  };
  
  const ColorChannelMenu = {
      RED:           'red',
      GREEN:         'green',
      BLUE:          'blue'
  };
  
  const AxisValueMenu = {
      X:           'x-axis',
      Y:           'y-axis',
      Z:           'z-axis'
  };
  
  const LedRingIndexMenu = {
      NUM1:        'the 1st',
      NUM2:        'the 2nd',
      NUM3:        'the 3rd',
      NUM4:        'the 4th',
      NUM5:        'the 5th',
      NUM6:        'the 6th',
      NUM7:        'the 7th',
      NUM8:        'the 8th',
      NUM9:        'the 9th',
      NUM10:       'the 10th',
      NUM11:       'the 11th',
      NUM12:       'the 12th',
      ALL:         'all'
  };
  
  const BrightnessLevelMenu = {
      LEV1:        '1',
      LEV2:        '2',
      LEV3:        '3',
      LEV4:        '4',
      LEV5:        '5',
      LEV6:        '6',
      ROLL_DICE:   'roll dice'
  };
  
  const MessageIndexMenu = {
      MSG1:        'one',
      MSG2:        'two',
      MSG3:        'three',
      MSG4:        'four',
      MSG5:        'five',
      MSG6:        'six',
      ROLL_DICE:   'roll dice'
  };
  
  const KeyValue = {
      KEY_PLAY:                           0x01,
      KEY_DELETE:                         0x02,
      KEY_MUSIC:                          0x04,
      KEY_FORWARD:                        0x08,
      KEY_BACKWARD:                       0x10,
      KEY_LEFT:                           0x20,
      KEY_RIGHT:                          0x40,
      KEY_RESERVE:                        0x80
  };
  
  const MotionTitle = {
      TITLE_SHAKEN:                       0x01,
      TITLE_UP:                           0x02,
      TITLE_DOWN:                         0x04,
      TITLE_LEFT:                         0x08,
      TITLE_RIGHT:                        0x10,
      TITLE_FRONT:                        0x20,
      TITLE_BACK:                         0x40,
      TITLE_FREE_FALL:                    0x80
  };
  
  /**
   * Scratch 3.0 blocks to interact with a MatataCon peripheral.
   */
  class Scratch3MatataConBlocks {
  
      /**
       * @return {string} - the name of this extension.
       */
      static get EXTENSION_NAME () {
          return 'MatataCon';
      }
  
      /**
       * @return {string} - the ID of this extension.
       */
      static get EXTENSION_ID () {
          return 'matatacon';
      }
  
      get LIGHT_EFFECT_MENU () {
          return [
              {
                  text: formatMessage({
                      id: 'matatacon.lightEffectMenu.spoondrift',
                      default: 'spoondrift',
                      description: 'The light ring spoondrift effect'
                  }),
                  value: LightEffectMenu.SPOONDRIFT
              },
              {
                  text: formatMessage({
                      id: 'matatacon.lightEffectMenu.meteor',
                      default: 'meteor',
                      description: 'The light ring meteor effect'
                  }),
                  value: LightEffectMenu.METEOR
              },
              {
                  text: formatMessage({
                      id: 'matatacon.lightEffectMenu.rainbow',
                      default: 'rainbow',
                      description: 'The light ring rainbow effect'
                  }),
                  value: LightEffectMenu.RAINBOW
              },
              {
                  text: formatMessage({
                      id: 'matatacon.lightEffectMenu.firefly',
                      default: 'firefly',
                      description: 'The light ring firefly effect'
                  }),
                  value: LightEffectMenu.FIREFLY
              },
              {
                  text: formatMessage({
                      id: 'matatacon.lightEffectMenu.colorwipe',
                      default: 'colorwipe',
                      description: 'The light ring colorwipe effect'
                  }),
                  value: LightEffectMenu.COLORWIPE
              },
              {
                  text: formatMessage({
                      id: 'matatacon.lightEffectMenu.breathe',
                      default: 'breathe',
                      description: 'The light ring breathe effect'
                  }),
                  value: LightEffectMenu.BREATHE
              }
          ];
      }
  
      get MOTION_STATUS_MENU () {
          return [
              {
                  text: formatMessage({
                      id: 'matatacon.motinStatusMenu.shaken',
                      default: 'shaken',
                      description: 'The sensor is shaken'
                  }),
                  value: MotinStatusMenu.SHAKEN
              },
              {
                  text: formatMessage({
                      id: 'matatacon.motinStatusMenu.up',
                      default: 'face up',
                      description: 'The sensor is up'
                  }),
                  value: MotinStatusMenu.UP
              },
              {
                  text: formatMessage({
                      id: 'matatacon.motinStatusMenu.down',
                      default: 'face down',
                      description: 'The sensor is down'
                  }),
                  value: MotinStatusMenu.DOWN
              },
              {
                  text: formatMessage({
                      id: 'matatacon.motinStatusMenu.left',
                      default: 'tilted left',
                      description: 'The sensor is left'
                  }),
                  value: MotinStatusMenu.LEFT
              },
              {
                  text: formatMessage({
                      id: 'matatacon.motinStatusMenu.right',
                      default: 'tilted right',
                      description: 'The sensor is right'
                  }),
                  value: MotinStatusMenu.RIGHT
              },
              {
                  text: formatMessage({
                      id: 'matatacon.motinStatusMenu.front',
                      default: 'tilted down',
                      description: 'The sensor is front'
                  }),
                  value: MotinStatusMenu.FRONT
              },
              {
                  text: formatMessage({
                      id: 'matatacon.motinStatusMenu.back',
                      default: 'tilted up',
                      description: 'The sensor is back'
                  }),
                  value: MotinStatusMenu.BACK
              },
              {
                  text: formatMessage({
                      id: 'matatacon.motinStatusMenu.freeFall',
                      default: 'free fall',
                      description: 'The sensor is free fall'
                  }),
                  value: MotinStatusMenu.FREE_FALL
              }
          ];
      }
  
      get COLOR_TYPE_MENU () {
          return [
              {
                  text: formatMessage({
                      id: 'matatacon.colorTypeMenu.white',
                      default: 'white',
                      description: 'The color is white'
                  }),
                  value: ColorTypeMenu.WHITE
              },
              {
                  text: formatMessage({
                      id: 'matatacon.colorTypeMenu.red',
                      default: 'red',
                      description: 'The color is red'
                  }),
                  value: ColorTypeMenu.RED
              },
              {
                  text: formatMessage({
                      id: 'matatacon.colorTypeMenu.yellow',
                      default: 'yellow',
                      description: 'The color is yellow'
                  }),
                  value: ColorTypeMenu.YELLOW
              },
              {
                  text: formatMessage({
                      id: 'matatacon.colorTypeMenu.green',
                      default: 'green',
                      description: 'The color is green'
                  }),
                  value: ColorTypeMenu.GREEN
              },
              {
                  text: formatMessage({
                      id: 'matatacon.colorTypeMenu.blue',
                      default: 'blue',
                      description: 'The color is blue'
                  }),
                  value: ColorTypeMenu.BLUE
              },
              {
                  text: formatMessage({
                      id: 'matatacon.colorTypeMenu.purple',
                      default: 'purple',
                      description: 'The color is purple'
                  }),
                  value: ColorTypeMenu.PURPLE
              },
              {
                  text: formatMessage({
                      id: 'matatacon.colorTypeMenu.black',
                      default: 'black',
                      description: 'The color is black'
                  }),
                  value: ColorTypeMenu.BLACK
              }
          ];
      }
  
      get BUTTON_KEY_MENU () {
          return [
              {
                  text: formatMessage({
                      id: 'matatacon.buttonKeyMenu.play',
                      default: 'play',
                      description: 'The button is play'
                  }),
                  value: ButtonKeyMenu.PLAY
              },
              {
                  text: formatMessage({
                      id: 'matatacon.buttonKeyMenu.delete',
                      default: 'delete',
                      description: 'The button is delete'
                  }),
                  value: ButtonKeyMenu.DELETE
              },
              {
                  text: formatMessage({
                      id: 'matatacon.buttonKeyMenu.music',
                      default: 'music',
                      description: 'The button is music'
                  }),
                  value: ButtonKeyMenu.MUSIC
              },
              {
                  text: formatMessage({
                      id: 'matatacon.buttonKeyMenu.forward',
                      default: 'forward',
                      description: 'The button is forward'
                  }),
                  value: ButtonKeyMenu.FORWARD
              },
              {
                  text: formatMessage({
                      id: 'matatacon.buttonKeyMenu.backward',
                      default: 'backward',
                      description: 'The button is backward'
                  }),
                  value: ButtonKeyMenu.BACKWARD
              },
              {
                  text: formatMessage({
                      id: 'matatacon.buttonKeyMenu.left',
                      default: 'left',
                      description: 'The button is left'
                  }),
                  value: ButtonKeyMenu.LEFT
              },
              {
                  text: formatMessage({
                      id: 'matatacon.buttonKeyMenu.right',
                      default: 'right',
                      description: 'The button is right'
                  }),
                  value: ButtonKeyMenu.RIGHT
              }
          ];
      }
  
      get COLOR_CHANNEL_MENU () {
          return [
              {
                  text: formatMessage({
                      id: 'matatacon.colorChannelMenu.red',
                      default: 'red',
                      description: 'The red color channel'
                  }),
                  value: ColorChannelMenu.RED
              },
              {
                  text: formatMessage({
                      id: 'matatacon.colorChannelMenu.green',
                      default: 'green',
                      description: 'The green color channel'
                  }),
                  value: ColorChannelMenu.GREEN
              },
              {
                  text: formatMessage({
                      id: 'matatacon.colorChannelMenu.blue',
                      default: 'blue',
                      description: 'The blue color channel'
                  }),
                  value: ColorChannelMenu.BLUE
              }
          ];
      }
  
      get AXIS_VALUE_MENU () {
          return [
              {
                  text: formatMessage({
                      id: 'matatacon.axisValueMenu.x',
                      default: 'x-axis',
                      description: 'The x-axis channel'
                  }),
                  value: AxisValueMenu.X
              },
              {
                  text: formatMessage({
                      id: 'matatacon.axisValueMenu.y',
                      default: 'y-axis',
                      description: 'The y-axis channel'
                  }),
                  value: AxisValueMenu.Y
              },
              {
                  text: formatMessage({
                      id: 'matatacon.axisValueMenu.z',
                      default: 'z-axis',
                      description: 'The z-axis channel'
                  }),
                  value: AxisValueMenu.Z
              }
          ];
      }
  
      get LED_RING_INDEX_MENU () {
          return [
              {
                  text: formatMessage({
                      id: 'matatacon.ledRingIndexMenu.num1',
                      default: 'the 1st',
                      description: 'led index 1'
                  }),
                  value: LedRingIndexMenu.NUM1
              },
              {
                  text: formatMessage({
                      id: 'matatacon.ledRingIndexMenu.num2',
                      default: 'the 2nd',
                      description: 'led index 2'
                  }),
                  value: LedRingIndexMenu.NUM2
              },
              {
                  text: formatMessage({
                      id: 'matatacon.ledRingIndexMenu.num3',
                      default: 'the 3rd',
                      description: 'led index 3'
                  }),
                  value: LedRingIndexMenu.NUM3
              },
              {
                  text: formatMessage({
                      id: 'matatacon.ledRingIndexMenu.num4',
                      default: 'the 4th',
                      description: 'led index 4'
                  }),
                  value: LedRingIndexMenu.NUM4
              },
              {
                  text: formatMessage({
                      id: 'matatacon.ledRingIndexMenu.num5',
                      default: 'the 5th',
                      description: 'led index 5'
                  }),
                  value: LedRingIndexMenu.NUM5
              },
              {
                  text: formatMessage({
                      id: 'matatacon.ledRingIndexMenu.num6',
                      default: 'the 6th',
                      description: 'led index 6'
                  }),
                  value: LedRingIndexMenu.NUM6
              },
              {
                  text: formatMessage({
                      id: 'matatacon.ledRingIndexMenu.num7',
                      default: 'the 7th',
                      description: 'led index 7'
                  }),
                  value: LedRingIndexMenu.NUM7
              },
              {
                  text: formatMessage({
                      id: 'matatacon.ledRingIndexMenu.num8',
                      default: 'the 8th',
                      description: 'led index 8'
                  }),
                  value: LedRingIndexMenu.NUM8
              },
              {
                  text: formatMessage({
                      id: 'matatacon.ledRingIndexMenu.num9',
                      default: 'the 9th',
                      description: 'led index 9'
                  }),
                  value: LedRingIndexMenu.NUM9
              },
              {
                  text: formatMessage({
                      id: 'matatacon.ledRingIndexMenu.num10',
                      default: 'the 10th',
                      description: 'led index 10'
                  }),
                  value: LedRingIndexMenu.NUM10
              },
              {
                  text: formatMessage({
                      id: 'matatacon.ledRingIndexMenu.num11',
                      default: 'the 11th',
                      description: 'led index 11'
                  }),
                  value: LedRingIndexMenu.NUM11
              },
              {
                  text: formatMessage({
                      id: 'matatacon.ledRingIndexMenu.num12',
                      default: 'the 12th',
                      description: 'led index 12'
                  }),
                  value: LedRingIndexMenu.NUM12
              },
              {
                  text: formatMessage({
                      id: 'matatacon.ledRingIndexMenu.all',
                      default: 'all',
                      description: 'all led'
                  }),
                  value: LedRingIndexMenu.ALL
              }
          ];
      }
  
      get BRIGHTNESS_LEVEL_MENU () {
          return [
              {
                  text: formatMessage({
                      id: 'matatacon.brightnessLevelMenu.lev1',
                      default: '1',
                      description: 'led brightness level 1'
                  }),
                  value: BrightnessLevelMenu.LEV1
              },
              {
                  text: formatMessage({
                      id: 'matatacon.brightnessLevelMenu.lev2',
                      default: '2',
                      description: 'led brightness level 2'
                  }),
                  value: BrightnessLevelMenu.LEV2
              },
              {
                  text: formatMessage({
                      id: 'matatacon.brightnessLevelMenu.lev3',
                      default: '3',
                      description: 'led brightness level 3'
                  }),
                  value: BrightnessLevelMenu.LEV3
              },
              {
                  text: formatMessage({
                      id: 'matatacon.brightnessLevelMenu.lev4',
                      default: '4',
                      description: 'led brightness level 4'
                  }),
                  value: BrightnessLevelMenu.LEV4
              },
              {
                  text: formatMessage({
                      id: 'matatacon.brightnessLevelMenu.lev5',
                      default: '5',
                      description: 'led brightness level 5'
                  }),
                  value: BrightnessLevelMenu.LEV5
              },
              {
                  text: formatMessage({
                      id: 'matatacon.brightnessLevelMenu.lev6',
                      default: '6',
                      description: 'led brightness level 6'
                  }),
                  value: BrightnessLevelMenu.LEV6
              },
              {
                  text: formatMessage({
                      id: 'matatacon.brightnessLevelMenu.rollDice',
                      default: 'roll dice',
                      description: 'roll dice led brightness'
                  }),
                  value: BrightnessLevelMenu.ROLL_DICE
              }
          ];
      }
  
      get MESSAGE_INDEX_MENU () {
          return [
              {
                  text: formatMessage({
                      id: 'matatacon.messageIndexMenu.msg1',
                      default: 'one',
                      description: 'message index one'
                  }),
                  value: MessageIndexMenu.MSG1
              },
              {
                  text: formatMessage({
                      id: 'matatacon.messageIndexMenu.msg2',
                      default: 'two',
                      description: 'message index two'
                  }),
                  value: MessageIndexMenu.MSG2
              },
              {
                  text: formatMessage({
                      id: 'matatacon.messageIndexMenu.msg3',
                      default: 'three',
                      description: 'message index three'
                  }),
                  value: MessageIndexMenu.MSG3
              },
              {
                  text: formatMessage({
                      id: 'matatacon.messageIndexMenu.msg4',
                      default: 'four',
                      description: 'message index four'
                  }),
                  value: MessageIndexMenu.MSG4
              },
              {
                  text: formatMessage({
                      id: 'matatacon.messageIndexMenu.msg5',
                      default: 'five',
                      description: 'message index five'
                  }),
                  value: MessageIndexMenu.MSG5
              },
              {
                  text: formatMessage({
                      id: 'matatacon.messageIndexMenu.msg6',
                      default: 'six',
                      description: 'message index six'
                  }),
                  value: MessageIndexMenu.MSG6
              },
              {
                  text: formatMessage({
                      id: 'matatacon.messageIndexMenu.rollDice',
                      default: 'roll dice',
                      description: 'roll dice message index'
                  }),
                  value: MessageIndexMenu.ROLL_DICE
              }
          ];
      }
  
      /**
       * Construct a set of MatataCon blocks.
       * @param {Runtime} runtime - the Scratch 3.0 runtime.
       */
      constructor (runtime) {
          /**
           * The Scratch 3.0 runtime.
           * @type {Runtime}
           */
          this.runtime = runtime;
  
          // Create a new MatataCon peripheral instance
          this._peripheral = new MatataCon(this.runtime, Scratch3MatataConBlocks.EXTENSION_ID);
      }
  
      /**
       * @returns {object} metadata for this extension and its blocks.
       */
      getInfo () {
          return {
              id: Scratch3MatataConBlocks.EXTENSION_ID,
              name: Scratch3MatataConBlocks.EXTENSION_NAME,
              blockIconURI: blockIconURI,
              showStatusButton: true,
              blocks: [
                  {
                      opcode: 'lightRingLedSingleSet1',
                      text: formatMessage({
                          id: 'matatacon.lightRingLedSingleSet1',
                          default: 'set [LED_INDEX] LED(s) to [COLOR_TYPE] and brightness is level [BRIGHTNESS_LEVEL]',
                          description: 'set single LED function 1'
                      }),
                      blockType: BlockType.COMMAND,
                      arguments: {
                          LED_INDEX: {
                              type: ArgumentType.STRING,
                              menu: 'ledRingIndex',
                              defaultValue: LedRingIndexMenu.ALL
                          },
                          COLOR_TYPE: {
                              type: ArgumentType.STRING,
                              menu: 'colorType',
                              defaultValue: ColorTypeMenu.WHITE
                          },
                          BRIGHTNESS_LEVEL: {
                              type: ArgumentType.STRING,
                              menu: 'brightnessLevel',
                              defaultValue: BrightnessLevelMenu.LEV1
                          }
                      }
                  },
                  {
                      opcode: 'lightRingLedSingleSet2',
                      text: formatMessage({
                          id: 'matatacon.lightRingLedSingleSet2',
                          default: 'set [LED_INDEX] LED(s) to color [COLOR_VALUE]',
                          description: 'set single LED function 2'
                      }),
                      blockType: BlockType.COMMAND,
                      arguments: {
                          LED_INDEX: {
                              type: ArgumentType.STRING,
                              menu: 'ledRingIndex',
                              defaultValue: LedRingIndexMenu.ALL
                          },
                          COLOR_VALUE: {
                              type: ArgumentType.COLOR
                          }
                      }
                  },
                  {
                      opcode: 'lightRingLedSingleSet3',
                      text: formatMessage({
                          id: 'matatacon.lightRingLedSingleSet3',
                          default: 'set LED [LED_INDEX] red [RED_VALUE] green [GREEN_VALUE] blue [BLUE_VALUE]',
                          description: 'set single LED function 3'
                      }),
                      blockType: BlockType.COMMAND,
                      arguments: {
                          LED_INDEX: {
                              type: ArgumentType.NUMBER,
                              defaultValue: 0
                          },
                          RED_VALUE: {
                              type: ArgumentType.NUMBER,
                              defaultValue: 50
                          },
                          GREEN_VALUE: {
                              type: ArgumentType.NUMBER,
                              defaultValue: 50
                          },
                          BLUE_VALUE: {
                              type: ArgumentType.NUMBER,
                              defaultValue: 50
                          }
                      }
                  },
                  {
                      opcode: 'lightRingLedSingleSet4',
                      text: formatMessage({
                          id: 'matatacon.lightRingLedSingleSet4',
                          default: 'set LED ring light [PANEL]',
                          description: 'set led lamp panel'
                      }),
                      blockType: BlockType.COMMAND,
                      arguments: {
                          PANEL: {
                              type: 'face',
                              defaultValue: '111111111111'
                          }
                      }
                  },
                  {
                      opcode: 'lightRingShowEffect',
                      text: formatMessage({
                          id: 'matatacon.lightRingShowEffect',
                          default: 'LED ring light show [LIGHT_EFFECT]',
                          description: 'Lamp ring display the set lighting effect'
                      }),
                      blockType: BlockType.COMMAND,
                      arguments: {
                          LIGHT_EFFECT: {
                              type: ArgumentType.STRING,
                              menu: 'lightEffect',
                              defaultValue: LightEffectMenu.METEOR
                          }
                      }
                  },
                  {
                      opcode: 'lightRingAllLedOff',
                      text: formatMessage({
                          id: 'matatacon.lightRingAllLedOff',
                          default: 'turn all LEDs off',
                          description: 'turn all led off'
                      }),
                      blockType: BlockType.COMMAND
                  },
                  {
                      opcode: 'sendIRMessage',
                      text: formatMessage({
                          id: 'matatacon.sendIRMessage',
                          default: 'send message [MESSAGE_INDEX]',
                          description: 'send IR message'
                      }),
                      blockType: BlockType.COMMAND,
                      arguments: {
                          MESSAGE_INDEX: {
                              type: ArgumentType.STRING,
                              menu: 'messageIndex',
                              defaultValue: MessageIndexMenu.MSG1
                          }
                      }
                  },
                  {
                      opcode: 'waitIRMessage',
                      text: formatMessage({
                          id: 'matatacon.waitIRMessage',
                          default: 'message [MESSAGE_INDEX] received?',
                          description: 'Whether the message is received'
                      }),
                      blockType: BlockType.BOOLEAN,
                      arguments: {
                          MESSAGE_INDEX: {
                              type: ArgumentType.STRING,
                              menu: 'messageIndex',
                              defaultValue: MessageIndexMenu.MSG1
                          }
                      }
                  },
                  {
                      opcode: 'isButtonPressed',
                      text: formatMessage({
                          id: 'matatacon.isButtonPressed',
                          default: 'button [BUTTON_KEY] pressed?',
                          description: 'Whether the button is pressed ?'
                      }),
                      blockType: BlockType.BOOLEAN,
                      arguments: {
                          BUTTON_KEY: {
                              type: ArgumentType.STRING,
                              menu: 'buttonKey',
                              defaultValue: ButtonKeyMenu.PLAY
                          }
                      }
                  },
                  {
                      opcode: 'motionSensorStatus',
                      text: formatMessage({
                          id: 'matatacon.motionSensorStatus',
                          default: 'sensing [MOTION_STATUS]?',
                          description: 'is status happened on sensor ?'
                      }),
                      blockType: BlockType.BOOLEAN,
                      arguments: {
                          MOTION_STATUS: {
                              type: ArgumentType.STRING,
                              menu: 'motionStatus',
                              defaultValue: MotinStatusMenu.SHAKEN
                          }
                      }
                  },
                  {
                      opcode: 'recognizeColor',
                      text: formatMessage({
                          id: 'matatacon.recognizeColor',
                          default: 'detect color [COLOR_TYPE]?',
                          description: 'Whether the specified color is recognized ?'
                      }),
                      blockType: BlockType.BOOLEAN,
                      arguments: {
                          COLOR_TYPE: {
                              type: ArgumentType.STRING,
                              menu: 'colorType',
                              defaultValue: ColorTypeMenu.WHITE
                          }
                      }
                  },
                  {
                      opcode: 'isHearSomething',
                      text: formatMessage({
                          id: 'matatacon.isHearSomething',
                          default: 'hear sound?',
                          description: 'Whether hear something ?'
                      }),
                      blockType: BlockType.BOOLEAN
                  },
                  {
                      opcode: 'isObstaclesAhead',
                      text: formatMessage({
                          id: 'matatacon.isObstaclesAhead',
                          default: 'meet obstacle?',
                          description: 'Whether obstacle ahead ?'
                      }),
                      blockType: BlockType.BOOLEAN
                  },
                  {
                      opcode: 'isBrightness',
                      text: formatMessage({
                          id: 'matatacon.isBrightness',
                          default: 'detect brightness?',
                          description: 'Whether detect brightness ?'
                      }),
                      blockType: BlockType.BOOLEAN
                  },
                  {
                      opcode: 'getMessage',
                      text: formatMessage({
                          id: 'matatacon.getMessage',
                          default: 'message',
                          description: 'get message'
                      }),
                      blockType: BlockType.REPORTER
                  },
                  {
                      opcode: 'getPitchAngle',
                      text: formatMessage({
                          id: 'matatacon.getPitchAngle',
                          default: 'pitch angle°',
                          description: 'get pitch angle'
                      }),
                      blockType: BlockType.REPORTER
                  },
                  {
                      opcode: 'getRollAngle',
                      text: formatMessage({
                          id: 'matatacon.getRollAngle',
                          default: 'roll angle°',
                          description: 'get roll angle'
                      }),
                      blockType: BlockType.REPORTER
                  },
                  {
                      opcode: 'getYawAngle',
                      text: formatMessage({
                          id: 'matatacon.getYawAngle',
                          default: 'yaw angle°',
                          description: 'get yaw angle'
                      }),
                      blockType: BlockType.REPORTER
                  },
                  {
                      opcode: 'getShakingStrength',
                      text: formatMessage({
                          id: 'matatacon.getShakingStrength',
                          default: 'shaking intensity',
                          description: 'get shaking strength'
                      }),
                      blockType: BlockType.REPORTER
                  },
                  {
                      opcode: 'getAmbientLightIntensity',
                      text: formatMessage({
                          id: 'matatacon.getAmbientLightIntensity',
                          default: 'ambient light intensity',
                          description: 'get ambient light intensity'
                      }),
                      blockType: BlockType.REPORTER
                  },
                  {
                      opcode: 'getRGBColor',
                      text: formatMessage({
                          id: 'matatacon.getRGBColor',
                          default: '[COLOR_CHANNEL] color value',
                          description: 'get RGB color value'
                      }),
                      blockType: BlockType.REPORTER,
                      arguments: {
                          COLOR_CHANNEL: {
                              type: ArgumentType.STRING,
                              menu: 'colorChannel',
                              defaultValue: ColorChannelMenu.RED
                          }
                      }
                  },
                  {
                      opcode: 'getAcceleration',
                      text: formatMessage({
                          id: 'matatacon.getAcceleration',
                          default: '[AXIS_VALUE] acceleration',
                          description: 'get acceleration value'
                      }),
                      blockType: BlockType.REPORTER,
                      arguments: {
                          AXIS_VALUE: {
                              type: ArgumentType.STRING,
                              menu: 'axisValue',
                              defaultValue: AxisValueMenu.X
                          }
                      }
                  },
                  {
                      opcode: 'whenButtonPressed',
                      text: formatMessage({
                          id: 'matatacon.whenButtonPressed',
                          default: 'when button [BUTTON_KEY] pressed',
                          description: 'when a button is pressed'
                      }),
                      blockType: BlockType.HAT,
                      arguments: {
                          BUTTON_KEY: {
                              type: ArgumentType.STRING,
                              menu: 'buttonKey',
                              defaultValue: ButtonKeyMenu.PLAY
                          }
                      }
                  },
                  {
                      opcode: 'whenAttitudeChangeTo',
                      text: formatMessage({
                          id: 'matatacon.whenAttitudeChangeTo',
                          default: 'when attitude [MOTION_STATUS]',
                          description: 'when attitude is change to'
                      }),
                      blockType: BlockType.HAT,
                      arguments: {
                          MOTION_STATUS: {
                              type: ArgumentType.STRING,
                              menu: 'motionStatus',
                              defaultValue: MotinStatusMenu.SHAKEN
                          }
                      }
                  },
                  {
                      opcode: 'whenHearSomething',
                      text: formatMessage({
                          id: 'matatacon.whenHearSomething',
                          default: 'when hear sound',
                          description: 'when hear something'
                      }),
                      blockType: BlockType.HAT
                  }
              ],
              menus: {
                  lightEffect: {
                      acceptReporters: true,
                      items: this.LIGHT_EFFECT_MENU
                  },
                  buttonKey: {
                      acceptReporters: true,
                      items: this.BUTTON_KEY_MENU
                  },
                  motionStatus: {
                      acceptReporters: true,
                      items: this.MOTION_STATUS_MENU
                  },
                  colorType: {
                      acceptReporters: true,
                      items: this.COLOR_TYPE_MENU
                  },
                  colorChannel: {
                      acceptReporters: true,
                      items: this.COLOR_CHANNEL_MENU
                  },
                  axisValue: {
                      acceptReporters: true,
                      items: this.AXIS_VALUE_MENU
                  },
                  ledRingIndex: {
                      acceptReporters: true,
                      items: this.LED_RING_INDEX_MENU
                  },
                  brightnessLevel: {
                      acceptReporters: true,
                      items: this.BRIGHTNESS_LEVEL_MENU
                  },
                  messageIndex: {
                      acceptReporters: true,
                      items: this.MESSAGE_INDEX_MENU
                  }
              }
          };
      }
  
      rollDice () {
          return Math.floor(Math.random() * 6) + 1;
      }
  
      lightRingLedSingleSet1 (args) {
          const lightRingLedSingleSet1Data = new Array();
          let led_index = 0x0d;
          let color_type = 0x01;
          let brightness_level = 0x01;
          if (args.LED_INDEX === LedRingIndexMenu.NUM1) {
              led_index = 0x01;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM2) {
              led_index = 0x02;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM3) {
              led_index = 0x03;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM4) {
              led_index = 0x04;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM5) {
              led_index = 0x05;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM6) {
              led_index = 0x06;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM7) {
              led_index = 0x07;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM8) {
              led_index = 0x08;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM9) {
              led_index = 0x09;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM10) {
              led_index = 0x0a;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM11) {
              led_index = 0x0b;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM12) {
              led_index = 0x0c;
          } else if (args.LED_INDEX === LedRingIndexMenu.ALL) {
              led_index = 0x0d;
          }
  
          if (args.COLOR_TYPE === ColorTypeMenu.WHITE) {
              color_type = 0x01;
          } else if (args.COLOR_TYPE === ColorTypeMenu.RED) {
              color_type = 0x02;
          } else if (args.COLOR_TYPE === ColorTypeMenu.YELLOW) {
              color_type = 0x03;
          } else if (args.COLOR_TYPE === ColorTypeMenu.GREEN) {
              color_type = 0x04;
          } else if (args.COLOR_TYPE === ColorTypeMenu.BLUE) {
              color_type = 0x05;
          } else if (args.COLOR_TYPE === ColorTypeMenu.PURPLE) {
              color_type = 0x06;
          } else if (args.COLOR_TYPE === ColorTypeMenu.BLACK) {
              color_type = 0x07;
          }
  
          if (args.BRIGHTNESS_LEVEL === BrightnessLevelMenu.LEV1) {
              brightness_level = 0x01;
          } else if (args.BRIGHTNESS_LEVEL === BrightnessLevelMenu.LEV2) {
              brightness_level = 0x02;
          } else if (args.BRIGHTNESS_LEVEL === BrightnessLevelMenu.LEV3) {
              brightness_level = 0x03;
          } else if (args.BRIGHTNESS_LEVEL === BrightnessLevelMenu.LEV4) {
              brightness_level = 0x04;
          } else if (args.BRIGHTNESS_LEVEL === BrightnessLevelMenu.LEV5) {
              brightness_level = 0x05;
          } else if (args.BRIGHTNESS_LEVEL === BrightnessLevelMenu.LEV6) {
              brightness_level = 0x06;
          } else if (args.BRIGHTNESS_LEVEL === BrightnessLevelMenu.ROLL_DICE) {
              brightness_level = this.rollDice();
          }
          lightRingLedSingleSet1Data.push(BLECommand.CMD_LIGHT_RING);
          if (led_index === 0x0d) {
              lightRingLedSingleSet1Data.push(LightRingCommand.ALL_LIGHT_SPECIFY_COLOR);
          } else {
              lightRingLedSingleSet1Data.push(LightRingCommand.SINGLE_LIGHT_SPECIFY_COLOR);
              lightRingLedSingleSet1Data.push(led_index);
          }
          lightRingLedSingleSet1Data.push(color_type);
          lightRingLedSingleSet1Data.push(brightness_level);
          this._peripheral.commandSyncFlag.lightRingLedSingleSet1Flag = true;
          this._peripheral.send(this._peripheral.packCommand(lightRingLedSingleSet1Data));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('lightRingLedSingleSet1 timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.lightRingLedSingleSet1Flag = false;
                      resolve();
                  } else if (this._peripheral.commandSyncFlag.lightRingLedSingleSet1Flag === false) {
                      clearInterval(interval);
                      resolve();
                  }
                  count += 10;
              }, 10);
          });
      }
  
      lightRingLedSingleSet2 (args) {
          const lightRingLedSingleSet2Data = new Array();
          const rgb = cast.toRgbColorObject(args.COLOR_VALUE);
          const red = rgb.r;
          const green = rgb.g;
          const blue = rgb.b;
          let led_index = 0x0d;
          if (args.LED_INDEX === LedRingIndexMenu.NUM1) {
              led_index = 0x01;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM2) {
              led_index = 0x02;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM3) {
              led_index = 0x03;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM4) {
              led_index = 0x04;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM5) {
              led_index = 0x05;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM6) {
              led_index = 0x06;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM7) {
              led_index = 0x07;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM8) {
              led_index = 0x08;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM9) {
              led_index = 0x09;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM10) {
              led_index = 0x0a;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM11) {
              led_index = 0x0b;
          } else if (args.LED_INDEX === LedRingIndexMenu.NUM12) {
              led_index = 0x0c;
          } else if (args.LED_INDEX === LedRingIndexMenu.ALL) {
              led_index = 0x0d;
          }
          lightRingLedSingleSet2Data.push(BLECommand.CMD_LIGHT_RING);
          if (led_index === 0x0d) {
              lightRingLedSingleSet2Data.push(LightRingCommand.ALL_LIGHT_RGB_COLOR);
          } else {
              lightRingLedSingleSet2Data.push(LightRingCommand.SINGLE_LIGHT_RGB_COLOR);
              lightRingLedSingleSet2Data.push(led_index);
          }
          lightRingLedSingleSet2Data.push(red);
          lightRingLedSingleSet2Data.push(green);
          lightRingLedSingleSet2Data.push(blue);
          this._peripheral.commandSyncFlag.lightRingLedSingleSet2Flag = true;
          this._peripheral.send(this._peripheral.packCommand(lightRingLedSingleSet2Data));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('lightRingLedSingleSet2 timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.lightRingLedSingleSet2Flag = false;
                      resolve();
                  } else if (this._peripheral.commandSyncFlag.lightRingLedSingleSet2Flag === false) {
                      clearInterval(interval);
                      resolve();
                  }
                  count += 10;
              }, 10);
          });
      }
  
      lightRingLedSingleSet3 (args) {
          const lightRingLedSingleSet3Data = new Array();
          const red = MathUtil.clamp(args.RED_VALUE, 0, 255);
          const green = MathUtil.clamp(args.GREEN_VALUE, 0, 255);
          const blue = MathUtil.clamp(args.BLUE_VALUE, 0, 255);
          lightRingLedSingleSet3Data.push(BLECommand.CMD_LIGHT_RING);
          if (args.LED_INDEX == 0) {
              lightRingLedSingleSet3Data.push(LightRingCommand.ALL_LIGHT_RGB_COLOR);
          } else {
              let led_index = args.LED_INDEX % 12;
              if (led_index < 0) {
                  led_index = led_index + 12;
              }
              lightRingLedSingleSet3Data.push(LightRingCommand.SINGLE_LIGHT_RGB_COLOR);
              lightRingLedSingleSet3Data.push(led_index);
          }
          lightRingLedSingleSet3Data.push(red);
          lightRingLedSingleSet3Data.push(green);
          lightRingLedSingleSet3Data.push(blue);
          this._peripheral.commandSyncFlag.lightRingLedSingleSet3Flag = true;
          this._peripheral.send(this._peripheral.packCommand(lightRingLedSingleSet3Data));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('lightRingLedSingleSet3 timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.lightRingLedSingleSet3Flag = false;
                      resolve();
                  } else if (this._peripheral.commandSyncFlag.lightRingLedSingleSet3Flag === false) {
                      clearInterval(interval);
                      resolve();
                  }
                  count += 10;
              }, 10);
          });
      }
  
      lightRingLedSingleSet4 (args) {
          const lightRingLedSingleSet4Data = new Array();
          const color_array_raw = args.PANEL.match(/./g);
          let color_arr = new Array(12);
          for (let key in color_array_raw) {
              color_arr[key] =  color_array_raw[(parseInt(key) + 11) % 12];
          }
          let r_value = 0;
          let g_value = 0;
          let b_value = 0;
          lightRingLedSingleSet4Data.push(BLECommand.CMD_LIGHT_RING);
          lightRingLedSingleSet4Data.push(LightRingCommand.LAMP_PANEL_RGB);
          for (let key in color_arr) {
              if (color_arr[key] === '0') {
                  r_value = 0xff;
                  g_value = 0xff;
                  b_value = 0xff;
              } else if (color_arr[key] === '1') {
                  r_value = 0xff;
                  g_value = 0x00;
                  b_value = 0x00;
              } else if (color_arr[key] === '2') {
                  r_value = 0xff;
                  g_value = 0x80;
                  b_value = 0x00;
              } else if (color_arr[key] === '3') {
                  r_value = 0xff;
                  g_value = 0xff;
                  b_value = 0x00;
              } else if (color_arr[key] === '4') {
                  r_value = 0x00;
                  g_value = 0xff;
                  b_value = 0x00;
              } else if (color_arr[key] === '5') {
                  r_value = 0x00;
                  g_value = 0xff;
                  b_value = 0xff;
              } else if (color_arr[key] === '6') {
                  r_value = 0x00;
                  g_value = 0x00;
                  b_value = 0xff;
              } else if (color_arr[key] === '7') {
                  r_value = 0x78;
                  g_value = 0x00;
                  b_value = 0xff;
              } else if (color_arr[key] === '8') {
                  r_value = 0x00;
                  g_value = 0x00;
                  b_value = 0x00;
              }
              lightRingLedSingleSet4Data.push(r_value);
              lightRingLedSingleSet4Data.push(g_value);
              lightRingLedSingleSet4Data.push(b_value);
          }
          this._peripheral.commandSyncFlag.lightRingLedSingleSet4Flag = true;
          this._peripheral.send(this._peripheral.packCommand(lightRingLedSingleSet4Data));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('lightRingLedSingleSet4 timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.lightRingLedSingleSet4Flag = false;
                      resolve();
                  } else if (this._peripheral.commandSyncFlag.lightRingLedSingleSet4Flag === false) {
                      clearInterval(interval);
                      resolve();
                  }
                  count += 10;
              }, 10);
          });
      }
  
      lightRingShowEffect (args) {
          const lightRingShowEffectData = new Array();
          let light_effect = 1;
          if (args.LIGHT_EFFECT === LightEffectMenu.SPOONDRIFT) {
              light_effect = 1;
          } else if (args.LIGHT_EFFECT === LightEffectMenu.METEOR) {
              light_effect = 2;
          } else if (args.LIGHT_EFFECT === LightEffectMenu.RAINBOW) {
              light_effect = 3;
          } else if (args.LIGHT_EFFECT === LightEffectMenu.FIREFLY) {
              light_effect = 4;
          } else if (args.LIGHT_EFFECT === LightEffectMenu.COLORWIPE) {
              light_effect = 5;
          } else if (args.LIGHT_EFFECT === LightEffectMenu.BREATHE) {
              light_effect = 6;
          }
  
          lightRingShowEffectData.push(BLECommand.CMD_LIGHT_RING);
          lightRingShowEffectData.push(LightRingCommand.LIGHT_EFFECT);
          lightRingShowEffectData.push(light_effect);
          this._peripheral.commandSyncFlag.lightRingEffectFlag = true;
          this._peripheral.send(this._peripheral.packCommand(lightRingShowEffectData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('lightRingShowEffect timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.lightRingEffectFlag = false;
                      resolve();
                  } else if (this._peripheral.commandSyncFlag.lightRingEffectFlag === false) {
                      clearInterval(interval);
                      resolve();
                  }
                  count += 10;
              }, 10);
          });
      }
  
      lightRingAllLedOff () {
          const lightRingAllLedOffData = new Array();
          lightRingAllLedOffData.push(BLECommand.CMD_LIGHT_RING);
          lightRingAllLedOffData.push(LightRingCommand.ALL_LIGHT_RGB_COLOR);
          lightRingAllLedOffData.push(0x00);
          lightRingAllLedOffData.push(0x00);
          lightRingAllLedOffData.push(0x00);
          this._peripheral.commandSyncFlag.lightRingAllLedOffFlag = true;
          this._peripheral.send(this._peripheral.packCommand(lightRingAllLedOffData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('lightRingAllLedOff timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.lightRingAllLedOffFlag = false;
                      resolve();
                  } else if (this._peripheral.commandSyncFlag.lightRingAllLedOffFlag === false) {
                      clearInterval(interval);
                      resolve();
                  }
                  count += 10;
              }, 10);
          });
      }
  
      sendIRMessage (args) {
          const sendIRMessageData = new Array();
          let message_index = 1;
          if (args.MESSAGE_INDEX === MessageIndexMenu.MSG1) {
              message_index = 1;
          } else if (args.MESSAGE_INDEX === MessageIndexMenu.MSG2) {
              message_index = 2;
          } else if (args.MESSAGE_INDEX === MessageIndexMenu.MSG3) {
              message_index = 3;
          } else if (args.MESSAGE_INDEX === MessageIndexMenu.MSG4) {
              message_index = 4;
          } else if (args.MESSAGE_INDEX === MessageIndexMenu.MSG5) {
              message_index = 5;
          } else if (args.MESSAGE_INDEX === MessageIndexMenu.MSG6) {
              message_index = 6;
          } else if (args.MESSAGE_INDEX === MessageIndexMenu.ROLL_DICE) {
              message_index = this.rollDice();
          }
          sendIRMessageData.push(BLECommand.CMD_SENSOR_DETECT);
          sendIRMessageData.push(SensorDetectCommand.IR_MESSAGE);
          sendIRMessageData.push(0x01); // send IR message
          sendIRMessageData.push(message_index);
          this._peripheral.commandSyncFlag.sendIRmessageFlag = true;
          this._peripheral.send(this._peripheral.packCommand(sendIRMessageData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('sendIRMessage timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.sendIRmessageFlag = false;
                      resolve();
                  } else if (this._peripheral.commandSyncFlag.sendIRmessageFlag === false) {
                      clearInterval(interval);
                      resolve();
                  }
                  count += 10;
              }, 10);
          });
      }
  
      waitIRMessage (args) {
          const waitIRMessageData = new Array();
          let message_index = 1;
          if (args.MESSAGE_INDEX === MessageIndexMenu.MSG1) {
              message_index = 1;
          } else if (args.MESSAGE_INDEX === MessageIndexMenu.MSG2) {
              message_index = 2;
          } else if (args.MESSAGE_INDEX === MessageIndexMenu.MSG3) {
              message_index = 3;
          } else if (args.MESSAGE_INDEX === MessageIndexMenu.MSG4) {
              message_index = 4;
          } else if (args.MESSAGE_INDEX === MessageIndexMenu.MSG5) {
              message_index = 5;
          } else if (args.MESSAGE_INDEX === MessageIndexMenu.MSG6) {
              message_index = 6;
          } else if (args.MESSAGE_INDEX === MessageIndexMenu.ROLL_DICE) {
              message_index = this.rollDice();
          }
          waitIRMessageData.push(BLECommand.CMD_SENSOR_DETECT);
          waitIRMessageData.push(SensorDetectCommand.IR_MESSAGE);
          waitIRMessageData.push(0x02); // wait IR message
          this._peripheral.wait_ir_message = 0;
          this._peripheral.commandSyncFlag.waitIRmessageFlag = true;
          this._peripheral.send(this._peripheral.packCommand(waitIRMessageData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > (2000)) {
                      console.log('waitIRMessage timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.waitIRmessageFlag = false;
                      this._peripheral.wait_ir_message = 0;
                      resolve(false);
                  } else if (this._peripheral.commandSyncFlag.waitIRmessageFlag === false) {
                      console.log('waitIRMessage success!');
                      clearInterval(interval);
                      if(this._peripheral.wait_ir_message === message_index) {
                          resolve(true);
                      } else {
                          resolve(false);
                      }
                  }
                  count += 10;
              }, 10);
          });
      }
  
      isButtonPressed (args) {
          let key_value = KeyValue.KEY_PLAY;
          if (args.BUTTON_KEY === ButtonKeyMenu.PLAY) {
              key_value = KeyValue.KEY_PLAY;
          } else if (args.BUTTON_KEY === ButtonKeyMenu.DELETE) {
              key_value = KeyValue.KEY_DELETE;
          } else if (args.BUTTON_KEY === ButtonKeyMenu.MUSIC) {
              key_value = KeyValue.KEY_MUSIC;
          } else if (args.BUTTON_KEY === ButtonKeyMenu.FORWARD) {
              key_value = KeyValue.KEY_FORWARD;
          } else if (args.BUTTON_KEY === ButtonKeyMenu.BACKWARD) {
              key_value = KeyValue.KEY_BACKWARD;
          } else if (args.BUTTON_KEY === ButtonKeyMenu.LEFT) {
              key_value = KeyValue.KEY_LEFT;
          } else if (args.BUTTON_KEY === ButtonKeyMenu.RIGHT) {
              key_value = KeyValue.KEY_RIGHT;
          }
          // console.log('isButtonPressed');
          // console.log(this._peripheral.key_value);
          if (key_value === (this._peripheral.key_value & key_value)){
              return true;
          }
          return false;
          
      }
  
      motionSensorStatus (args) {
          let title_status = MotionTitle.TITLE_SHAKEN;
          if (args.MOTION_STATUS === MotinStatusMenu.SHAKEN) {
              title_status = MotionTitle.TITLE_SHAKEN;
          } else if (args.MOTION_STATUS === MotinStatusMenu.UP) {
              title_status = MotionTitle.TITLE_UP;
          } else if (args.MOTION_STATUS === MotinStatusMenu.DOWN) {
              title_status = MotionTitle.TITLE_DOWN;
          } else if (args.MOTION_STATUS === MotinStatusMenu.LEFT) {
              title_status = MotionTitle.TITLE_LEFT;
          } else if (args.MOTION_STATUS === MotinStatusMenu.RIGHT) {
              title_status = MotionTitle.TITLE_RIGHT;
          } else if (args.MOTION_STATUS === MotinStatusMenu.FRONT) {
              title_status = MotionTitle.TITLE_FRONT;
          } else if (args.MOTION_STATUS === MotinStatusMenu.BACK) {
              title_status = MotionTitle.TITLE_BACK;
          } else if (args.MOTION_STATUS === MotinStatusMenu.FREE_FALL) {
              title_status = MotionTitle.TITLE_FREE_FALL;
          }
          // console.log('motionSensorStatus');
          // console.log(this._peripheral.motion_title_status);
          if (title_status === (this._peripheral.motion_title_status & title_status)){
              return true;
          }
          return false;
          
      }
  
      recognizeColor (args) {
          const recognizeColorData = new Array();
          let color_type = 1;
          if (args.COLOR_TYPE === ColorTypeMenu.WHITE) {
              color_type = 1;
          } else if (args.COLOR_TYPE === ColorTypeMenu.RED) {
              color_type = 2;
          } else if (args.COLOR_TYPE === ColorTypeMenu.YELLOW) {
              color_type = 3;
          } else if (args.COLOR_TYPE === ColorTypeMenu.GREEN) {
              color_type = 4;
          } else if (args.COLOR_TYPE === ColorTypeMenu.BLUE) {
              color_type = 5;
          } else if (args.COLOR_TYPE === ColorTypeMenu.PURPLE) {
              color_type = 6;
          } else if (args.COLOR_TYPE === ColorTypeMenu.BLACK) {
              color_type = 7;
          }
  
          recognizeColorData.push(BLECommand.CMD_SENSOR_DETECT);
          recognizeColorData.push(SensorDetectCommand.COLOR_DETECT);
          recognizeColorData.push(color_type);
  
          this._peripheral.commandSyncFlag.sensorDetectColorTypeFlag = true;
          this._peripheral.send(this._peripheral.packCommand(recognizeColorData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('recognizeColor timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.sensorDetectColorTypeFlag = false;
                      resolve(false);
                  } else if (this._peripheral.commandSyncFlag.sensorDetectColorTypeFlag === false) {
                      clearInterval(interval);
                      resolve(this._peripheral.color_type_match);
                  }
                  count += 10;
              }, 10);
          });
      }
  
      isHearSomething () {
          return this._peripheral.sound_flag;
      }
  
      isObstaclesAhead () {
          const isObstaclesAheadData = new Array();
          isObstaclesAheadData.push(BLECommand.CMD_SENSOR_DETECT);
          isObstaclesAheadData.push(SensorDetectCommand.OBSTACLE_DETECT);
          this._peripheral.commandSyncFlag.obstaclesAheadFlag = true;
          this._peripheral.send(this._peripheral.packCommand(isObstaclesAheadData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('isObstaclesAhead timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.obstaclesAheadFlag = false;
                      resolve(false);
                  } else if (this._peripheral.commandSyncFlag.obstaclesAheadFlag === false) {
                      clearInterval(interval);
                      resolve(this._peripheral.obstacles_ahead_flag);
                  }
                  count += 10;
              }, 10);
          });
      }
  
      isBrightness () {
          const isBrightnessData = new Array();
          isBrightnessData.push(BLECommand.CMD_SENSOR_DETECT);
          isBrightnessData.push(SensorDetectCommand.LIGHT_DETECT);
          isBrightnessData.push(0x01);
          this._peripheral.commandSyncFlag.brightnessDetectFlag = true;
          this._peripheral.send(this._peripheral.packCommand(isBrightnessData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('isBrightness timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.brightnessDetectFlag = false;
                      resolve(false);
                  } else if (this._peripheral.commandSyncFlag.brightnessDetectFlag === false) {
                      clearInterval(interval);
                      resolve(this._peripheral.brightness_flag);
                  }
                  count += 10;
              }, 10);
          });
      }
  
      getMessage() {
         const getMessageData = new Array();
          getMessageData.push(BLECommand.CMD_SENSOR_DETECT);
          getMessageData.push(SensorDetectCommand.IR_MESSAGE);
          getMessageData.push(0x02); // wait IR message
          this._peripheral.wait_ir_message = 0;
          this._peripheral.commandSyncFlag.waitIRmessageFlag = true;
          this._peripheral.send(this._peripheral.packCommand(getMessageData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > (2000)) {
                      console.log('getMessageData timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.waitIRmessageFlag = false;
                      this._peripheral.wait_ir_message = 0;
                      resolve(0);
                  } else if (this._peripheral.commandSyncFlag.waitIRmessageFlag === false) {
                      console.log('getMessageData success!');
                      clearInterval(interval);
                      if (this._peripheral.wait_ir_message == 0xff) {
                          resolve(0);
                      } else {
                          resolve(this._peripheral.wait_ir_message);
                      }
  
                  }
                  count += 10;
              }, 10);
          });
      }
  
      getPitchAngle () {
          const getPitchAngleData = new Array();
          getPitchAngleData.push(BLECommand.CMD_GET_SENSOR_VALUE);
          getPitchAngleData.push(GetSensorValueCommand.MOTION_SENSOR);
          getPitchAngleData.push(0x05); // pitch
          this._peripheral.commandSyncFlag.getMotionPitchFlag = true;
          this._peripheral.send(this._peripheral.packCommand(getPitchAngleData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('getPitchAngle timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.getMotionPitchFlag = false;
                      resolve(this._peripheral.pitch);
                  } else if (this._peripheral.commandSyncFlag.getMotionPitchFlag === false) {
                      clearInterval(interval);
                      resolve(this._peripheral.pitch);
                  }
                  count += 10;
              }, 10);
          });
      }
  
      getRollAngle () {
          const getRollAngleData = new Array();
          getRollAngleData.push(BLECommand.CMD_GET_SENSOR_VALUE);
          getRollAngleData.push(GetSensorValueCommand.MOTION_SENSOR);
          getRollAngleData.push(0x04); // roll
          this._peripheral.commandSyncFlag.getMotionRollFlag = true;
          this._peripheral.send(this._peripheral.packCommand(getRollAngleData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('getRollAngle timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.getMotionRollFlag = false;
                      resolve(this._peripheral.roll);
                  } else if (this._peripheral.commandSyncFlag.getMotionRollFlag === false) {
                      clearInterval(interval);
                      resolve(this._peripheral.roll);
                  }
                  count += 10;
              }, 10);
          });
      }
  
      getYawAngle () {
          const getYawAngleData = new Array();
          getYawAngleData.push(BLECommand.CMD_GET_SENSOR_VALUE);
          getYawAngleData.push(GetSensorValueCommand.MOTION_SENSOR);
          getYawAngleData.push(0x06); // yaw
          this._peripheral.commandSyncFlag.getMotionYawFlag = true;
          this._peripheral.send(this._peripheral.packCommand(getYawAngleData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('getYawAngle timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.getMotionYawFlag = false;
                      resolve(this._peripheral.yaw);
                  } else if (this._peripheral.commandSyncFlag.getMotionYawFlag === false) {
                      clearInterval(interval);
                      resolve(this._peripheral.yaw);
                  }
                  count += 10;
              }, 10);
          });
      }
  
      getShakingStrength () {
          const getShakingStrengthData = new Array();
          getShakingStrengthData.push(BLECommand.CMD_GET_SENSOR_VALUE);
          getShakingStrengthData.push(GetSensorValueCommand.MOTION_SENSOR);
          getShakingStrengthData.push(0x07); // Shaking Strength
          this._peripheral.commandSyncFlag.getShakingStrengthFlag = true;
          this._peripheral.send(this._peripheral.packCommand(getShakingStrengthData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('getShakingStrength timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.getShakingStrengthFlag = false;
                      this._peripheral.shaking_strength = 0;
                      resolve(this._peripheral.shaking_strength);
                  } else if (this._peripheral.commandSyncFlag.getShakingStrengthFlag === false) {
                      clearInterval(interval);
                      resolve(this._peripheral.shaking_strength);
                  }
                  count += 10;
              }, 10);
          });
      }
  
      getAmbientLightIntensity () {
          const getAmbientLightIntensityData = new Array();
          getAmbientLightIntensityData.push(BLECommand.CMD_GET_SENSOR_VALUE);
          getAmbientLightIntensityData.push(GetSensorValueCommand.LIGHT_SENSOR);
          getAmbientLightIntensityData.push(0x04); // light sensor
          this._peripheral.commandSyncFlag.getAmbientLightIntensityFlag = true;
          this._peripheral.send(this._peripheral.packCommand(getAmbientLightIntensityData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('getAmbientLightIntensity timeout!');
                      clearInterval(interval);
                      this._peripheral.commandSyncFlag.getAmbientLightIntensityFlag = false;
                      resolve(this._peripheral.ambient_light_intensity);
                  } else if (this._peripheral.commandSyncFlag.getAmbientLightIntensityFlag === false) {
                      clearInterval(interval);
                      resolve(this._peripheral.ambient_light_intensity);
                  }
                  count += 10;
              }, 10);
          });
      }
  
      getRGBColor (args) {
          let color_channel = 0x01;
          const getRGBColorData = new Array();
          if (args.COLOR_CHANNEL === ColorChannelMenu.RED) {
              color_channel = 0x01;
              this._peripheral.commandSyncFlag.getRGBColorRedFlag = true;
          } else if (args.COLOR_CHANNEL === ColorChannelMenu.GREEN) {
              color_channel = 0x02;
              this._peripheral.commandSyncFlag.getRGBColorGreenFlag = true;
          } else if (args.COLOR_CHANNEL === ColorChannelMenu.BLUE) {
              color_channel = 0x03;
              this._peripheral.commandSyncFlag.getRGBColorBlueFlag = true;
          }
          getRGBColorData.push(BLECommand.CMD_GET_SENSOR_VALUE);
          getRGBColorData.push(GetSensorValueCommand.LIGHT_SENSOR);
          getRGBColorData.push(color_channel); // color sensor
          this._peripheral.send(this._peripheral.packCommand(getRGBColorData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('getRGBColor timeout!');
                      clearInterval(interval);
                      if (color_channel === 0x01) {
                          this._peripheral.commandSyncFlag.getRGBColorRedFlag = false;
                          resolve(this._peripheral.color_red);
                      } else if (color_channel === 0x02) {
                          this._peripheral.commandSyncFlag.getRGBColorGreenFlag = false;
                          resolve(this._peripheral.color_green);
                      } else if (color_channel === 0x03) {
                          this._peripheral.commandSyncFlag.getRGBColorBlueFlag = false;
                          resolve(this._peripheral.color_blue);
                      }
                  } else if ((color_channel === 0x01) && (this._peripheral.commandSyncFlag.getRGBColorRedFlag === false)) {
                      clearInterval(interval);
                      resolve(this._peripheral.color_red);
                  } else if ((color_channel === 0x02) && (this._peripheral.commandSyncFlag.getRGBColorGreenFlag === false)) {
                      clearInterval(interval);
                      resolve(this._peripheral.color_green);
                  } else if ((color_channel === 0x03) && (this._peripheral.commandSyncFlag.getRGBColorBlueFlag === false)) {
                      clearInterval(interval);
                      resolve(this._peripheral.color_blue);
                  }
                  count += 10;
              }, 10);
          });
      }
  
      getAcceleration (args) {
          let axis_value = 0x01;
          const getAccelerationData = new Array();
          if (args.AXIS_VALUE === AxisValueMenu.X) {
              axis_value = 0x01;
              this._peripheral.commandSyncFlag.getAccelerationXFlag = true;
          } else if (args.AXIS_VALUE === AxisValueMenu.Y) {
              axis_value = 0x02;
              this._peripheral.commandSyncFlag.getAccelerationYFlag = true;
          } else if (args.AXIS_VALUE === AxisValueMenu.Z) {
              axis_value = 0x03;
              this._peripheral.commandSyncFlag.getAccelerationZFlag = true;
          }
          getAccelerationData.push(BLECommand.CMD_GET_SENSOR_VALUE);
          getAccelerationData.push(GetSensorValueCommand.MOTION_SENSOR);
          getAccelerationData.push(axis_value); // axis value
          this._peripheral.send(this._peripheral.packCommand(getAccelerationData));
          return new Promise(resolve => {
              let count = 0;
              const interval = setInterval(() => {
                  if (count > 2000) {
                      console.log('getAcceleration timeout!');
                      clearInterval(interval);
                      if (axis_value === 0x01) {
                          this._peripheral.commandSyncFlag.getAccelerationXFlag = false;
                          resolve(this._peripheral.acc_x);
                      } else if (axis_value === 0x02) {
                          this._peripheral.commandSyncFlag.getAccelerationYFlag = false;
                          resolve(this._peripheral.acc_y);
                      } else if (axis_value === 0x03) {
                          this._peripheral.commandSyncFlag.getAccelerationZFlag = false;
                          resolve(this._peripheral.acc_z);
                      }
                  } else if ((axis_value === 0x01) && (this._peripheral.commandSyncFlag.getAccelerationXFlag === false)) {
                      clearInterval(interval);
                      resolve(this._peripheral.acc_x);
                  } else if ((axis_value === 0x02) && (this._peripheral.commandSyncFlag.getAccelerationYFlag === false)) {
                      clearInterval(interval);
                      resolve(this._peripheral.acc_y);
                  } else if ((axis_value === 0x03) && (this._peripheral.commandSyncFlag.getAccelerationZFlag === false)) {
                      clearInterval(interval);
                      resolve(this._peripheral.acc_z);
                  }
                  count += 10;
              }, 10);
          });
      }
  
      whenButtonPressed (args) {
          let key_value = KeyValue.KEY_PLAY;
          if (args.BUTTON_KEY === ButtonKeyMenu.PLAY) {
              key_value = KeyValue.KEY_PLAY;
          } else if (args.BUTTON_KEY === ButtonKeyMenu.DELETE) {
              key_value = KeyValue.KEY_DELETE;
          } else if (args.BUTTON_KEY === ButtonKeyMenu.MUSIC) {
              key_value = KeyValue.KEY_MUSIC;
          } else if (args.BUTTON_KEY === ButtonKeyMenu.FORWARD) {
              key_value = KeyValue.KEY_FORWARD;
          } else if (args.BUTTON_KEY === ButtonKeyMenu.BACKWARD) {
              key_value = KeyValue.KEY_BACKWARD;
          } else if (args.BUTTON_KEY === ButtonKeyMenu.LEFT) {
              key_value = KeyValue.KEY_LEFT;
          } else if (args.BUTTON_KEY === ButtonKeyMenu.RIGHT) {
              key_value = KeyValue.KEY_RIGHT;
          }
          if (key_value === (this._peripheral.key_value & key_value)){
              return true;
          }
          return false;
          
      }
  
      whenAttitudeChangeTo (args) {
          let title_status = MotionTitle.TITLE_SHAKEN;
          if (args.MOTION_STATUS === MotinStatusMenu.SHAKEN) {
              title_status = MotionTitle.TITLE_SHAKEN;
          } else if (args.MOTION_STATUS === MotinStatusMenu.UP) {
              title_status = MotionTitle.TITLE_UP;
          } else if (args.MOTION_STATUS === MotinStatusMenu.DOWN) {
              title_status = MotionTitle.TITLE_DOWN;
          } else if (args.MOTION_STATUS === MotinStatusMenu.LEFT) {
              title_status = MotionTitle.TITLE_LEFT;
          } else if (args.MOTION_STATUS === MotinStatusMenu.RIGHT) {
              title_status = MotionTitle.TITLE_RIGHT;
          } else if (args.MOTION_STATUS === MotinStatusMenu.FRONT) {
              title_status = MotionTitle.TITLE_FRONT;
          } else if (args.MOTION_STATUS === MotinStatusMenu.BACK) {
              title_status = MotionTitle.TITLE_BACK;
          } else if (args.MOTION_STATUS === MotinStatusMenu.FREE_FALL) {
              title_status = MotionTitle.TITLE_FREE_FALL;
          }
          if (title_status === (this._peripheral.motion_title_status & title_status)){
              return true;
          }
          return false;
          
      }
  
      whenHearSomething () {
          return this._peripheral.sound_flag;
      }
  }
  
  module.exports = Scratch3MatataConBlocks;
  