Port shared modules to typescript. Migrate shared modules first, then look at individual needs of sub-projects.

This commit is contained in:
mmcwilliams 2019-03-04 22:09:12 -05:00
parent 806342b623
commit aa5bbfc9fd
17 changed files with 2695 additions and 0 deletions

File diff suppressed because one or more lines are too long

82
lib/arduino/Readme.md Normal file
View File

@ -0,0 +1,82 @@
## Functions
<dl>
<dt><a href="#delay">delay(ms)</a><code>Promise</code></dt>
<dd><p>Pause the process for X milliseconds in async/await functions</p>
</dd>
<dt><a href="#send">send(device, cmd)</a><code>Promise</code></dt>
<dd><p>Send a command to an Arduino using async/await</p>
</dd>
<dt><a href="#write">write(device, str)</a><code>Promise</code></dt>
<dd><p>Send a string to an Arduino using async/await</p>
</dd>
<dt><a href="#open">open(device)</a><code>Promise</code></dt>
<dd><p>Connect to an Arduino using async/await</p>
</dd>
<dt><a href="#close">close(device)</a><code>Promise</code></dt>
<dd><p>Close a connection to an Arduino using async/await</p>
</dd>
</dl>
<a name="delay"></a>
## delay(ms) ⇒ <code>Promise</code>
Pause the process for X milliseconds in async/await functions
**Kind**: global function
**Returns**: <code>Promise</code> - Resolves after wait
| Param | Type | Description |
| --- | --- | --- |
| ms | <code>integer</code> | milliseconds |
<a name="send"></a>
## send(device, cmd) ⇒ <code>Promise</code>
Send a command to an Arduino using async/await
**Kind**: global function
**Returns**: <code>Promise</code> - Resolves after sending
| Param | Type | Description |
| --- | --- | --- |
| device | <code>string</code> | Arduino identifier |
| cmd | <code>string</code> | Single character command to send |
<a name="write"></a>
## write(device, str) ⇒ <code>Promise</code>
Send a string to an Arduino using async/await
**Kind**: global function
**Returns**: <code>Promise</code> - Resolves after sending
| Param | Type | Description |
| --- | --- | --- |
| device | <code>string</code> | Arduino identifier |
| str | <code>string</code> | String to send |
<a name="open"></a>
## open(device) ⇒ <code>Promise</code>
Connect to an Arduino using async/await
**Kind**: global function
**Returns**: <code>Promise</code> - Resolves after opening
| Param | Type | Description |
| --- | --- | --- |
| device | <code>string</code> | Arduino identifier |
<a name="close"></a>
## close(device) ⇒ <code>Promise</code>
Close a connection to an Arduino using async/await
**Kind**: global function
**Returns**: <code>Promise</code> - Resolves after closing
| Param | Type | Description |
| --- | --- | --- |
| device | <code>string</code> | Arduino identifier |

372
lib/arduino/index.js Normal file
View File

@ -0,0 +1,372 @@
'use strict';
const SerialPort = require('serialport');
const Readline = SerialPort.parsers.Readline;
const exec = require('child_process').exec;
const parser = new Readline('');
const newlineRe = new RegExp('\n', 'g');
const returnRe = new RegExp('\r', 'g');
let eventEmitter;
let cfg;
/**
* Pause the process for X milliseconds in async/await functions
*
* @param {integer} ms milliseconds
*
* @returns {Promise} Resolves after wait
**/
async function delay(ms) {
return new Promise(resolve => {
return setTimeout(resolve, ms);
});
}
/**
* Send a command to an Arduino using async/await
*
* @param {string} device Arduino identifier
* @param {string} cmd Single character command to send
*
* @returns {Promise} Resolves after sending
**/
async function send(device, cmd) {
return new Promise((resolve, reject) => {
arduino.queue[cmd] = (ms) => {
return resolve(ms);
};
return arduino.serial[device].write(cmd, (err, results) => {
if (err) {
//console.error(err)
return reject(err);
}
//
});
});
}
/**
* Send a string to an Arduino using async/await
*
* @param {string} device Arduino identifier
* @param {string} str String to send
*
* @returns {Promise} Resolves after sending
**/
async function write(device, str) {
return new Promise((resolve, reject) => {
arduino.serial[device].write(str, function (err, results) {
if (err) {
return reject(err);
}
//console.log('sent: ' + str)
return resolve(results);
});
});
}
/**
* Connect to an Arduino using async/await
*
* @param {string} device Arduino identifier
*
* @returns {Promise} Resolves after opening
**/
async function openArduino(device) {
return new Promise((resolve, reject) => {
return arduino.serial[device].open(error => {
if (error) {
return reject(error);
}
return resolve(true);
});
});
}
/**
* Close a connection to an Arduino using async/await
*
* @param {string} device Arduino identifier
*
* @returns {Promise} Resolves after closing
**/
async function closeArduino(device) {
return new Promise((resolve, reject) => {
return arduino.serial[device].close((err) => {
if (err) {
return reject(err);
}
return resolve(true);
});
});
}
/******
Arduino handlers
*******/
const arduino = {
path: {},
known: [
'/dev/tty.usbmodem1a161',
'/dev/tty.usbserial-A800f8dk',
'/dev/tty.usbserial-A900cebm',
'/dev/tty.usbmodem1a131',
'/dev/tty.usbserial-a900f6de',
'/dev/tty.usbmodem1a141',
'/dev/ttyACM0',
'COM3'
],
alias: {},
serial: {
connect: {},
projector: {},
camera: {},
light: {}
},
baud: 57600,
queue: {},
timer: 0,
lock: false,
locks: {}
};
arduino.enumerate = async function () {
return new Promise((resolve, reject) => {
return SerialPort.list((err, ports) => {
let matches = [];
if (err) {
return reject(err);
}
ports.forEach(port => {
if (arduino.known.indexOf(port.comName) !== -1) {
matches.push(port.comName);
}
else if ((port.manufacturer + '').toLowerCase().indexOf('arduino') !== -1) {
matches.push(port.comName);
}
else if ((port.comName + '').toLowerCase().indexOf('usbserial') !== -1) {
matches.push(port.comName);
}
else if ((port.comName + '').toLowerCase().indexOf('usbmodem') !== -1) {
matches.push(port.comName);
}
});
if (matches.length === 0) {
return reject('No USB devices found');
}
else if (matches.length > 0) {
return resolve(matches);
}
});
});
};
//commands which respond to a sent char
arduino.send = async function (serial, cmd, res) {
const device = arduino.alias[serial];
let results;
if (arduino.locks[serial]) {
return false;
}
arduino.locks[serial] = true;
await delay(cfg.arduino.serialDelay);
try {
results = await send(device, cmd);
}
catch (e) {
return console.error(e);
}
arduino.locks[serial] = false;
arduino.timer = new Date().getTime();
return await eventEmitter.emit('arduino_send', cmd);
};
//send strings, after char triggers firmware to accept
arduino.string = async function (serial, str) {
const device = arduino.alias[serial];
let writeSuccess;
await delay(cfg.arduino.serialDelay);
if (typeof arduino.serial[device].fake !== 'undefined'
&& arduino.serial[device].fake) {
return arduino.serial[device].string(str);
}
else {
try {
writeSuccess = await write(device, str);
}
catch (e) {
return console.error(e);
}
return writeSuccess;
}
};
//respond with same char over serial when done
arduino.end = async function (serial, data) {
const end = new Date().getTime();
const ms = end - arduino.timer;
let complete;
if (arduino.queue[data] !== undefined) {
arduino.locks[serial] = false;
//console.log('Command ' + data + ' took ' + ms + 'ms');
complete = arduino.queue[data](ms); //execute callback
eventEmitter.emit('arduino_end', data);
delete arduino.queue[data];
}
else {
//console.log('Received stray "' + data + '"'); //silent to user
}
return complete;
};
arduino.alias = function (serial, device) {
console.log(`Making "${serial}" an alias of ${device}`);
arduino.alias[serial] = device;
};
arduino.connect = async function (serial, device, confirm) {
return new Promise(async (resolve, reject) => {
let connectSuccess;
arduino.path[serial] = device;
arduino.alias[serial] = device;
arduino.serial[device] = new SerialPort(arduino.path[serial], {
autoOpen: false,
baudRate: cfg.arduino.baud,
parser: parser
});
arduino.locks[device] = false;
try {
connectSuccess = await openArduino(device);
}
catch (e) {
console.error('failed to open: ' + e);
return reject(e);
}
console.log(`Opened connection with ${arduino.path[serial]} as ${serial}`);
if (!confirm) {
arduino.serial[device].on('data', async (data) => {
let d = data.toString('utf8');
d = d.replace(newlineRe, '').replace(returnRe, '');
return await arduino.end(serial, d);
});
}
else {
arduino.serial[device].on('data', async (data) => {
let d = data.toString('utf8');
d = d.replace(newlineRe, '').replace(returnRe, '');
return await arduino.confirmEnd(d);
});
}
return resolve(arduino.path[serial]);
});
};
arduino.confirmExec = {};
arduino.confirmEnd = function (data) {
//console.dir(data)
if (data === cfg.arduino.cmd.connect
|| data === cfg.arduino.cmd.proj_identifier
|| data === cfg.arduino.cmd.cam_identifier
|| data === cfg.arduino.cmd.light_identifier
|| data === cfg.arduino.cmd.proj_light_identifier
|| data === cfg.arduino.cmd.proj_cam_light_identifier
|| data === cfg.arduino.cmd.proj_cam_identifier) {
arduino.confirmExec(null, data);
arduino.confirmExec = {};
}
};
arduino.verify = async function () {
return new Promise(async (resolve, reject) => {
const device = arduino.alias['connect'];
let writeSuccess;
arduino.confirmExec = function (err, data) {
if (data === cfg.arduino.cmd.connect) {
return resolve(true);
}
else {
return reject('Wrong data returned');
}
};
await delay(cfg.arduino.serialDelay);
try {
writeSuccess = await send(device, cfg.arduino.cmd.connect);
}
catch (e) {
return reject(e);
}
return resolve(writeSuccess);
});
};
arduino.distinguish = async function () {
return new Promise(async (resolve, reject) => {
const device = arduino.alias['connect'];
let writeSuccess;
let type;
arduino.confirmExec = function (err, data) {
if (data === cfg.arduino.cmd.proj_identifier) {
type = 'projector';
}
else if (data === cfg.arduino.cmd.cam_identifier) {
type = 'camera';
}
else if (data === cfg.arduino.cmd.light_identifier) {
type = 'light';
}
else if (data === cfg.arduino.cmd.proj_light_identifier) {
type = 'projector,light';
}
else if (data === cfg.arduino.cmd.proj_cam_light_identifier) {
type = 'projector,camera,light';
}
else if (data === cfg.arduino.cmd.proj_cam_identifier) {
type = 'projector,camera';
}
else if (data === cfg.ardino.cmd.proj_second_identifier) {
type = 'projector_second';
}
return resolve(type);
};
await delay(cfg.arduino.serialDelay);
try {
writeSuccess = await send(device, cfg.arduino.cmd.mcopy_identifier);
}
catch (e) {
console.error(e);
return reject(e);
}
});
};
arduino.close = async function (callback) {
const device = arduino.alias['connect'];
let closeSuccess;
try {
closeSuccess = await closeArduino(device);
}
catch (e) {
return console.error(e);
}
return closeSuccess;
};
arduino.fakeConnect = async function (serial) {
//console.log('Connecting to fake arduino...');
const device = '/dev/fake';
arduino.alias[serial] = device;
arduino.serial[device] = {
write: function (cmd, cb) {
const t = {
c: cfg.arduino.cam.time + cfg.arduino.cam.delay,
p: cfg.arduino.proj.time + cfg.arduino.proj.delay
};
let timeout = t[cmd];
let end;
if (typeof timeout === 'undefined')
timeout = 10;
arduino.timer = +new Date();
setTimeout(() => {
arduino.end(serial, cmd);
return cb();
}, timeout);
},
string: async function (str) {
//do nothing
return true;
},
fake: true
};
//console.log('Connected to fake arduino! Not real! Doesn\'t exist!');
return true;
};
if (typeof module !== 'undefined' && module.parent) {
module.exports = function (c, ee) {
eventEmitter = ee;
cfg = c;
return arduino;
};
}
//# sourceMappingURL=index.js.map

1
lib/arduino/index.js.map Normal file

File diff suppressed because one or more lines are too long

11
lib/arduino/package.json Normal file
View File

@ -0,0 +1,11 @@
{
"name": "arduino",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

186
lib/mscript/Readme.md Normal file
View File

@ -0,0 +1,186 @@
<a name="module_lib/mscript"></a>
## lib/mscript
* [lib/mscript](#module_lib/mscript)
* [~Mscript](#module_lib/mscript..Mscript)
* [.clear()](#module_lib/mscript..Mscript+clear)
* [.interpret()](#module_lib/mscript..Mscript+interpret)
* [.basic_cmd()](#module_lib/mscript..Mscript+basic_cmd)
* [.new_loop()](#module_lib/mscript..Mscript+new_loop)
* [.end_loop()](#module_lib/mscript..Mscript+end_loop)
* [.move_cam()](#module_lib/mscript..Mscript+move_cam)
* [.move_proj()](#module_lib/mscript..Mscript+move_proj)
* [.set_state()](#module_lib/mscript..Mscript+set_state)
* [.last_loop()](#module_lib/mscript..Mscript+last_loop)
* [.parent_loop()](#module_lib/mscript..Mscript+parent_loop)
* [.loop_count()](#module_lib/mscript..Mscript+loop_count)
* [.fade()](#module_lib/mscript..Mscript+fade)
* [.fade_count()](#module_lib/mscript..Mscript+fade_count)
* [.fade_start()](#module_lib/mscript..Mscript+fade_start)
* [.fade_end()](#module_lib/mscript..Mscript+fade_end)
* [.update()](#module_lib/mscript..Mscript+update)
* [.str_to_arr()](#module_lib/mscript..Mscript+str_to_arr)
* [.light_to_arr()](#module_lib/mscript..Mscript+light_to_arr)
* [.light_state()](#module_lib/mscript..Mscript+light_state)
* [.fail()](#module_lib/mscript..Mscript+fail)
* [~startsWith()](#module_lib/mscript..startsWith)
<a name="module_lib/mscript..Mscript"></a>
### lib/mscript~Mscript
class Mscript
**Kind**: inner class of [<code>lib/mscript</code>](#module_lib/mscript)
* [~Mscript](#module_lib/mscript..Mscript)
* [.clear()](#module_lib/mscript..Mscript+clear)
* [.interpret()](#module_lib/mscript..Mscript+interpret)
* [.basic_cmd()](#module_lib/mscript..Mscript+basic_cmd)
* [.new_loop()](#module_lib/mscript..Mscript+new_loop)
* [.end_loop()](#module_lib/mscript..Mscript+end_loop)
* [.move_cam()](#module_lib/mscript..Mscript+move_cam)
* [.move_proj()](#module_lib/mscript..Mscript+move_proj)
* [.set_state()](#module_lib/mscript..Mscript+set_state)
* [.last_loop()](#module_lib/mscript..Mscript+last_loop)
* [.parent_loop()](#module_lib/mscript..Mscript+parent_loop)
* [.loop_count()](#module_lib/mscript..Mscript+loop_count)
* [.fade()](#module_lib/mscript..Mscript+fade)
* [.fade_count()](#module_lib/mscript..Mscript+fade_count)
* [.fade_start()](#module_lib/mscript..Mscript+fade_start)
* [.fade_end()](#module_lib/mscript..Mscript+fade_end)
* [.update()](#module_lib/mscript..Mscript+update)
* [.str_to_arr()](#module_lib/mscript..Mscript+str_to_arr)
* [.light_to_arr()](#module_lib/mscript..Mscript+light_to_arr)
* [.light_state()](#module_lib/mscript..Mscript+light_state)
* [.fail()](#module_lib/mscript..Mscript+fail)
<a name="module_lib/mscript..Mscript+clear"></a>
#### mscript.clear()
Clear the state of the script
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+interpret"></a>
#### mscript.interpret()
Main function, accepts multi-line string, parses into lines
and interprets the instructions from the text. Returns an array
of steps to be fed into the mcopy.
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+basic_cmd"></a>
#### mscript.basic\_cmd()
Apply a basic two character command
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+new_loop"></a>
#### mscript.new\_loop()
Start a new loop
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+end_loop"></a>
#### mscript.end\_loop()
Close the most recent loop
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+move_cam"></a>
#### mscript.move\_cam()
Move camera to explicitly-defined frame
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+move_proj"></a>
#### mscript.move\_proj()
Move projector to explicitly-defined frame
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+set_state"></a>
#### mscript.set\_state()
Set the state of either the cam or projector
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+last_loop"></a>
#### mscript.last\_loop()
Return the last loop
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+parent_loop"></a>
#### mscript.parent\_loop()
Return the second-last loop
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+loop_count"></a>
#### mscript.loop\_count()
Extract the loop count integer from a LOOP cmd
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+fade"></a>
#### mscript.fade()
Execute a fade of frame length, from color to another color
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+fade_count"></a>
#### mscript.fade\_count()
Extract the fade length integer from a FADE cmd
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+fade_start"></a>
#### mscript.fade\_start()
Extract the start color from a string
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+fade_end"></a>
#### mscript.fade\_end()
Extract the end color from a string
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+update"></a>
#### mscript.update()
Increase the state of a specific object, such as the camera/projector,
by the value defined in val
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+str_to_arr"></a>
#### mscript.str\_to\_arr()
Split string on command, extract any integers from string
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+light_to_arr"></a>
#### mscript.light\_to\_arr()
Split a string on a command to extract data for light array
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+light_state"></a>
#### mscript.light\_state()
Split a string to extract an rgb color value
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..Mscript+fail"></a>
#### mscript.fail()
Throw an error with specific message
**Kind**: instance method of [<code>Mscript</code>](#module_lib/mscript..Mscript)
<a name="module_lib/mscript..startsWith"></a>
### lib/mscript~startsWith()
startswith function from lodash, do not want the entire lib for this
**Kind**: inner method of [<code>lib/mscript</code>](#module_lib/mscript)

13
lib/mscript/TODO.md Normal file
View File

@ -0,0 +1,13 @@
# TODO - mscript
* Add variables and simple evaluation
* Add "Light" feature
Bash-like variables?
Similar to LESS/SASS?
Makes a tokenization easier
@ is better than $
RangeError: Invalid array length
at Mscript.str_to_arr (./mcopy/app/lib/mscript/index.js:474:9)

545
lib/mscript/index.js Normal file
View File

@ -0,0 +1,545 @@
'use strict';
/** @module lib/mscript */
const BLACK = '0,0,0';
const WHITE = '255,255,255';
const CMD = [
'CF',
'PF',
'BF',
'CB',
'PB',
'BB'
];
const ALTS = {
'CF' : ['CAMERA FORWARD', 'CAM FORWARD'],
'PF' : ['PROJECTOR FORWARD', 'PROJ FORWARD'],
'BF' : ['BLACK FORWARD', 'BLACK', 'BLANK FORWARD', 'BLANK'],
'CB' : ['CAMERA BACKWARD', 'CAM BACKWARD', 'CAMERA BACK', 'CAM BACK'],
'PB' : ['PROJECTOR FORWARD', 'PROJ FORWARD', 'PROJECTOR BACK', 'PROJ BACK'],
'BB' : ['BLACK BACKWARD', 'BLACK BACK', 'BLANK BACK'],
'L ' : ['LIGHT', 'COLOR', 'LAMP'],
'F ' : ['FADE']
};
/** helper functions */
/** startswith function from lodash, do not want the entire lib for this */
function startsWith(string, target, position) {
const { length } = string;
position = position == null ? 0 : position;
if (position < 0) {
position = 0;
} else if (position > length) {
position = length;
}
target = `${target}`;
return string.slice(position, position + target.length) == target;
}
/** class Mscript */
class Mscript {
constructor () {
this.output = {};
}
/**
* Clear the state of the script
*/
clear () {
this.lines = [];
this.cam = 0;
this.proj = 0;
this.color = '';
this.loops = [];
this.rec = -1;
this.two = '';
this.arr = [];
this.light = [];
this.target = 0; //move to target using CAM # or PROJ #
this.dist = 0;
this.variables = {};
this.output = {};
}
/**
* Main function, accepts multi-line string, parses into lines
* and interprets the instructions from the text. Returns an array
* of steps to be fed into the mcopy.
*/
interpret (text, callback) {
this.clear()
if (typeof text === 'undefined') {
return this.fail('No input');
}
//split string into lines, each containing a command
this.lines = text.split('\n');
this.lines = this.lines.map(line => {
line = line.replace(/\t+/g, ''); //strip tabs
line = line.trim(); //remove excess whitespace before and after command
line = line.toUpperCase();
return line;
})
for (let line of this.lines) {
this.two = line.substring(0, 2);
if (CMD.indexOf(this.two) !== -1) {
this.basic_cmd(line);
} else if (startsWith(line, '@') || line.indexOf('@') !== -1) {
this.variable(line);
} else if (startsWith(line, 'LOOP')) {
this.new_loop(line);
} else if (startsWith(line, 'L ')) {
this.light_state(line);
} else if (startsWith(line, 'F ')) {
this.new_loop(line, true);
} else if (startsWith(line, 'END')) {
this.end_loop(line);
} else if (startsWith(line, 'CAM')) { //directly go to that frame (black?)
this.move_cam(line);
} else if (startsWith(line, 'PROJ')) { //directly go to that frame
this.move_proj(line);
} else if (startsWith(line, 'SET')) { //set that state
this.set_state(line);
} else if (startsWith(line, '#') || startsWith(line, '//')) {
//comments
//ignore while parsing
}
}
this.output.success = true;
this.output.arr = this.arr; //all instructions
this.output.light = this.light; //all light instructions
this.output.cam = this.cam;
this.output.proj = this.proj;
if (typeof callback !== 'undefined') {
//should only be invoked by running mscript.tests()
callback(this.output);
} else {
return this.output;
}
}
variable (line) {
let parts = line.split('=');
let key = parts[0];
let value = parts[1];
let update = false;
if (value && value.indexOf('#') !== -1) {
value = value.split('#')[0];
}
if (value && value.indexOf('//') !== -1) {
value = value.split('//')[0];
}
if (value && value.indexOf('+') !== -1) {
if (value)
update = true;
}
if (line.indexOf('-') !== -1) {
update = true;
}
if (line.indexOf(',') === -1) { //if not color string
try {
value = parseInt(value);
} catch (err) {
//supress parsing error
}
}
//console.dir(parts)
if (!this.variables[key] || update) {
this.variables[key] = value;
}
console.dir(this.variables)
}
variable_replace(line) {
}
/**
* Apply a basic two character command
*/
basic_cmd (line) {
if (this.rec !== -1) {
//hold generated arr in state loop array
this.loops[this.rec].arr
.push.apply(this.loops[this.rec].arr,
this.str_to_arr(line,
this.two));
this.loops[this.rec].light
.push.apply(this.loops[this.rec].light,
this.light_to_arr(line,
this.two));
} else {
this.arr.push.apply(this.arr, this.str_to_arr(line, this.two));
this.light.push.apply(this.light, this.light_to_arr(line, this.two))
}
}
/**
* Start a new loop
*/
new_loop (line, fade) {
this.rec++;
this.loops[this.rec] = {
arr : [],
light : [],
cam : 0,
proj : 0,
cmd : line + ''
};
if (fade) {
this.fade(line);
}
}
/**
* Close the most recent loop
*/
end_loop (line) {
let light_arr;
let start;
let end;
let len;
for (let x = 0; x < this.loop_count(this.loops[this.rec].cmd); x++) {
light_arr = this.loops[this.rec].light;
if (this.loops[this.rec].fade) {
start = this.loops[this.rec].start;
end = this.loops[this.rec].end;
len = this.loops[this.rec].fade_len;
light_arr = light_arr.map(l => {
return this.fade_rgb(start, end, len, x);
})
}
if (this.rec === 0) {
this.arr.push.apply(this.arr, this.loops[this.rec].arr);
this.light.push.apply(this.light, light_arr);
} else if (this.rec >= 1) {
this.loops[this.rec - 1].arr
.push.apply(this.loops[this.rec - 1].arr,
this.loops[this.rec].arr);
this.loops[this.rec - 1].light
.push.apply(this.loops[this.rec - 1].light,
light_arr);
}
}
this.update('END', this.loop_count(this.loops[this.rec].cmd));
delete this.loops[this.rec];
this.rec--;
}
/**
* Move camera to explicitly-defined frame
*/
move_cam (line) {
this.target = parseInt(line.split('CAM ')[1]);
if (this.rec !== -1) {
if (this.target > this.cam) {
this.dist = this.target - this.cam;
for (let x = 0; x < this.dist; x++) {
this.loops[this.rec].arr.push('BF');
this.loops[this.rec].light.push(BLACK);
this.update('BF');
}
} else {
this.dist = this.cam - this.target;
for (let x = 0; x < this.dist; x++) {
this.loops[this.rec].arr.push('BB');
this.loops[this.rec].light.push(BLACK);
this.update('BB');
}
}
} else {
if (target > this.cam) {
this.dist = this.target - this.cam;
for (let x = 0; x < this.dist; x++) {
this.arr.push('BF');
this.light.push(BLACK);
this.update('BF');
}
} else {
this.dist = this.cam - this.target;
for (let x = 0; x < this.dist; x++) {
this.arr.push('BB');
this.light.push(BLACK);
this.update('BB');
}
}
}
}
/**
* Move projector to explicitly-defined frame
*/
move_proj (line) {
this.target = parseInt(line.split('PROJ ')[1]);
if (this.rec !== -1) {
if (this.target > this.proj) {
this.dist = this.target - this.proj;
for (let x = 0; x < this.dist; x++) {
this.loops[this.rec].arr.push('PF');
this.loops[this.rec].light.push('');
this.update('PF');
}
} else {
this.dist = this.proj - this.target;
for (let x = 0; x < this.dist; x++) {
this.loops[this.rec].arr.push('PB');
this.loops[this.rec].light.push('');
this.update('PB');
}
}
} else {
if (this.target > this.proj) {
this.dist = this.target - this.proj;
for (let x = 0; x < this.dist; x++) {
this.arr.push('PF');
this.light.push('');
this.update('PF');
}
} else {
this.dist = this.proj - this.target;
for (let x = 0; x < this.dist; x++) {
this.arr.push('PB');
this.light.push('');
this.update('PB');
}
}
}
}
/**
* Set the state of either the cam or projector
*/
set_state (line) {
if (startsWith(line, 'SET CAM')) {
this.cam = parseInt(line.split('SET CAM')[1]);
} else if (startsWith(line, 'SET PROJ')) {
this.proj = parseInt(line.split('SET PROJ')[1]);
}
}
/**
* Return the last loop
*/
last_loop () {
return this.loops[this.loops.length - 1];
}
/**
* Return the second-last loop
*/
parent_loop () {
return this.loops[this.loops.length - 2];
}
/**
* Extract the loop count integer from a LOOP cmd
*/
loop_count (str) {
return parseInt(str.split(' ')[1]);
}
/**
* Execute a fade of frame length, from color to another color
*/
fade (line) {
let len = this.fade_count(line);
let start = this.fade_start(line);
let end = this.fade_end(line);
this.loops[this.rec].start = start;
this.loops[this.rec].end = end;
this.loops[this.rec].fade = true;
this.loops[this.rec].fade_count = 0;
this.loops[this.rec].fade_len = len;
}
/**
* Extract the fade length integer from a FADE cmd
*/
fade_count (str) {
return parseInt(str.split(' ')[1]);
}
/**
* Extract the start color from a string
*/
fade_start (str) {
let color = str.split(' ')[2];
return this.rgb(color.trim())
}
/**
* Extract the end color from a string
*/
fade_end (str) {
let color = str.split(' ')[3];
return this.rgb(color.trim())
}
fade_rgb (start, end, len, x) {
let cur = [];
let diff;
for (let i = 0; i < 3; i++) {
if (x === len - 1) {
cur[i] = end[i];
} else if (start[i] >= end[i]) {
diff = start[i] - end[i];
cur[i] = start[i] - Math.round((diff / (len - 1)) * x);
} else {
diff = end[i] - start[i];
cur[i] = start[i] + Math.round((diff / (len - 1)) * x);
}
}
return this.rgb_str(cur);
}
rgb (str) {
let rgb = str.split(',');
return rgb.map( char => {
return parseInt(char);
})
}
rgb_str (arr) {
return arr.join(',');
}
/**
* Increase the state of a specific object, such as the camera/projector,
* by the value defined in val
*/
update (cmd, val = 1) {
if (cmd === 'END') {
//I don't understand this loop
for (let i = 0; i < val; i++) {
if (this.rec === 0) {
this.cam += this.loops[this.rec].cam;
this.proj += this.loops[this.rec].proj;
} else if (this.rec >= 1) {
this.loops[this.rec - 1].cam += this.loops[this.rec].cam;
this.loops[this.rec - 1].proj += this.loops[this.rec].proj;
}
}
} else if (cmd === 'CF') {
if (this.rec === -1) {
this.cam += val;
} else {
this.loops[this.rec].cam += val;
}
} else if (cmd === 'CB') {
if (this.rec === -1) {
this.cam -= val;
} else {
this.loops[this.rec].cam--;
}
} else if (cmd === 'PF') {
if (this.rec === -1) {
this.proj += val;
} else {
this.loops[this.rec].proj += val;
}
} else if (cmd === 'PB') {
if (this.rec === -1) {
this.proj -= val;
} else {
this.loops[this.rec].proj--;
}
} else if (cmd === 'BF') {
if (this.rec === -1) {
this.cam += val;
} else {
this.loops[this.rec].cam += val;
}
} else if (cmd === 'BB') {
if (this.rec === -1) {
this.cam -= val;
} else {
this.loops[this.rec].cam -= val;
}
} else if (cmd === 'L ') {
}
}
/**
* Split string on command, extract any integers from string
*/
str_to_arr (str, cmd) {
const cnt = str.split(cmd);
let c = parseInt(cnt[1]);
let arr = [];
if (cnt[1] === '') {
c = 1;
} else {
c = parseInt(cnt[1]);
}
arr = new Array(c).fill(cmd);
this.update(cmd, c);
return arr;
}
/**
* Split a string on a command to extract data for light array
*/
light_to_arr (str, cmd) {
const cnt = str.split(cmd);
let c = parseInt(cnt[1]);
let arr = [];
if (cnt[1] === '') {
c = 1;
} else {
c = parseInt(cnt[1]);
}
for (var i = 0; i < c; i++) {
if (cmd === 'CF'
|| cmd === 'CB') {
arr.push(this.color);
} else if (cmd === 'BF'
|| cmd === 'BB') {
arr.push(BLACK);
} else {
arr.push('');
}
}
return arr;
}
/**
* Split a string to extract an rgb color value
*/
light_state (str) {
//add parsers for other color spaces
const color = str.replace('L ', '').trim();
this.color = color;
}
/**
* Throw an error with specific message
*/
fail (msg) {
throw new Error(msg);
}
}
module.exports = Mscript;
/*
CAM # - go to camera frame #
PROJ # - go to projector frame #
SET CAM # - sets camera count to #
SET PROJ # - sets projector count to #
LOOP # - begin loop, can nest recursively, # times
END LOOP - (or END) closes loop
L #RGB - sets light to rgb value
FADE 24 0,0,0 255,255,255
CF - Camera forwards
PF - Projector forwards
BF - Black forwards
CB - Camera backwards
PB - Projector backwards
BB - Black backwards
*/

476
lib/mscript/index.old.js Normal file
View File

@ -0,0 +1,476 @@
'use strict';
/** @module lib/mscript */
let fs;
let input;
/** object mscript */
const mscript = {};
/**
* Check for the presence of specific arguments in process
* argv
*
* @param {string} shrt Short version of argument or flag
* @param {string} lng Long version of argument or flag
*
* @return {boolean} Is flag present
*/
mscript.arg = function arg (shrt, lng) {
if (process.argv.indexOf(shrt) !== -1 ||
process.argv.indexOf(lng) !== -1) {
return true;
}
return false;
};
/**
* Check for the position of specific arguments in process
* argv
*
* @param {string} shrt Short version of argument or flag
* @param {string} lng Long version of argument or flag
*
* @return {boolean} Position of arg or flag, for locating input
*/
mscript.arg_pos = function arg_pos (shrt, lng) {
var pos = -1;
pos = process.argv.indexOf(shrt);
if (pos === -1) {
pos = process.argv.indexOf(lng);
}
return pos;
};
mscript.black = '0,0,0';
mscript.cmd = [
'CF',
'PF',
'BF',
'CB',
'PB',
'BB'
];
mscript.alts = {
'CF' : ['CAMERA FORWARD', 'CAM FORWARD'],
'PF' : ['PROJECTOR FORWARD', 'PROJ FORWARD'],
'BF' : ['BLACK FORWARD', 'BLACK', 'BLANK FORWARD', 'BLANK'],
'CB' : ['CAMERA BACKWARD', 'CAM BACKWARD', 'CAMERA BACK', 'CAM BACK'],
'PB' : ['PROJECTOR FORWARD', 'PROJ FORWARD', 'PROJECTOR BACK', 'PROJ BACK'],
'BB' : ['BLACK BACKWARD', 'BLACK BACK', 'BLANK BACK'],
'L ' : ['LIGHT', 'COLOR', 'LAMP'],
'F ' : ['FADE']
};
mscript.state = {};
/**
* Clear the state object
*/
mscript.state_clear = function state_clear () {
mscript.state = {
cam : 0,
proj : 0,
color : '',
loops : [],
rec : -1
};
};
/**
* This is never used and doesn't do anything?
*/
mscript.alts_unique = function alts_unique () {
var ids = Object.keys(mscript.alts),
all = [];
for (var i = 0; i < ids.length; i++) {
if (all.indexOf(ids[i]) === -1) {
all.push(ids[i]);
} else {
mscript.fail("Can't compile");
}
}
};
/**
*
*/
mscript.interpret = function interpret (text, callback) {
mscript.state_clear();
if (typeof text === 'undefined') {
mscript.fail('No input');
}
var lines = text.split('\n'),
two = '',
arr = [],
light = [],
target = 0,
dist = 0, //?
output = {};
for (var i = 0; i < lines.length; i++) {
lines[i] = lines[i].replace(/\t+/g, ""); //strip tabs
lines[i] = lines[i].trim(); //remove excess whitespace before and after command
two = lines[i].substring(0, 2);
if (mscript.cmd.indexOf(two) !== -1) {
if (mscript.state.loops.length > 0) {
//hold generated arr in state loop array
mscript.state.loops[mscript.state.rec].arr
.push.apply(mscript.state.loops[mscript.state.rec].arr,
mscript.str_to_arr(lines[i],
two));
mscript.state.loops[mscript.state.rec].light
.push.apply(mscript.state.loops[mscript.state.rec].light,
mscript.light_to_arr(lines[i],
two));
} else {
arr.push.apply(arr, mscript.str_to_arr(lines[i], two));
light.push.apply(light, mscript.light_to_arr(lines[i], two))
}
} else if (lines[i].substring(0, 4) === 'LOOP') {
mscript.state.rec++;
mscript.state.loops[mscript.state.rec] = {
arr : [],
light : [],
cam : 0,
proj : 0,
cmd : lines[i] + ''
};
} else if (lines[i].substring(0, 2) === 'L ') {
mscript.light_state(lines[i]);
} else if (lines[i].substring(0, 3) === 'END') {
for (var x = 0; x < mscript.loop_count(mscript.state.loops[mscript.state.rec].cmd); x++) {
if (mscript.state.rec === 0) {
arr.push.apply(arr, mscript.state.loops[mscript.state.rec].arr);
light.push.apply(light, mscript.state.loops[mscript.state.rec].light);
} else if (mscript.state.rec >= 1) {
mscript.state.loops[mscript.state.rec - 1].arr
.push.apply(mscript.state.loops[mscript.state.rec - 1].arr,
mscript.state.loops[mscript.state.rec].arr);
mscript.state.loops[mscript.state.rec - 1].light
.push.apply(mscript.state.loops[mscript.state.rec - 1].light,
mscript.state.loops[mscript.state.rec].light);
}
}
mscript.state_update('END', mscript.loop_count(mscript.state.loops[mscript.state.rec].cmd));
delete mscript.state.loops[mscript.state.rec];
mscript.state.rec--;
} else if (lines[i].substring(0, 3) === 'CAM') { //directly go to that frame (black?)
target = parseInt(lines[i].split('CAM ')[1]);
if (mscript.state.loops.length > 0) {
if (target > mscript.state.cam) {
dist = target - mscript.state.cam;
for (var x = 0; x < dist; x++) {
mscript.state.loops[mscript.state.rec].arr.push('BF');
mscript.state.loops[mscript.state.rec].light.push(mscript.black);
mscript.state_update('BF');
}
} else {
dist = mscript.state.cam - target;
for (var x = 0; x < dist; x++) {
mscript.state.loops[mscript.state.rec].arr.push('BB');
mscript.state.loops[mscript.state.rec].light.push(mscript.black);
mscript.state_update('BB');
}
}
} else {
if (target > mscript.state.cam) {
dist = target - mscript.state.cam;
for (var x = 0; x < dist; x++) {
arr.push('BF');
light.push(mscript.black);
mscript.state_update('BF');
}
} else {
dist = mscript.state.cam - target;
for (var x = 0; x < dist; x++) {
arr.push('BB');
light.push(mscript.black);
mscript.state_update('BB');
}
}
}
} else if (lines[i].substring(0, 4) === 'PROJ') { //directly go to that frame
target = parseInt(lines[i].split('PROJ ')[1]);
if (mscript.state.loops.length > 0) {
if (target > mscript.state.proj) {
dist = target - mscript.state.proj;
for (var x = 0; x < dist; x++) {
mscript.state.loops[mscript.state.rec].arr.push('PF');
mscript.state.loops[mscript.state.rec].light.push('');
mscript.state_update('PF');
}
} else {
dist = mscript.state.proj - target;
for (var x = 0; x < dist; x++) {
mscript.state.loops[mscript.state.rec].arr.push('PB');
mscript.state.loops[mscript.state.rec].light.push('');
mscript.state_update('PB');
}
}
} else {
if (target > mscript.state.proj) {
dist = target - mscript.state.proj;
for (var x = 0; x < dist; x++) {
arr.push('PF');
light.push('');
mscript.state_update('PF');
}
} else {
dist = mscript.state.proj - target;
for (var x = 0; x < dist; x++) {
arr.push('PB');
light.push('');
mscript.state_update('PB');
}
}
}
} else if (lines[i].substring(0, 3) === 'SET') { //set that state
if (lines[i].substring(0, 7) === 'SET CAM') {
mscript.state.cam = parseInt(lines[i].split('SET CAM')[1]);
} else if (lines[i].substring(0, 8) === 'SET PROJ') {
mscript.state.proj = parseInt(lines[i].split('SET PROJ')[1]);
}
} else if (lines[i].substring(0, 1) === '#' || lines[i].substring(0, 2) === '//') {
//comments
//ignore while parsing
}
}
output.success = true;
output.arr = arr;
output.light = light;
output.cam = mscript.state.cam;
output.proj = mscript.state.proj;
if (typeof callback !== 'undefined') {
//should only be invoked by running mscript.tests()
callback(output);
} else {
return mscript.output(output);
}
};
/**
*
*/
mscript.last_loop = function last_loop () {
return mscript.state.loops[mscript.state.loops.length - 1];
};
/**
*
*/
mscript.parent_loop = function parent_loop () {
return mscript.state.loops[mscript.state.loops.length - 2];
};
/**
*
*/
mscript.state_update = function state_update (cmd, val) {
if (cmd === 'END') {
for (var i = 0; i < val; i++) {
if (mscript.state.rec === 0) {
mscript.state.cam += mscript.state.loops[mscript.state.rec].cam;
mscript.state.proj += mscript.state.loops[mscript.state.rec].proj;
} else if (mscript.state.rec >= 1) {
mscript.state.loops[mscript.state.rec - 1].cam += mscript.state.loops[mscript.state.rec].cam;
mscript.state.loops[mscript.state.rec - 1].proj += mscript.state.loops[mscript.state.rec].proj;
}
}
} else if (cmd === 'CF') {
if (mscript.state.loops.length < 1) {
mscript.state.cam++;
} else {
mscript.state.loops[mscript.state.rec].cam++;
}
} else if (cmd === 'CB') {
if (mscript.state.loops.length < 1) {
mscript.state.cam--;
} else {
mscript.state.loops[mscript.state.rec].cam--;
}
} else if (cmd === 'PF') {
if (mscript.state.loops.length < 1) {
mscript.state.proj++;
} else {
mscript.state.loops[mscript.state.rec].proj++;
}
} else if (cmd === 'PB') {
if (mscript.state.loops.length < 1) {
mscript.state.proj--;
} else {
mscript.state.loops[mscript.state.rec].proj--;
}
} else if (cmd === 'BF') {
if (mscript.state.loops.length < 1) {
mscript.state.cam++;
} else {
mscript.state.loops[mscript.state.rec].cam++;
}
} else if (cmd === 'BB') {
if (mscript.state.loops.length < 1) {
mscript.state.cam--;
} else {
mscript.state.loops[mscript.state.rec].cam++;
}
} else if (cmd === 'L ') {
}
};
/**
*
*/
mscript.str_to_arr = function str_to_arr (str, cmd) {
var cnt = str.split(cmd),
c = parseInt(cnt[1]),
arr = [];
if (cnt[1] === '') {
c = 1;
} else {
c = parseInt(cnt[1]);
}
for (var i = 0; i < c; i++) {
arr.push(cmd);
mscript.state_update(cmd);
}
return arr;
};
/**
*
*/
mscript.light_state = function light_state (str) {
//add parsers for other color spaces
var color = str.replace('L ', '').trim();
mscript.state.color = color;
};
/**
*
*/
mscript.light_to_arr = function light_to_arr (str, cmd) {
var cnt = str.split(cmd),
c = parseInt(cnt[1]),
arr = [];
if (cnt[1] === '') {
c = 1;
} else {
c = parseInt(cnt[1]);
}
for (var i = 0; i < c; i++) {
if (cmd === 'CF'
|| cmd === 'CB') {
arr.push(mscript.state.color);
} else if (cmd === 'BF'
|| cmd === 'BB') {
arr.push(mscript.black);
} else {
arr.push('');
}
}
return arr;
};
/**
*
*/
mscript.loop_count = function loop_count (str) {
return parseInt(str.split(' ')[1]);
};
mscript.fade_count = function fade_count (str) {
return parseInt(str.split(' ')[1]);
}
/**
*
*/
mscript.fail = function fail (reason) {
console.error(JSON.stringify({success: false, error: true, msg : reason}));
if (process) process.exit();
};
/**
*
*/
mscript.output = function output (data) {
var json = true; //default
if (mscript.arg('-j', '--json')) {
json = true;
}
if (mscript.arg('-t', '--text')) {
json = false;
}
if (json) {
console.log(JSON.stringify(data));
} else {
var ids = Object.keys(data);
for (var i = 0; i < ids.length; i++) {
console.log(ids[i] + ': ' + data[ids[i]]);
}
}
};
/**
*
*/
mscript.init = function init () {
if (mscript.arg('-t', '--tests')) {
return mscript.tests();
}
if (mscript.arg('-v', '--verbose')) {
console.time('mscript');
}
if (mscript.arg('-c', '--cam')) {
mscript.state.cam = parseInt(process.argv[mscript.arg_pos('-c', '--cam') + 1]);
}
if (mscript.arg('-p', '--proj')) {
mscript.state.proj = parseInt(process.argv[mscript.arg_pos('-p', '--proj') + 1]);
}
if (mscript.arg('-f', '--file')) {
input = process.argv[mscript.arg_pos('-f', '--file') + 1];
mscript.interpret(fs.readFileSync(input, 'utf8'));
} else {
mscript.interpret(input);
}
if (mscript.arg('-v', '--verbose')) {
console.timeEnd('mscript');
}
};
if (typeof document === 'undefined'
&& typeof module !== 'undefined'
&& !module.parent) {
//node script
fs = require('fs');
input = process.argv[2];
mscript.init();
} else if (typeof module !== 'undefined' && module.parent) {
//module
fs = require('fs');
module.exports = mscript;
} else {
//web
}
/*
CAM # - go to camera frame #
PROJ # - go to projector frame #
SET CAM # - sets camera count to #
SET PROJ # - sets projector count to #
LOOP # - begin loop, can nest recursively, # times
END LOOP - (or END) closes loop
L #RGB - sets light to rgb value
FADE
CF - Camera forwards
PF - Projector forwards
BF - Black forwards
CB - Camera backwards
PB - Projector backwards
BB - Black backwards
*/

11
lib/mscript/package.json Normal file
View File

@ -0,0 +1,11 @@
{
"name": "mscript",
"version": "1.0.0",
"description": "<a name=\"module_lib/mscript\"></a>",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

20
package-lock.json generated Normal file
View File

@ -0,0 +1,20 @@
{
"name": "mcopy",
"version": "0.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "11.10.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.4.tgz",
"integrity": "sha512-wa09itaLE8L705aXd8F80jnFpxz3Y1/KRHfKsYL2bPc0XF+wEWu8sR9n5bmeu8Ba1N9z2GRNzm/YdHcghLkLKg==",
"dev": true
},
"typescript": {
"version": "3.3.3333",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz",
"integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==",
"dev": true
}
}
}

28
package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "mcopy",
"version": "0.1.0",
"description": "Small gauge film optical printer platform",
"main": "build.js",
"directories": {
"doc": "docs",
"lib": "lib"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "sh ./scripts/build.sh"
},
"repository": {
"type": "git",
"url": "git+https://github.com/sixteenmillimeter/mcopy.git"
},
"author": "mmcwilliams",
"license": "MIT",
"bugs": {
"url": "https://github.com/sixteenmillimeter/mcopy/issues"
},
"homepage": "https://github.com/sixteenmillimeter/mcopy#readme",
"devDependencies": {
"@types/node": "^11.10.4",
"typescript": "^3.3.3333"
}
}

7
scripts/build.sh Normal file
View File

@ -0,0 +1,7 @@
#!/bin/sh
./node_modules/.bin/tsc -p tsconfig.json
#electron-build fails when local modules are in parent directory
#copy them into lib directory
cp -r ./lib/ ./app/lib

8
scripts/docs.sh Normal file
View File

@ -0,0 +1,8 @@
#!/bin/sh
libs="./lib/*"
for l in $libs
do
echo "Generating documentation for $l"
./node_modules/.bin/jsdoc2md $l/index.js > $l/Readme.md
done

375
src/arduino/index.ts Normal file
View File

@ -0,0 +1,375 @@
'use strict'
const SerialPort = require('serialport')
const Readline = SerialPort.parsers.Readline
const exec = require('child_process').exec
const parser = new Readline('')
const newlineRe : RegExp = new RegExp('\n', 'g')
const returnRe : RegExp = new RegExp('\r', 'g')
let eventEmitter : any
let cfg : object
/**
* Pause the process for X milliseconds in async/await functions
*
* @param {integer} ms milliseconds
*
* @returns {Promise} Resolves after wait
**/
async function delay (ms : number) {
return new Promise(resolve => {
return setTimeout(resolve, ms)
})
}
/**
* Send a command to an Arduino using async/await
*
* @param {string} device Arduino identifier
* @param {string} cmd Single character command to send
*
* @returns {Promise} Resolves after sending
**/
async function send (device : string, cmd : string) {
return new Promise ((resolve, reject) => {
arduino.queue[cmd] = (ms : number) => {
return resolve(ms)
}
return arduino.serial[device].write(cmd, (err : any, results : any) => {
if (err) {
//console.error(err)
return reject(err)
}
//
})
})
}
/**
* Send a string to an Arduino using async/await
*
* @param {string} device Arduino identifier
* @param {string} str String to send
*
* @returns {Promise} Resolves after sending
**/
async function write (device : string, str : string) {
return new Promise ((resolve, reject) => {
arduino.serial[device].write(str, function (err, results) {
if (err) {
return reject(err)
}
//console.log('sent: ' + str)
return resolve(results)
})
})
}
/**
* Connect to an Arduino using async/await
*
* @param {string} device Arduino identifier
*
* @returns {Promise} Resolves after opening
**/
async function openArduino (device : string) {
return new Promise((resolve, reject) => {
return arduino.serial[device].open(error => {
if (error) {
return reject(error)
}
return resolve(true)
})
})
}
/**
* Close a connection to an Arduino using async/await
*
* @param {string} device Arduino identifier
*
* @returns {Promise} Resolves after closing
**/
async function closeArduino (device : string) {
return new Promise((resolve : any, reject : any) => {
return arduino.serial[device].close((err) => {
if (err) {
return reject(err)
}
return resolve(true)
})
})
}
/******
Arduino handlers
*******/
const arduino = {
path : {},
known: [
'/dev/tty.usbmodem1a161',
'/dev/tty.usbserial-A800f8dk',
'/dev/tty.usbserial-A900cebm',
'/dev/tty.usbmodem1a131',
'/dev/tty.usbserial-a900f6de',
'/dev/tty.usbmodem1a141',
'/dev/ttyACM0',
'COM3'
],
alias : {
},
serial : {
connect : {},
projector : {},
camera : {},
light : {}
},
baud : 57600,
queue : {},
timer : 0,
lock : false,
locks : {
}
}
arduino.enumerate = async function () {
return new Promise( (resolve, reject) => {
return SerialPort.list((err, ports) => {
let matches = []
if (err) {
return reject(err)
}
ports.forEach(port => {
if (arduino.known.indexOf(port.comName) !== -1) {
matches.push(port.comName)
} else if ((port.manufacturer + '').toLowerCase().indexOf('arduino') !== -1) {
matches.push(port.comName)
} else if ((port.comName + '').toLowerCase().indexOf('usbserial') !== -1) {
matches.push(port.comName)
} else if ((port.comName + '').toLowerCase().indexOf('usbmodem') !== -1) {
matches.push(port.comName)
}
})
if (matches.length === 0) {
return reject('No USB devices found');
} else if (matches.length > 0) {
return resolve(matches)
}
})
})
}
//commands which respond to a sent char
arduino.send = async function (serial, cmd, res) {
const device = arduino.alias[serial]
let results
if (arduino.locks[serial]) {
return false
}
arduino.locks[serial] = true
await delay(cfg.arduino.serialDelay)
try {
results = await send(device, cmd)
} catch (e) {
return console.error(e)
}
arduino.locks[serial] = false
arduino.timer = new Date().getTime()
return await eventEmitter.emit('arduino_send', cmd)
}
//send strings, after char triggers firmware to accept
arduino.string = async function (serial, str) {
const device = arduino.alias[serial]
let writeSuccess
await delay(cfg.arduino.serialDelay)
if (typeof arduino.serial[device].fake !== 'undefined'
&& arduino.serial[device].fake) {
return arduino.serial[device].string(str)
} else {
try {
writeSuccess = await write(device, str)
} catch (e) {
return console.error(e)
}
return writeSuccess
}
}
//respond with same char over serial when done
arduino.end = async function (serial, data) {
const end = new Date().getTime()
const ms = end - arduino.timer
let complete
if (arduino.queue[data] !== undefined) {
arduino.locks[serial] = false;
//console.log('Command ' + data + ' took ' + ms + 'ms');
complete = arduino.queue[data](ms) //execute callback
eventEmitter.emit('arduino_end', data)
delete arduino.queue[data]
} else {
//console.log('Received stray "' + data + '"'); //silent to user
}
return complete
};
arduino.alias = function (serial, device) {
console.log(`Making "${serial}" an alias of ${device}`)
arduino.alias[serial] = device
}
arduino.connect = async function (serial, device, confirm) {
return new Promise(async (resolve, reject) => {
let connectSuccess
arduino.path[serial] = device;
arduino.alias[serial] = device;
arduino.serial[device] = new SerialPort(arduino.path[serial], {
autoOpen : false,
baudRate: cfg.arduino.baud,
parser: parser
})
arduino.locks[device] = false
try {
connectSuccess = await openArduino(device)
} catch (e) {
console.error('failed to open: ' + e)
return reject(e)
}
console.log(`Opened connection with ${arduino.path[serial]} as ${serial}`);
if (!confirm) {
arduino.serial[device].on('data', async (data) => {
let d = data.toString('utf8')
d = d.replace(newlineRe, '').replace(returnRe, '')
return await arduino.end(serial, d)
})
} else {
arduino.serial[device].on('data', async (data) => {
let d = data.toString('utf8')
d = d.replace(newlineRe, '').replace(returnRe, '')
return await arduino.confirmEnd(d)
})
}
return resolve(arduino.path[serial])
})
}
arduino.confirmExec = {};
arduino.confirmEnd = function (data) {
//console.dir(data)
if (data === cfg.arduino.cmd.connect
|| data === cfg.arduino.cmd.proj_identifier
|| data === cfg.arduino.cmd.cam_identifier
|| data === cfg.arduino.cmd.light_identifier
|| data === cfg.arduino.cmd.proj_light_identifier
|| data === cfg.arduino.cmd.proj_cam_light_identifier
|| data === cfg.arduino.cmd.proj_cam_identifier ) {
arduino.confirmExec(null, data);
arduino.confirmExec = {};
}
}
arduino.verify = async function () {
return new Promise(async (resolve, reject) => {
const device = arduino.alias['connect']
let writeSuccess
arduino.confirmExec = function (err, data) {
if (data === cfg.arduino.cmd.connect) {
return resolve(true)
} else {
return reject('Wrong data returned')
}
}
await delay(cfg.arduino.serialDelay)
try {
writeSuccess = await send(device, cfg.arduino.cmd.connect)
} catch (e) {
return reject(e)
}
return resolve(writeSuccess)
})
}
arduino.distinguish = async function () {
return new Promise(async (resolve, reject) => {
const device = arduino.alias['connect']
let writeSuccess
let type
arduino.confirmExec = function (err, data) {
if (data === cfg.arduino.cmd.proj_identifier) {
type = 'projector'
} else if (data === cfg.arduino.cmd.cam_identifier) {
type = 'camera'
} else if (data === cfg.arduino.cmd.light_identifier) {
type = 'light'
} else if (data === cfg.arduino.cmd.proj_light_identifier) {
type = 'projector,light'
} else if (data === cfg.arduino.cmd.proj_cam_light_identifier) {
type = 'projector,camera,light'
} else if (data === cfg.arduino.cmd.proj_cam_identifier) {
type = 'projector,camera'
} else if (data === cfg.ardino.cmd.proj_second_identifier) {
type = 'projector_second'
}
return resolve(type)
}
await delay(cfg.arduino.serialDelay)
try {
writeSuccess = await send(device, cfg.arduino.cmd.mcopy_identifier)
} catch (e) {
console.error(e)
return reject(e)
}
})
}
arduino.close = async function (callback) {
const device = arduino.alias['connect']
let closeSuccess
try {
closeSuccess = await closeArduino(device)
} catch (e) {
return console.error(e)
}
return closeSuccess
};
arduino.fakeConnect = async function (serial) {
//console.log('Connecting to fake arduino...');
const device = '/dev/fake'
arduino.alias[serial] = device
arduino.serial[device] = {
write : function (cmd, cb) {
const t = {
c : cfg.arduino.cam.time + cfg.arduino.cam.delay,
p : cfg.arduino.proj.time + cfg.arduino.proj.delay
}
let timeout = t[cmd]
let end
if (typeof timeout === 'undefined') timeout = 10
arduino.timer = +new Date()
setTimeout(() => {
arduino.end(serial, cmd)
return cb()
}, timeout)
},
string : async function (str) {
//do nothing
return true
},
fake : true
};
//console.log('Connected to fake arduino! Not real! Doesn\'t exist!');
return true
}
if (typeof module !== 'undefined' && module.parent) {
module.exports = function (c, ee) {
eventEmitter = ee
cfg = c
return arduino
}
}

545
src/mscript/index.js Normal file
View File

@ -0,0 +1,545 @@
'use strict';
/** @module lib/mscript */
const BLACK = '0,0,0';
const WHITE = '255,255,255';
const CMD = [
'CF',
'PF',
'BF',
'CB',
'PB',
'BB'
];
const ALTS = {
'CF' : ['CAMERA FORWARD', 'CAM FORWARD'],
'PF' : ['PROJECTOR FORWARD', 'PROJ FORWARD'],
'BF' : ['BLACK FORWARD', 'BLACK', 'BLANK FORWARD', 'BLANK'],
'CB' : ['CAMERA BACKWARD', 'CAM BACKWARD', 'CAMERA BACK', 'CAM BACK'],
'PB' : ['PROJECTOR FORWARD', 'PROJ FORWARD', 'PROJECTOR BACK', 'PROJ BACK'],
'BB' : ['BLACK BACKWARD', 'BLACK BACK', 'BLANK BACK'],
'L ' : ['LIGHT', 'COLOR', 'LAMP'],
'F ' : ['FADE']
};
/** helper functions */
/** startswith function from lodash, do not want the entire lib for this */
function startsWith(string, target, position) {
const { length } = string;
position = position == null ? 0 : position;
if (position < 0) {
position = 0;
} else if (position > length) {
position = length;
}
target = `${target}`;
return string.slice(position, position + target.length) == target;
}
/** class Mscript */
class Mscript {
constructor () {
this.output = {};
}
/**
* Clear the state of the script
*/
clear () {
this.lines = [];
this.cam = 0;
this.proj = 0;
this.color = '';
this.loops = [];
this.rec = -1;
this.two = '';
this.arr = [];
this.light = [];
this.target = 0; //move to target using CAM # or PROJ #
this.dist = 0;
this.variables = {};
this.output = {};
}
/**
* Main function, accepts multi-line string, parses into lines
* and interprets the instructions from the text. Returns an array
* of steps to be fed into the mcopy.
*/
interpret (text, callback) {
this.clear()
if (typeof text === 'undefined') {
return this.fail('No input');
}
//split string into lines, each containing a command
this.lines = text.split('\n');
this.lines = this.lines.map(line => {
line = line.replace(/\t+/g, ''); //strip tabs
line = line.trim(); //remove excess whitespace before and after command
line = line.toUpperCase();
return line;
})
for (let line of this.lines) {
this.two = line.substring(0, 2);
if (CMD.indexOf(this.two) !== -1) {
this.basic_cmd(line);
} else if (startsWith(line, '@') || line.indexOf('@') !== -1) {
this.variable(line);
} else if (startsWith(line, 'LOOP')) {
this.new_loop(line);
} else if (startsWith(line, 'L ')) {
this.light_state(line);
} else if (startsWith(line, 'F ')) {
this.new_loop(line, true);
} else if (startsWith(line, 'END')) {
this.end_loop(line);
} else if (startsWith(line, 'CAM')) { //directly go to that frame (black?)
this.move_cam(line);
} else if (startsWith(line, 'PROJ')) { //directly go to that frame
this.move_proj(line);
} else if (startsWith(line, 'SET')) { //set that state
this.set_state(line);
} else if (startsWith(line, '#') || startsWith(line, '//')) {
//comments
//ignore while parsing
}
}
this.output.success = true;
this.output.arr = this.arr; //all instructions
this.output.light = this.light; //all light instructions
this.output.cam = this.cam;
this.output.proj = this.proj;
if (typeof callback !== 'undefined') {
//should only be invoked by running mscript.tests()
callback(this.output);
} else {
return this.output;
}
}
variable (line) {
let parts = line.split('=');
let key = parts[0];
let value = parts[1];
let update = false;
if (value && value.indexOf('#') !== -1) {
value = value.split('#')[0];
}
if (value && value.indexOf('//') !== -1) {
value = value.split('//')[0];
}
if (value && value.indexOf('+') !== -1) {
if (value)
update = true;
}
if (line.indexOf('-') !== -1) {
update = true;
}
if (line.indexOf(',') === -1) { //if not color string
try {
value = parseInt(value);
} catch (err) {
//supress parsing error
}
}
//console.dir(parts)
if (!this.variables[key] || update) {
this.variables[key] = value;
}
console.dir(this.variables)
}
variable_replace(line) {
}
/**
* Apply a basic two character command
*/
basic_cmd (line) {
if (this.rec !== -1) {
//hold generated arr in state loop array
this.loops[this.rec].arr
.push.apply(this.loops[this.rec].arr,
this.str_to_arr(line,
this.two));
this.loops[this.rec].light
.push.apply(this.loops[this.rec].light,
this.light_to_arr(line,
this.two));
} else {
this.arr.push.apply(this.arr, this.str_to_arr(line, this.two));
this.light.push.apply(this.light, this.light_to_arr(line, this.two))
}
}
/**
* Start a new loop
*/
new_loop (line, fade) {
this.rec++;
this.loops[this.rec] = {
arr : [],
light : [],
cam : 0,
proj : 0,
cmd : line + ''
};
if (fade) {
this.fade(line);
}
}
/**
* Close the most recent loop
*/
end_loop (line) {
let light_arr;
let start;
let end;
let len;
for (let x = 0; x < this.loop_count(this.loops[this.rec].cmd); x++) {
light_arr = this.loops[this.rec].light;
if (this.loops[this.rec].fade) {
start = this.loops[this.rec].start;
end = this.loops[this.rec].end;
len = this.loops[this.rec].fade_len;
light_arr = light_arr.map(l => {
return this.fade_rgb(start, end, len, x);
})
}
if (this.rec === 0) {
this.arr.push.apply(this.arr, this.loops[this.rec].arr);
this.light.push.apply(this.light, light_arr);
} else if (this.rec >= 1) {
this.loops[this.rec - 1].arr
.push.apply(this.loops[this.rec - 1].arr,
this.loops[this.rec].arr);
this.loops[this.rec - 1].light
.push.apply(this.loops[this.rec - 1].light,
light_arr);
}
}
this.update('END', this.loop_count(this.loops[this.rec].cmd));
delete this.loops[this.rec];
this.rec--;
}
/**
* Move camera to explicitly-defined frame
*/
move_cam (line) {
this.target = parseInt(line.split('CAM ')[1]);
if (this.rec !== -1) {
if (this.target > this.cam) {
this.dist = this.target - this.cam;
for (let x = 0; x < this.dist; x++) {
this.loops[this.rec].arr.push('BF');
this.loops[this.rec].light.push(BLACK);
this.update('BF');
}
} else {
this.dist = this.cam - this.target;
for (let x = 0; x < this.dist; x++) {
this.loops[this.rec].arr.push('BB');
this.loops[this.rec].light.push(BLACK);
this.update('BB');
}
}
} else {
if (target > this.cam) {
this.dist = this.target - this.cam;
for (let x = 0; x < this.dist; x++) {
this.arr.push('BF');
this.light.push(BLACK);
this.update('BF');
}
} else {
this.dist = this.cam - this.target;
for (let x = 0; x < this.dist; x++) {
this.arr.push('BB');
this.light.push(BLACK);
this.update('BB');
}
}
}
}
/**
* Move projector to explicitly-defined frame
*/
move_proj (line) {
this.target = parseInt(line.split('PROJ ')[1]);
if (this.rec !== -1) {
if (this.target > this.proj) {
this.dist = this.target - this.proj;
for (let x = 0; x < this.dist; x++) {
this.loops[this.rec].arr.push('PF');
this.loops[this.rec].light.push('');
this.update('PF');
}
} else {
this.dist = this.proj - this.target;
for (let x = 0; x < this.dist; x++) {
this.loops[this.rec].arr.push('PB');
this.loops[this.rec].light.push('');
this.update('PB');
}
}
} else {
if (this.target > this.proj) {
this.dist = this.target - this.proj;
for (let x = 0; x < this.dist; x++) {
this.arr.push('PF');
this.light.push('');
this.update('PF');
}
} else {
this.dist = this.proj - this.target;
for (let x = 0; x < this.dist; x++) {
this.arr.push('PB');
this.light.push('');
this.update('PB');
}
}
}
}
/**
* Set the state of either the cam or projector
*/
set_state (line) {
if (startsWith(line, 'SET CAM')) {
this.cam = parseInt(line.split('SET CAM')[1]);
} else if (startsWith(line, 'SET PROJ')) {
this.proj = parseInt(line.split('SET PROJ')[1]);
}
}
/**
* Return the last loop
*/
last_loop () {
return this.loops[this.loops.length - 1];
}
/**
* Return the second-last loop
*/
parent_loop () {
return this.loops[this.loops.length - 2];
}
/**
* Extract the loop count integer from a LOOP cmd
*/
loop_count (str) {
return parseInt(str.split(' ')[1]);
}
/**
* Execute a fade of frame length, from color to another color
*/
fade (line) {
let len = this.fade_count(line);
let start = this.fade_start(line);
let end = this.fade_end(line);
this.loops[this.rec].start = start;
this.loops[this.rec].end = end;
this.loops[this.rec].fade = true;
this.loops[this.rec].fade_count = 0;
this.loops[this.rec].fade_len = len;
}
/**
* Extract the fade length integer from a FADE cmd
*/
fade_count (str) {
return parseInt(str.split(' ')[1]);
}
/**
* Extract the start color from a string
*/
fade_start (str) {
let color = str.split(' ')[2];
return this.rgb(color.trim())
}
/**
* Extract the end color from a string
*/
fade_end (str) {
let color = str.split(' ')[3];
return this.rgb(color.trim())
}
fade_rgb (start, end, len, x) {
let cur = [];
let diff;
for (let i = 0; i < 3; i++) {
if (x === len - 1) {
cur[i] = end[i];
} else if (start[i] >= end[i]) {
diff = start[i] - end[i];
cur[i] = start[i] - Math.round((diff / (len - 1)) * x);
} else {
diff = end[i] - start[i];
cur[i] = start[i] + Math.round((diff / (len - 1)) * x);
}
}
return this.rgb_str(cur);
}
rgb (str) {
let rgb = str.split(',');
return rgb.map( char => {
return parseInt(char);
})
}
rgb_str (arr) {
return arr.join(',');
}
/**
* Increase the state of a specific object, such as the camera/projector,
* by the value defined in val
*/
update (cmd, val = 1) {
if (cmd === 'END') {
//I don't understand this loop
for (let i = 0; i < val; i++) {
if (this.rec === 0) {
this.cam += this.loops[this.rec].cam;
this.proj += this.loops[this.rec].proj;
} else if (this.rec >= 1) {
this.loops[this.rec - 1].cam += this.loops[this.rec].cam;
this.loops[this.rec - 1].proj += this.loops[this.rec].proj;
}
}
} else if (cmd === 'CF') {
if (this.rec === -1) {
this.cam += val;
} else {
this.loops[this.rec].cam += val;
}
} else if (cmd === 'CB') {
if (this.rec === -1) {
this.cam -= val;
} else {
this.loops[this.rec].cam--;
}
} else if (cmd === 'PF') {
if (this.rec === -1) {
this.proj += val;
} else {
this.loops[this.rec].proj += val;
}
} else if (cmd === 'PB') {
if (this.rec === -1) {
this.proj -= val;
} else {
this.loops[this.rec].proj--;
}
} else if (cmd === 'BF') {
if (this.rec === -1) {
this.cam += val;
} else {
this.loops[this.rec].cam += val;
}
} else if (cmd === 'BB') {
if (this.rec === -1) {
this.cam -= val;
} else {
this.loops[this.rec].cam -= val;
}
} else if (cmd === 'L ') {
}
}
/**
* Split string on command, extract any integers from string
*/
str_to_arr (str, cmd) {
const cnt = str.split(cmd);
let c = parseInt(cnt[1]);
let arr = [];
if (cnt[1] === '') {
c = 1;
} else {
c = parseInt(cnt[1]);
}
arr = new Array(c).fill(cmd);
this.update(cmd, c);
return arr;
}
/**
* Split a string on a command to extract data for light array
*/
light_to_arr (str, cmd) {
const cnt = str.split(cmd);
let c = parseInt(cnt[1]);
let arr = [];
if (cnt[1] === '') {
c = 1;
} else {
c = parseInt(cnt[1]);
}
for (var i = 0; i < c; i++) {
if (cmd === 'CF'
|| cmd === 'CB') {
arr.push(this.color);
} else if (cmd === 'BF'
|| cmd === 'BB') {
arr.push(BLACK);
} else {
arr.push('');
}
}
return arr;
}
/**
* Split a string to extract an rgb color value
*/
light_state (str) {
//add parsers for other color spaces
const color = str.replace('L ', '').trim();
this.color = color;
}
/**
* Throw an error with specific message
*/
fail (msg) {
throw new Error(msg);
}
}
module.exports = Mscript;
/*
CAM # - go to camera frame #
PROJ # - go to projector frame #
SET CAM # - sets camera count to #
SET PROJ # - sets projector count to #
LOOP # - begin loop, can nest recursively, # times
END LOOP - (or END) closes loop
L #RGB - sets light to rgb value
FADE 24 0,0,0 255,255,255
CF - Camera forwards
PF - Projector forwards
BF - Black forwards
CB - Camera backwards
PB - Projector backwards
BB - Black backwards
*/

14
tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "ES2017",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"removeComments" : false,
"outDir": "./lib/",
"rootDir" : "./src/"
},
"exclude" : []
}