+(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 ULTRASONIC_MESSAGE = 0x01,
MIN_PING_INTERVAL = 20,
MIN_RESPONSE_TIME = 30,
RETRY_INTERVAL = 5000;
var UltrasonicEvent = {
/**
* Fires when receiving a ping response.
*
* @event UltrasonicEvent.PING
*/
PING: 'ping',
/**
* Fires when receiving a ping-error response.
*
* @event UltrasonicEvent.PING_ERROR
*/
PING_ERROR: 'pingError'
};
/**
* The Ultrasonic class.
*
* @namespace webduino.module
* @class Ultrasonic
* @constructor
* @param {webduino.Board} board The board the ultrasonic sensor is attached to.
* @param {webduino.Pin} trigger The trigger pin the sensor is connected to.
* @param {webduino.Pin} echo The echo pin the sensor is connected to.
* @extends webduino.Module
*/
function Ultrasonic(board, trigger, echo) {
Module.call(this);
this._type = 'HC-SR04';
this._board = board;
this._trigger = trigger;
this._echo = echo;
this._distance = null;
this._lastRecv = null;
this._pingTimer = null;
this._pingCallback = function () {};
this._board.on(BoardEvent.BEFOREDISCONNECT, this.stopPing.bind(this));
this._messageHandler = onMessage.bind(this);
this._board.on(BoardEvent.ERROR, this.stopPing.bind(this));
}
function onMessage(event) {
var message = event.message;
if (message[0] !== ULTRASONIC_MESSAGE) {
return;
} else {
processUltrasonicData(this, message);
}
}
function processUltrasonicData(self, data) {
var str = '',
i = 3,
d1, d2;
if (data[1] === self._trigger.number && data[2] === self._echo.number) {
while (i < data.length) {
d1 = data[i];
d2 = data[i + 1];
str += (d1 - 48);
d2 && (str += (d2 - 48));
i += 2;
}
self._lastRecv = Date.now();
self.emit(UltrasonicEvent.PING, parseInt(str));
}
}
Ultrasonic.prototype = proto = Object.create(Module.prototype, {
constructor: {
value: Ultrasonic
},
/**
* Distance returned from the previous transmission.
*
* @attribute distance
* @type {Number}
* @readOnly
*/
distance: {
get: function () {
return this._distance;
}
}
});
/**
* Transmit an ultrasonic to sense the distance at a (optional) given interval.
*
* @method ping
* @param {Function} [callback] Callback when a response is returned.
* @param {Number} [interval] Interval between each transmission. If omitted the ultrasonic will be transmitted once.
* @return {Promise} A promise when the ping response is returned. Will not return anything if a callback function is given.
*/
proto.ping = function (callback, interval) {
var self = this,
timer;
self.stopPing();
if (typeof callback === 'function') {
self._pingCallback = function (distance) {
self._distance = distance;
callback(distance);
};
self._board.on(BoardEvent.SYSEX_MESSAGE, self._messageHandler);
self.on(UltrasonicEvent.PING, self._pingCallback);
timer = function () {
self._board.sendSysex(ULTRASONIC_MESSAGE, [self._trigger.number, self._echo.number]);
if (interval) {
interval = Math.max(interval, MIN_PING_INTERVAL);
if (self._lastRecv === null || Date.now() - self._lastRecv < 5 * interval) {
self._pingTimer = setTimeout(timer, interval);
} else {
self.stopPing();
setTimeout(function () {
self.ping(callback, interval);
}, RETRY_INTERVAL);
}
}
};
timer();
} else {
return new Promise(function (resolve) {
self.ping(function (cm) {
setTimeout(function () {
resolve(cm);
}, MIN_RESPONSE_TIME);
});
});
}
};
/**
* Stop transmitting any ultrasonic.
*
* @method stopPing
*/
proto.stopPing = function () {
this.removeListener(UltrasonicEvent.PING, this._pingCallback);
this._board.removeListener(BoardEvent.SYSEX_MESSAGE, this._messageHandler);
this._lastRecv = null;
if (this._pingTimer) {
clearTimeout(this._pingTimer);
delete this._pingTimer;
}
};
scope.module.UltrasonicEvent = UltrasonicEvent;
scope.module.Ultrasonic = Ultrasonic;
}));