j5e
Version:
j5e is a device first robotics an IoT framework built to levarage the ECMA-419 specification
415 lines (382 loc) • 12.4 kB
JavaScript
/**
* Utilities used in most IO modules.
* @module j5e/fn
* @ignore
*/
/** Get the io instance
* @param {(object|string|object|IO)} ioOpts - A pin number, pin identifier or a complete IO options object
* @param {string} [ioType] - type of IO
*/
export async function getIO(ioOpts, ioDefaults) {
if (typeof ioDefaults === "string") {
ioDefaults = {
type: ioDefaults
};
}
// && typeof "ioOpts.constructor.name !== "undefined"" is because of https://github.com/Moddable-OpenSource/moddable/issues/914
if (typeof ioOpts === "object" && ioOpts.constructor.name !== "Object" && typeof ioOpts.constructor.name !== "undefined") {
// This is an IO instance
return ioOpts;
}
ioOpts = normalizeIO(ioOpts, ioDefaults);
const Provider = await getProvider(ioOpts, ioDefaults.type);
["edge", "mode", "type"].forEach(key => {
if (ioOpts[key]) {
if (!Array.isArray(ioOpts[key])) {
ioOpts[key] = [ioOpts[key]];
}
ioOpts[key] = ioOpts[key].map(prop => {
if (typeof prop === "string") {
return Provider[prop];
} else {
return prop;
}
});
ioOpts[key] = ioOpts[key].reduce((acc, curr) => {
return acc + curr;
}, 0);
}
});
const io = await new Provider(ioOpts);
return io;
};
/** Normalize parameters passed on device instantiation
* @param {(number|string|object)} ioOpts - A pin number, pin identifier or a complete IO options object
* @param {(number|string)} [ioOpts.pin] - If passing an object, a pin number or pin identifier
* @param {(string|constructor)} [ioOpts.io] - If passing an object, a string specifying a path to the IO provider or a constructor
* @param {object} [deviceOpts={}] - An object containing device options
* @ignore
*/
export function normalizeParams(options = {}) {
return normalizeDevice(normalizeIO(options));
};
/** Normalize IO parameters
* @param {(number|string|object)} options - A pin number, pin identifier, a complete IO options object, or an IO instance
* @param {(number|string)} [ioOpts.pin] - If passing an object, a pin number or pin identifier
* @param {(string|constructor)} [ioOpts.io] - If passing an object, a string specifying a path to the IO provider or a constructor
* @ignore
*/
export function normalizeIO(ioOptions = null, ioDefaults = {}) {
if (ioOptions === null) {
return ioOptions;
}
if (typeof ioOptions === "number" || typeof ioOptions === "string") {
ioOptions = { pin: ioOptions };
}
if (typeof ioDefaults === "number" || typeof ioDefaults === "string") {
ioDefaults = { mode: ioDefaults };
}
if (Array.isArray(ioOptions)) {
ioOptions = { pins: ioOptions };
}
ioOptions = Object.assign(ioDefaults, ioOptions);
return ioOptions;
};
/** Normalize Device parameter
* @param {object} [options={}] - An object containing device options
* @ignore
*/
export function normalizeDevice(options = {}) {
return options;
};
/** Normalize Multi-pin Device parameter
* @param {object} [options={}] - An object containing device options
* @ignore
*/
export function normalizeMulti(options = {}) {
if (!Array.isArray(options)) {
let { io, pins, ...rest } = options;
// Loop through the pins property array and make sure each is an object
pins = options.pins.map(pin => normalizeIO(pin));
// If IO is defined on options use it as default
if (io) {
pins.forEach(pin => pin.io = pin.io || io);
}
// Copy each property that is not ```pins``` onto each member of the pins array
return pins.map(pin => Object.assign(pin, rest));
} else {
return options.map(pin => normalizeIO(pin));
}
};
/** Wait for an async forEach loop. Does not run in parallel.
* @param {array[]} array - An input array
* @param {function} callback - A function to execute when iteration is complete
* @author Sebastien Chopin
* @see {@link https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404|Sebastien's Medium article} for more information
* @example
* const waitFor = (ms) => new Promise(r => setTimeout(r, ms));
*
* await asyncForEach([1, 2, 3], async (num) => {
* await waitFor(50);
* console.log(num);
* });
* console.log('Done');
*/
export async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
};
/**
* Map a value (number) from one range to another. Based on Arduino's map().
* Truncates the returned value to an integer
*
* @param {Number} value - value to map
* @param {Number} fromLow - low end of originating range
* @param {Number} fromHigh - high end of originating range
* @param {Number} toLow - low end of target range
* @param {Number} toHigh - high end of target range
* @return {Number} mapped value (integer)
* @example
* Fn.map(500, 0, 1000, 0, 255); // -> 127
*/
export function map(value, fromLow, fromHigh, toLow, toHigh) {
return (((value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow) | 0);
};
/**
* Like map, but does not truncate the returned value
*
* @param {Number} value - value to map
* @param {Number} fromLow - low end of originating range
* @param {Number} fromHigh - high end of originating range
* @param {Number} toLow - low end of target range
* @param {Number} toHigh - high end of target range
* @return {Number}
*/
export function fmap(value, fromLow, fromHigh, toLow, toHigh) {
return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow;
};
/** Constrain a value to a range.
* @param {number} value - An input value
* @param {number} low - The minimum allowed value (inclusive)
* @param {number} high - The maximum allowed value (inclusive)
* @return {Number} constrained value
* @example
* constrain(120, 0, 100); // -> 100
*/
export function constrain(value, low, high) {
if (value > high) {
value = high;
}
if (value < low) {
value = low;
}
return value;
};
/** Asynchronously load a provider. This allows users to simply pass a path or skip specifying a provider altogether (uses builtins).
* @param {object} ioOpts - An IO options object
* @param {string|io} [ioOpts.io] - The path to the IO class or an io instance
* @param {string} defaultProvider - The default provider to use if none was passed in the io object
* @ignore
*/
export async function getProvider(ioOpts, ioType) {
if (!ioOpts) {
return null;
}
if (ioOpts.io) {
if (typeof ioOpts.io === "string") {
const Provider = await import(ioOpts.io);
return Provider;
} else {
return ioOpts.io;
}
}
return defaultProvider(ioType);
}
/** Divine the default provider and return constructor for desired io type
* @param {string} ioType - The desired io type
* @ignore
*/
export async function defaultProvider(ioType) {
let defaultProvider = device.io;
return defaultProvider[ioType];
}
/** Wrapper for setInterval, clearInterval, and setImmediate. This is necessary so we can use the Global methods in node.js and the System methods in XS.
* @namespace timer
*/
export const timer = Object.freeze({
/**
* Execute a callback on a recurring interval
* @function setInterval
* @memberof timer
* @param {function} callback
* @param {Number} duration
* @returns {Interval}
* @example
* // Blink an LED (We're pretending Led.blink() doesn't exist here)
* import LED from "j5e/led";
* import {timer} from "j5e/fn";
*
* const led = await new LED(12);
*
* timer.setInterval(function() {
* led.toggle();
* }, 100);
*/
setInterval(callback, duration) {
if (global && global.setInterval) {
return global.setInterval(callback, duration);
}
if (typeof System !== "undefined" && System.setInterval) {
return System.setInterval(callback, duration);
}
},
/**
* Stop a recurring interval
* @function clearInterval
* @memberof timer
* @param {Interval} identifier
* @example
* // Blink an LED for one second and then stop (We're pretending Led.blink() doesn't exist here)
* import LED from "j5e/led";
* import {timer} from "j5e/fn";
*
* const led = await new LED(12);
*
* let myTimer = timer.setInterval(function() {
* led.toggle();
* }, 100)
*
* timer.setTimeout(function() {
* timer.clearInterval(myTimer);
* }, 1000);
*/
clearInterval(identifier) {
if (global && global.clearInterval) {
return global.clearInterval(identifier);
}
if (typeof System !== "undefined" && System.clearInterval) {
return System.clearInterval(identifier);
}
},
/**
* Execute a callback after a specified period of time
* @function setInterval
* @memberof timer
* @param {function} callback
* @param {Number} duration
* @returns {Timer}
* @example
* // Blink an LED for one second and then stop
* import LED from "j5e/led";
* import {timer} from "j5e/fn";
*
* const led = await new LED(12);
* led.blink();
*
* timer.setTimeout(function() {
* led.stop();
* }, 1000);
*/
setTimeout(callback, duration) {
if (global && global.setTimeout) {
return global.setTimeout(callback, duration);
}
if (typeof System !== "undefined" && System.setTimeout) {
return System.setTimeout(callback, duration);
}
},
/**
* Stop a timeout before it occurs
* @function clearTimeout
* @memberof timer
* @param {Interval} identifier
* @example
* // Clear a debounce timeout
* import LED from "j5e/led";
* import {timer} from "j5e/fn";
*
* const debounce = (f, ms) => {
* let timeout;
* return (...args) => {
* if (timeout) {
* timer.clearTimeout(timeout);
* }
* timeout = timer.setTimeout(() => {
* timeout = null;
* f(...args);
* }, ms);
* };
* };
*/
clearTimeout(identifier) {
if (global && global.clearTimeout) {
return global.clearTimeout(identifier);
}
if (typeof System !== "undefined" && System.clearTimeout && identifier) {
return System.clearTimeout(identifier);
}
},
/**
* Execute a callback on next tick
* @function setImmediate
* @memberof timer
* @param {function} callback
*/
setImmediate(callback) {
if (typeof process !== "undefined" && global.setImmediate) {
global.setImmediate(callback);
}
if (typeof System !== "undefined" && System.setTimeout) {
System.setTimeout(callback);
}
},
/** Blocking pause, used when we need to wait a very short period of time
* i.e. waiting for a peripheral to respond to a command
* @param {number} ms - Number of milliseconds to wait
*/
sleep(ms) {
new Promise(resolve => this.setTimeout(resolve, ms));
}
});
/** Debounce a function so that it is not invoked unless it stops being called for N milliseconds
* @function debounce
* @param {function} func The function to be debounced
* @param {number} wait The number of milliseconds to wait
* @param {boolean} [immediate] Triggers the function on the leading edge
* @returns {function}
* @author David Walsh
* @see {@link https://davidwalsh.name/javascript-debounce-function} For more information
*/
export function debounce(func, wait, immediate) {
let timeout;
return () => {
const args = arguments;
const later = () => {
timeout = null;
if (!immediate) {
func.apply(this, args);
};
};
let callNow = immediate && !timeout;
timer.clearTimeout(timeout);
timeout = timer.setTimeout(later, wait);
if (callNow) {
func.apply(this, args);
}
};
};
/** Format a number such that it has a given number of digits after the
* decimal point
*
* @param {Number} number - The number to format
* @param {Number} [digits = 0] - The number of digits after the decimal point
* @return {Number} Formatted number
* @example
* Fn.toFixed(5.4564, 2); // -> 5.46
* @example
* Fn.toFixed(1.5, 2); // -> 1.5
*/
export function toFixed(number, digits) {
return +(number || 0).toFixed(digits);
};
/** left Pad a Number with zeros
* @param {number} value - An input value
* @param {number} length - The desired length
* @return {String} The padded string
* @example
* pad(3, 2); // -> "03"
*/
export function pad(value, length) {
return String(value).padStart(length, "0");
}