Show:
+(function (global, factory) {
  if (typeof exports === 'undefined') {
    factory(global.webduino || {});
  } else {
    module.exports = factory;
  }
}(this, function (scope) {
  'use strict';

  var Module = scope.Module,
    BoardEvent = scope.BoardEvent,
    proto;

  var ADXL345Event = {

    /**
     * Fires when the accelerometer senses a value change.
     * 
     * @event ADXL234Event.MESSAGE
     */
    MESSAGE: 'message'
  };

  /**
   * The ADXL345 class.
   * 
   * ADXL345 is a small, thin, ultralow power, 3-axis accelerometer.
   * 
   * @namespace webduino.module
   * @class ADXL345
   * @constructor
   * @param {webduino.Board} board The board that the ADXL345 accelerometer is attached to.
   * @extends webduino.Module
   */
  function ADXL345(board) {
    Module.call(this);
    this._board = board;
    this._baseAxis = 'z';
    this._sensitive = 10;
    this._detectTime = 50;
    this._messageHandler = onMessage.bind(this);
    this._init = false;
    this._info = {
      x: 0,
      y: 0,
      z: 0,
      fXg: 0,
      fYg: 0,
      fZg: 0
    };
    this._callback = function () {};
    this._board.send([0xf0, 0x04, 0x0b, 0x00, 0xf7]);
  }

  function onMessage(event) {
    var msg = event.message;
    var msgPort = [0x04, 0x0b, 0x04];
    var stus = true;
    var x, y, z;

    if (msg.length !== 9) {
      return false;
    }

    msgPort.forEach(function (val, idx) {
      if (val !== msg[idx]) {
        stus = false;
      }
    });

    if (!stus) {
      return false;
    }

    x = (msg[3] << 8 | msg[4]) - 1024;
    y = (msg[5] << 8 | msg[6]) - 1024;
    z = (msg[7] << 8 | msg[8]) - 1024;

    this.emit(ADXL345Event.MESSAGE, x, y, z);
  }

  function calcFixed(base, data, fixedInfo) {
    Object.getOwnPropertyNames(data).forEach(function (key) {
      fixedInfo[key] = data[key];

      if (key === base) {
        if (data[key] > 0) {
          fixedInfo[key] = data[key] - 256;
        } else {
          fixedInfo[key] = data[key] + 256;
        }
      }
    });
  }

  function estimateRollandPitch(x, y, z, fXg, fYg, fZg) {
    var alpha = 0.5;
    var roll, pitch;

    // Low Pass Filter
    fXg = x * alpha + (fXg * (1.0 - alpha));
    fYg = y * alpha + (fYg * (1.0 - alpha));
    fZg = z * alpha + (fZg * (1.0 - alpha));

    // Roll & Pitch Equations
    roll = (Math.atan2(-fYg, fZg) * 180.0) / Math.PI;
    pitch = (Math.atan2(fXg, Math.sqrt(fYg * fYg + fZg * fZg)) * 180.0) / Math.PI;
    roll = roll.toFixed(2);
    pitch = pitch.toFixed(2);

    return {
      roll: roll,
      pitch: pitch,
      fXg: fXg,
      fYg: fYg,
      fZg: fZg
    };
  }

  ADXL345.prototype = proto = Object.create(Module.prototype, {
    constructor: {
      value: ADXL345
    },

    /**
     * The state indicating whether the accelerometer is detecting.
     * 
     * @attribute state
     * @type {String} `on` or `off`
     */
    state: {
      get: function () {
        return this._state;
      },
      set: function (val) {
        this._state = val;
      }
    }
  });

  /**
   * Start detection.
   *
   * @method detect
   * @param {Function} [callback] Detection callback.
   */
  
  /**
   * Start detection.
   *
   * @method on
   * @param {Function} [callback] Detection callback.
   * @deprecated `on()` is deprecated, use `detect()` instead.
   */
  proto.detect = proto.on = function(callback) {
    var _this = this;

    this._board.send([0xf0, 0x04, 0x0b, 0x01, 0xf7]);

    if (typeof callback !== 'function') {
      callback = function () {};
    }

    this._callback = function (x, y, z) {
      var info = _this._info;
      var rt;

      if (!_this._init) {
        _this._init = true;
        calcFixed(this._baseAxis, { x: x, y: y, z: z }, info);
      }

      x -= info.x;
      y -= info.y;
      z -= info.z;

      rt = estimateRollandPitch(x, y, z, info.fXg, info.fYg, info.fZg);
      ['fXg', 'fYg', 'fZg'].forEach(function (val) {
        info[val] = rt[val];
      });

      // uint : mg -> g
      x = (x / 256).toFixed(2);
      y = (y / 256).toFixed(2);
      z = (z / 256).toFixed(2);

      callback(x, y, z, rt.roll, rt.pitch);
    };

    this._state = 'on';
    this._board.on(BoardEvent.SYSEX_MESSAGE, this._messageHandler);
    this.addListener(ADXL345Event.MESSAGE, this._callback);
  };

  /**
   * Stop detection.
   *
   * @method off
   */
  proto.off = function () {
    this._state = 'off';
    this._board.send([0xf0, 0x04, 0x0b, 0x02, 0xf7]);
    this._board.removeListener(BoardEvent.SYSEX_MESSAGE, this._messageHandler);
    this.removeListener(ADXL345Event.MESSAGE, this._callback);
    this._callback = null;
  };

  /**
   * Reset detection value.
   *
   * @method refresh
   */
  proto.refresh = function () {
    this._init = false;
    if (this._init === true) {
      this._info = {
        x: 0,
        y: 0,
        z: 0,
        fXg: 0,
        fYg: 0,
        fZg: 0
      };
    }
  };

  /**
   * Set the base axis for calculation.
   *
   * @method setBaseAxis 
   * @param {String} axis Axis to be set to, either `x`, `y`, or `z`.
   */
  proto.setBaseAxis = function (val) {
    this._baseAxis = val;
  };

  /**
   * Set detection sensitivity.
   *
   * @method setSensitivity
   * @param {Number} sensitivity Detection sensitivity.
   */
  proto.setSensitivity = function (sensitive) {
    if (sensitive !== this._sensitive) {
      this._sensitive = sensitive;
      this._board.send([0xf0, 0x04, 0x0b, 0x03, sensitive, 0xf7]);
    }
  };

  /**
   * Set detecting time period.
   *
   * @method setDetectTime
   * @param {Number} detectTime The time period for detecting, in ms.
   */
  proto.setDetectTime = function (detectTime) {
    if (detectTime !== this._detectTime) {
      this._detectTime = detectTime;
      this._board.send([0xf0, 0x04, 0x0b, 0x04, detectTime, 0xf7]);
    }
  };

  scope.module.ADXL345 = ADXL345;
}));