UNPKG

napi-bindings

Version:

Nymi API bindings to work with Nymi SDK 4.x

327 lines (301 loc) 11.3 kB
'use strict'; const FFI = require('ffi'), path = require('path'), ref = require('ref'), /** * @typedef LogLevel * @type {object} * @property {int} NONE No logging. * @property {int} NORMAL Normal logging of important events like errors and warnings. The default log level. * @property {int} INFO Logs significantly more information about the internals of NAPI. * @property {int} DEBUG The log level that will likely be used when working with Nymi Support. * @property {int} VERBOSE Logs pretty much everything down to the Bluetooth level. * @constant */ LogLevel = { NONE: 0, NORMAL: 1, INFO: 2, DEBUG: 3, VERBOSE: 4 }, /** * @typedef ConfigOutcome * @type {object} * @property {int} OKAY Configured successfully. * @property {int} INVALID_PROVISION_JSON Provision information provided is invalid (likely invalid JSON). * @property {int} MISSING_NEA_NAME Provision information does not include neaName. * @property {int} FAILED_TO_INIT Configuration infomation is okay, but NAPI was unable to start successfully. * @property {int} ERROR An error occurred, likely an exception, possibly involving the parameters provided. * @property {int} IMPOSSIBLE * @constant */ ConfigOutcome = { OKAY: 0, INVALID_PROVISION_JSON: 1, MISSING_NEA_NAME: 2, FAILED_TO_INIT: 3, ERROR: 4, IMPOSSIBLE: 5 }, /** * @typedef PutOutcome * @type {object} * @property {int} OKAY Sending JSON was successful. * @property {int} NAPI_NOT_RUNNING NAPI is not running – either napiConfigure did not complete or napiTerminate was already called. * @property {int} UNPARSEABLE_JSON The provided string is not parseable as JSON. * @property {int} ERROR An error occurred, likely an exception. * @property {int} IMPOSSIBLE * @constant */ PutOutcome = { OKAY: 0, NAPI_NOT_RUNNING: 1, UNPARSEABLE_JSON: 2, ERROR: 3, IMPOSSIBLE: 4 }, /** * @typedef GetOutcome * @type {object} * @property {int} OKAY A JSON string has been returned. * @property {int} NAPI_NOT_RUNNING NAPI is not running – either napiConfigure did not complete or napiTerminate was already called. * @property {int} BUFFER_TOO_SMALL The provided char* buffer is not long enough; the length value will contain the minimum required size. * @property {int} NAPI_TERMINATED Napi::terminate was called. This outcome will be returned once. Afterwards, the outcome is NAPI_NOT_RUNNING. * @property {int} ERROR An error occurred, likely an exception. * @property {int} IMPOSSIBLE * @constant */ GetOutcome = { OKAY: 0, NAPI_NOT_RUNNING: 1, BUFFER_TOO_SMALL: 2, NAPI_TERMINATED: 3, ERROR: 4, IMPOSSIBLE: 5 }, /** * @typedef TryGetOutcome * @type {object} * @property {int} OKAY A JSON string has been returned. * @property {int} NOTHING There is no JSON available at the time of the call. * @property {int} NAPI_NOT_RUNNING NAPI is not running – either napiConfigure did not complete or napiTerminate was already called. * @property {int} BUFFER_TOO_SMALL The provided char* buffer is not long enough; the length value will contain the minimum required size. * @property {int} NAPI_TERMINATED Napi::terminate was called. This outcome will be returned once. Afterwards, the outcome is NAPI_NOT_RUNNING. * @property {int} ERROR An error occurred, likely an exception. * @property {int} IMPOSSIBLE * @constant */ TryGetOutcome = { OKAY: 0, NOTHING: 1, NAPI_NOT_RUNNING: 2, BUFFER_TOO_SMALL: 3, NAPI_TERMINATED: 4, ERROR: 5, IMPOSSIBLE: 6 }, stringPtr = ref.refType('string'), intPtr = ref.refType('int'), NapiInterface = { napiConfigure: ['int', ['string', 'string', 'string', 'int', 'int', 'string']], napiGet: ['int', [stringPtr, 'int', intPtr]], napiTryGet: ['int', [stringPtr, 'int', intPtr]], napiPut: ['int', ['string']], napiTerminate: ['void', []] }; let priv = new WeakMap(), privates = {}, _s = (scope, key, value) => {privates[key] = value; priv.set(scope, privates)}, _g = (scope, key) => priv.get(scope)[key]; /** * <p><b>Class NapiBinding</b></p> * * @class NapuBinding */ class NapiBinding { /** * GetOutcome * * @static * @return {GetOutcome} */ static get GetOutcome () { return GetOutcome; } /** * TryGetOutcome * * @static * @return {TryGetOutcome} */ static get TryGetOutcome () { return TryGetOutcome; } /** * PutOutcome * * @static * @return {PutOutcome} */ static get PutOutcome () { return PutOutcome; } /** * ConfigOutcome * * @static * @return {ConfigOutcome} */ static get ConfigOutcome () { return ConfigOutcome; } /** * LogLevel * * @static * @return {LogLevel} */ static get LogLevel () { return LogLevel; } /** * <p>Create bindings for the Nymi API</p> * * @constructor * @param {boolean} [nymulator=false] TRUE create bindings for networked library, FALSE create bindings for native library. */ constructor (nymulator) { nymulator = nymulator || false; let lib = process.platform === 'darwin' && nymulator ? './../bin/napi-net' : './../bin/napi'; _s(this, 'binding', new FFI.Library(path.resolve(__dirname, lib), NapiInterface)); } /** * <p>Configure and start NAPI.</p> * <p>For most NEAs the default arguments are correct so the call would be similar to napiConfigure("root-directory-path");. * The default host of "" is treated as "127.0.0.1". The default port of -1 will choose the port depending on platform (OS X or Windows) and libary (native or networked). * The value of provisions should be the same as the last saved value. * </p> * * @param {string} neaName Name of this NEA (used when provisioning). (6 to 18 characters) * @param {string} logDirectory Path to a directory that will contain log files. * @param {string} [provisions = '{}'] The provision data saved by previous runs of the NEA. * @param {int} [logLevel=LogLevel.NORMAL] The log level to use (see LogLevel). * @param {int} [port=-1] The default port for networked Nymi Bands (on Windows) or the Nymulator. * @param {string} [host=''] The default host for networked Nymi Bands (on Windows) or the Nymulator. * @return {ConfigOutcome} */ napiConfigure (neaName, logDirectory, provisions, logLevel, port, host) { neaName = String(neaName); logDirectory = String(logDirectory); provisions = String(provisions) || '{}'; logLevel = ~~logLevel || LogLevel.NORMAL; port = ~~port || -1; host = String(host) || ''; return _g(this, 'binding').napiConfigure(neaName, logDirectory, provisions, logLevel, port, host); } /** * <p>Receive a JSON message from NAPI, blocks if nothing is available yet; standard usage.</p> * <p>napiGet is a blocking call. * If NAPI is not running, wait a short time and call napiGet again. No JSON messages are lost. * </p> * <b>This variant returns when:</b> * <li>A message is available from NAPI (GetOutcome.OKAY)</li> * <li>A message from NAPI is ready, but the provided buffer is too small (GetOutcome::BUFFER_TOO_SMALL)</li> * <li>NAPI is not running (GetOutcome.NAPI_NOT_RUNNING)</li> * <li>NAPI has terminated (GetOutcome.NAPI_TERMINATED)</li> * * @return {{outcome: GetOutcome, json: object}} */ napiGet () { let outcome, buf, len, json = null; try { buf = Buffer.alloc(4096); len = ref.alloc('int'); buf.type = stringPtr; outcome = _g(this, 'binding').napiGet(buf, buf.length, len); if (outcome === NapiBinding.GetOutcome.BUFFER_TOO_SMALL) { outcome = _g(this, 'binding').napiGet(buf, len.deref(), len); } if (outcome === NapiBinding.GetOutcome.OKAY) { json = JSON.parse(buf.readCString(0)); } } catch (err) { outcome = NapiBinding.GetOutcome.ERROR; } return {outcome: outcome, json: json}; } /** * <p>Receive a JSON message from NAPI if one is available, non-blocking; standard usage.</p> * <p>napiTryGet is a non-blocking call. * If NAPI is not running, wait a short time and call napiTryGet again. No JSON messages are lost. * </p> * <b>This variant returns when:</b> * <li>A message is available from NAPI (TryGetOutcome.OKAY)</li> * <li>No message is available at the time of the call. (TryGetOutcome::NOTHING)</li> * <li>A message from NAPI is ready, but the provided buffer is too small (TryGetOutcome::BUFFER_TOO_SMALL)</li> * <li>NAPI is not running (TryGetOutcome.NAPI_NOT_RUNNING)</li> * <li>NAPI has terminated (TryGetOutcome.NAPI_TERMINATED)</li> * * @return {{outcome: (TryGetOutcome), json: object}} */ napiTryGet () { let outcome, buf, len, json = null; try { buf = Buffer.alloc(4096); len = ref.alloc('int'); buf.type = stringPtr; outcome = _g(this, 'binding').napiTryGet(buf, buf.length, len); if (outcome === NapiBinding.TryGetOutcome.BUFFER_TOO_SMALL) { outcome = _g(this, 'binding').napiTryGet(buf, len.deref(), len); } if (outcome === NapiBinding.TryGetOutcome.OKAY) { json = JSON.parse(buf.readCString(0)); } } catch (err) { outcome = NapiBinding.TryGetOutcome.ERROR; } return {outcome: outcome, json: json}; } /** * <p>Send a JSON message to NAPI.</p> * * @param {object} json Stringified JSON to send to NAPI. * @return {PutOutcome} */ napiPut (json) { try { return _g(this, 'binding').napiPut(JSON.stringify(json)); } catch (err) { return PutOutcome.IMPOSSIBLE; } } /** * <p>Shutdown NAPI.</p> * <p>The NEA should call this function before exiting.</p> * <b>Note:</b> * Calling this function, followed by a second call to napiConfigD, may now work (consider it beta functionality). * * @return {void} */ napiTerminate () { _g(this, 'binding').napiTerminate(); } } module.exports = NapiBinding;