/*
* The MIT License (http://www.opensource.org/licenses/mit-license.html)
*
* Copyright (c) 2014 Tribalyte Technologies S.L. (http://www.tribalyte.com/)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/** @overview
* JavaScript API of the Myo plugin for Cordova
* @license MIT
* @copyright [Tribalyte Technologies S.L.]{@link http://www.tribalyte.com/}
* @author rbarriuso
*/
"use strict";
//TODO: minify
var DEBUG_LOG = false; //TODO: make API log level configurable (e.g. none / all / only errors)
var cordova = require("cordova");
var utils = require("cordova/utils");
//Internal functions
function plgLog(arg){
if(DEBUG_LOG){
console.log(arg);
}
}
function dbgSuccess(opName){
return function(res){
plgLog("Success! Operation: " + opName + ". Res: " + JSON.stringify(res));
};
}
function dbgError(opName){
return function(err){
plgLog("ERROR. Operation: " + opName + ". Err: " + JSON.stringify(err));
};
}
function getExecOperationFn(apiName){
return function(sCb, eCb, opName, args){
var argsToSend = [];
if(args){
argsToSend = (utils.isArray(args) ? args : [args]);
}
plgLog("Executing \"" + opName + "\"" + (args ? ". Args: " + args : ""));
cordova.exec(sCb || dbgSuccess, eCb || dbgError, apiName, opName, argsToSend);
};
}
/** Enums namespace
* @namespace Enum
*/
var Enum = {
/** Locking policy as defined in the
* [Myo Android SDK]{@link https://developer.thalmic.com/docs/api_reference/android/enumcom_1_1thalmic_1_1myo_1_1_hub_1_1_locking_policy.html}
* @enum {string}
*/
LockingPolicy: {
/** Pose events are always sent. */
NONE: "NONE",
/** Pose events are not sent while a Myo is locked. */
STANDARD: "STANDARD"
},
/** Arm identification as defined in the
* [Myo Android SDK]{@link https://developer.thalmic.com/docs/api_reference/android/enumcom_1_1thalmic_1_1myo_1_1_arm.html}
* @enum {string}
*/
Arm: {
LEFT: "LEFT",
RIGHT: "RIGHT",
UNKNOWN: "UNKNOWN"
},
/** Possible directions for Myo's +x axis relative to a user's arm as defined in the
* [Myo Android SDK]{@link https://developer.thalmic.com/docs/api_reference/android/enumcom_1_1thalmic_1_1myo_1_1_x_direction.html}
* @enum {string}
*/
XDirection: {
TOWARD_WRIST: "TOWARD_WRIST",
TOWARD_ELBOW: "TOWARD_ELBOW",
UNKNOWN: "UNKNOWN"
},
/** Supported hand poses as defined in the
* [Myo Android SDK]{@link https://developer.thalmic.com/docs/api_reference/android/enumcom_1_1thalmic_1_1myo_1_1_pose.html}
* @enum {string}
*/
Pose: {
REST: "REST",
FIST: "FIST",
WAVE_IN: "WAVE_IN",
WAVE_OUT: "WAVE_OUT",
FINGERS_SPREAD: "FINGERS_SPREAD",
DOUBLE_TAP: "DOUBLE_TAP",
UNKNOWN: "UNKNOWN"
},
/** Unlock types supported by Myo, as defined in the
* [Myo Android SDK]{@link https://developer.thalmic.com/docs/api_reference/android/enumcom_1_1thalmic_1_1myo_1_1_hub_1_1_locking_policy.html}
* @enum {string}
*/
UnlockType: {
/** Unlock for a fixed period of time. */
TIMED: "TIMED",
/** Unlock until explicitly told to re-lock. */
HOLD: "HOLD"
},
/** Types of vibration supported by the Myo, as defined in the
* [Myo Android SDK]{@link https://developer.thalmic.com/docs/api_reference/android/enumcom_1_1thalmic_1_1myo_1_1_hub_1_1_locking_policy.html}
* @enum {string}
*/
VibrationType: {
SHORT: "SHORT",
MEDIUM: "MEDIUM",
LONG: "LONG"
},
/** Connetion state
* @enum {string}
*/
ConnectionState: {
CONNECTED: "CONNECTED",
CONNECTING: "CONNECTING",
DISCONNECTED: "DISCONNECTED"
}
};
var execOperation = getExecOperationFn("MyoApi");
/** Represents a Myo device
* @constructor
* @param {Array} dataArray Myo information fields as an array
*/
var Myo = function(dataArray){
//dataArray format: [name, macAddress, fwVersion]
/** Given name of the Myo device
* @type {string} */
this.name = dataArray[0];
/** MAC address of the Myo device
* @type {string} */
this.macAddress = dataArray[1];
/** Firmware version of the Myo device (e.g. "1.1.4")
* @type {string} */
this.fwVersion = dataArray[2];
};
Myo.prototype = {
constructor: Myo,
/** Comparison function
* @param {Myo} myo2 Device to compare to
* @returns {Boolean} true if it's the same Myo device, false otherwise.
*/
equals: function(myo2){
var res = false;
if(myo2){
res = (this.macAddress === myo2.macAddress);
}
return res;
},
/** Request to get the current locking state of the device.
* @param {SuccessCallback} sCb Success callback, which will receive a boolean as result
* @param {ErrorCallback} eCb Error callback
*/
isUnlocked: function(sCb, eCb){
execOperation(sCb, eCb, "myo_isUnlocked", [this.macAddress]);
},
/** Request to lock the device.
* @fires module:MyoApi#lock
* @param {SuccessCallback} sCb Success callback
* @param {ErrorCallback} eCb Error callback
*/
lock: function(sCb, eCb){
execOperation(sCb, eCb, "myo_lock", [this.macAddress]);
},
/** Request to unlock the device.
* @fires module:MyoApi#unlock
* @param {SuccessCallback} sCb Success callback
* @param {ErrorCallback} eCb Error callback
*/
unlock: function(unlockType, sCb, eCb){
execOperation(sCb, eCb, "myo_unlock", [this.macAddress, unlockType]);
},
/** Request RSSI (received signal strength indication). The data is received via
* the callback registered with {@link MyoApi.on} for the "rssi" event.
* @fires module:MyoApi#rssi
* @param {SuccessCallback} sCb Success callback
* @param {ErrorCallback} eCb Error callback
*/
requestRssi: function(sCb, eCb){
execOperation(sCb, eCb, "myo_requestRssi", [this.macAddress]);
},
/** Send command to vibrate this device.
* @param vibrationType {MyoApi.VibrationType} Vibration type to be requested
* @param {SuccessCallback} sCb Success callback
* @param {ErrorCallback} eCb Error callback
*/
vibrate: function(vibrationType, sCb, eCb){
execOperation(sCb, eCb, "myo_vibrate", [this.macAddress, vibrationType]);
},
/** Request the device to notify the user (short vibration)
* @param {SuccessCallback} sCb Success callback
* @param {ErrorCallback} eCb Error callback
*/
notifyUserAction: function(sCb, eCb){
execOperation(sCb, eCb, "myo_notifyUserAction", [this.macAddress]);
},
/**
* @callback ConnStateCallback
* @param {MyoApi.ConnectionState} connectionState Current connection state
*/
/** Request the current connection state of the device
* @param {ConnStateCallback} sCb Success callback, which receives a
* {@link MyoApi.ConnectionState} representing the connection state
* of the device
* @param {ErrorCallback} eCb Error callback
*/
getConnectionState: function(sCb, eCb){
execOperation(sCb, eCb, "myo_getConnectionState", [this.macAddress]);
},
/** Check if the device is currently connected
* @param {BooleanCallback} sCb Success callback, which receives a boolean
* representing if the device is connected
* @param {ErrorCallback} eCb Error callback
*/
isConnected: function(sCb, eCb){
execOperation(sCb, eCb, "myo_isConnected", [this.macAddress]);
}
};
/** @module MyoApi */
var MyoApi = function(){
plgLog("MyoApi constructor called");
return {
/** @see {@link Enum.LockingPolicy} */
LockingPolicy: Enum.LockingPolicy,
/** @see {@link Enum.Arm} */
Arm: Enum.Arm,
/** @see {@link Enum.XDirection} */
XDirection: Enum.XDirection,
/** @see {@link Enum.Pose} */
Pose: Enum.Pose,
/** @see {@link Enum.UnlockType} */
UnlockType: Enum.UnlockType,
/** @see {@link Enum.VibrationType} */
VibrationType: Enum.VibrationType,
/** @see {@link Enum.ConnectionState} */
ConnectionState: Enum.ConnectionState,
/** Request to initialize the API. Will fail if the system doesn't support Bluetooth Low Energy
* @param {SuccessCallback} sCb Success callback
* @param {ErrorCallback} eCb Error callback
*/
init: function(sCb, eCb){
execOperation(sCb, eCb, "init");
},
/** Request to free all resources of the API
* @param {SuccessCallback} sCb Success callback
* @param {ErrorCallback} eCb Error callback
*/
shutdown: function(sCb, eCb){
execOperation(sCb, eCb, "shutdown");
},
/** Opens a native dialog which displays the Myo devices in range and
* gives the option to connect to them. No result is returned.
* @param {SuccessCallback} sCb Success callback
* @param {ErrorCallback} eCb Error callback
* */
openScanDialog: function(sCb, eCb){
execOperation(sCb, eCb, "openScanDialog");
},
/** Opens the Bluetooth adapter configuration screen.
* @param {BooleanCallback} sCb Success callback. Receives 1 if the user
* turned on the Bluetooth adapter, 0 otherwise.
* @param {ErrorCallback} eCb Error callback
* */
openBluetoothConfig: function(sCb, eCb){
execOperation(sCb, eCb, "openBluetoothConfig");
},
/** Checks if the Bluetooth adapter is enabled
* @param {BooleanCallback} sCb Success callback. Receives 1 if the adapter is
* enabled, 0 otherwise.
* @param {ErrorCallback} eCb Error callback
* */
isBluetoothEnabled: function(sCb, eCb){
execOperation(sCb, eCb, "isBluetoothEnabled");
},
/** Initiate attaching to a Myo that is physically very near to (almost touching) the Bluetooth radio.
* When the device is connected, events will be fired, and can be processed
* registering a callback for the "attach" and / or "connect" event.
* @fires module:MyoApi#attach
* @fires module:MyoApi#connect
* @param {SuccessCallback} sCb Success callback
* @param {ErrorCallback} eCb Error callback
*/
attachToAdjacentMyo: function(sCb, eCb){
execOperation(sCb, eCb, "attachToAdjacentMyo");
},
/** Initiate attaching to several Myos that are physically very near to (almost touching) the Bluetooth radio.
* When a device is connected, events will be fired, and can be processed
* registering a callback for the "attach" and / or "connect" event.
* @fires module:MyoApi#attach
* @fires module:MyoApi#connect
* @param {number} count The number of devices which want to be attached
* @param {SuccessCallback} sCb Success callback
* @param {ErrorCallback} eCb Error callback
*/
attachToAdjacentMyos: function(count, sCb, eCb){
execOperation(sCb, eCb, "attachToAdjacentMyos", count);
},
/** Attach / connect to a Myo device identified by its MAC address.
* When the device is connected, events will be fired, and can be processed
* registering a callback for the "attach" and / or "connect" event.
* @fires module:MyoApi#attach
* @fires module:MyoApi#connect
* @param {string} macAddress The MAC address of the device to be attached
* @param {SuccessCallback} sCb Success callback
* @param {ErrorCallback} eCb Error callback
*/
attachByMacAddress: function(macAddress, sCb, eCb){
execOperation(sCb, eCb, "attachByMacAddress", macAddress);
},
/** Detach from the Myo device identified by its MAC address
* @fires module:MyoApi#detach
* @fires module:MyoApi#disconnect
* @param {string} macAddress The MAC address of the device to be detached
* @param {SuccessCallback} sCb Success callback
* @param {ErrorCallback} eCb Error callback
*/
detach: function(macAddress, sCb, eCb){
execOperation(sCb, eCb, "detach", macAddress);
},
/** Set the locking policy for the connected Myos
* @param {MyoApi.LockingPolicy} policy Locking policy to be set
* @param {SuccessCallback} sCb Success callback
* @param {ErrorCallback} eCb Error callback
*/
setLockingPolicy: function(policy, sCb, eCb){
execOperation(sCb, eCb, "setLockingPolicy", policy);
},
/** @callback GetLockPolicyCallback
* @param {MyoApi.LockingPolicy} policy Currently configured locking policy.
*/
/** Get the current local policy of the Hub
* @param {GetLockPolicyCallback} sCb Success callback
* @param {ErrorCallback} eCb Error callback
* @todo TODO: return info synchronously?
*/
getLockingPolicy: function(sCb, eCb){
execOperation(sCb, eCb, "getLockingPolicy");
},
/** Enable or disable the option to send device usage data to Thalmic Labs
* over the network.
* @param {boolean} isSend Whether to send or not usage data to Thalmic
* @param {SuccessCallback} sCb
* @param {ErrorCallback} eCb
*/
setSendUsageData: function(isSend, sCb, eCb){
execOperation(sCb, eCb, "setSendUsageData", isSend);
},
/** Retrieve the current state of the usage data sending option.
* @param {BooleanCallback} sCb
* @param {ErrorCallback} eCb
* @todo TODO: return bool synchronously?
*/
isSendingUsageData: function(sCb, eCb){
execOperation(sCb, eCb, "isSendingUsageData");
},
/** @callback GetAllowanceCallback
* @param {number} devNum Number of devices allowed
*/
/** Retrieve the current maximum number of devices allowed to connect at once.
* @param {SuccessCallback} sCb Callback which receives the requested number of devices
* @param {ErrorCallback} eCb
* @todo TODO: return number synchronously?
*/
getMyoAttachAllowance: function(sCb, eCb){
execOperation(sCb, eCb, "getMyoAttachAllowance");
},
/** Set the current maximum number of devices allowed to connect at once.
* @param {number} allowance Number of devices to allow to connect at the same time
* @param {SuccessCallback} sCb
* @param {ErrorCallback} eCb
*/
setMyoAttachAllowance: function(allowance, sCb, eCb){
execOperation(sCb, eCb, "setMyoAttachAllowance", allowance);
},
/**@callback DeviceListCallback
* @param {Array} devList An array of Myo objects which represents the
* list of connected devices.
*/
/** Requests the list of currently connected Myo devices.
* @param {DeviceListCallback} sCb Callback to receive the result
* @param {ErrorCallback} eCb
* @todo TODO: return list synchronously?
*/
getConnectedDevices: function(sCb, eCb){
execOperation(function(res){
var devList = [];
for(var i = 0; i < res.length; ++i){
devList.push(new Myo(res[i]));
}
sCb(devList);
}, eCb, "getConnectedDevices");
},
/** Requests the current timestamp to the API
* @param {SuccessCallback} sCb Success callback which receives the current timestamp
* as a Number representing the milliseconds since the Unix Epoch
* @param {ErrorCallback} eCb
*/
now: function(sCb, eCb){
execOperation(function(res){
if(sCb){
sCb(new Number(res)); //Convert from String to Number
}
}, eCb, "now");
},
/** @typedef {Object} EventDataType
* @property {string} eventName Name (type) of the event
* @property {Myo} myo Myo device which originated the event
* @property {number} timestamp Timestamp when the event occurred
* @property ... Other extra properties depending on the specific type of event
*/
/** @callback EventCallback
* @param {EventDataType} eventData
*/
/** Registers an event listener. Use {@link MyoApi.off} to unregister
* @param {string} eventName One of the supported events: "connect", "disconnect", "pose", "attach", "detach", "armSync", "armUnsync",
* "unlock", "lock", "orientationData", "accelerometerData", "gyroscopeData", "rssi".
* @param {EventCallback} onEventCb Callback to be called when an event is received. The signature depends on event,
* for example, for "pose": function(Myo myo, number timestamp, Pose pose)
* @param {ErrorCallback} onErrCb Error callback.
* @todo TODO: calculate edge and add parameter to event callback
* @todo TODO: timer function to reduce false positives?
*/
on: function(eventName, onEventCb, onErrCb){
execOperation(function(res){
res.myo = new Myo(res.myo); //Wrap it with the myo object API
onEventCb(res);
}, onErrCb, "on", eventName);
return this;
},
/** Unregisters an event listener registered with {@link MyoApi.on}
* @param {string} eventName Name / type of event to unregister
* @param {SuccessCallback} sCb
* @param {ErrorCallback} eCb
*/
off: function(eventName, sCb, eCb){
execOperation(sCb, eCb, "off", eventName);
return this;
}
};
};
module.exports = MyoApi();
/**Function which is called when the requested operation could not be completed
* @callback ErrorCallback
* @param {string} err Error reason
*
*/
/**Function that is called when the requested operation succeeded
* @callback SuccessCallback
* @param {Object|number|boolean|string|null} res Result (if any) of the request.
*
*/
/**Success callback returning a boolean result
* @callback BooleanCallback
* @param {boolean} Boolean res response to the requested operation.
*/