diff --git a/package-lock.json b/package-lock.json index ce68a24e..548087ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1498,6 +1498,11 @@ "@octokit/openapi-types": "^10.0.0" } }, + "@socket.io/component-emitter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz", + "integrity": "sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q==" + }, "@soda/friendly-errors-webpack-plugin": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.0.tgz", @@ -3209,6 +3214,11 @@ "@babel/helper-define-polyfill-provider": "^0.2.2" } }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3270,6 +3280,11 @@ } } }, + "base64-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz", + "integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA==" + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -5757,6 +5772,50 @@ "once": "^1.4.0" } }, + "engine.io-client": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.0.1.tgz", + "integrity": "sha512-/7xmDxU2i9aYmqz7ekseB2jUfUYvJ5s3+OJcEvObloQNB31wd+0j68kmLVAoZG7Qu0FzB+EN46hsc3bY/kjgSw==", + "requires": { + "@socket.io/component-emitter": "~3.0.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.0", + "has-cors": "1.1.0", + "parseqs": "0.0.6", + "parseuri": "0.0.6", + "ws": "~8.2.3", + "xmlhttprequest-ssl": "~2.0.0", + "yeast": "0.1.2" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==" + } + } + }, + "engine.io-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.0.tgz", + "integrity": "sha512-wn6QavHEqXoM+cg+x8uUG7GhxLBCfKEKNEsCNc7V2ugj3gB3lK91l1MuZiy6xFB2V9D1eew0aWkmpiT/aBb/KA==", + "requires": { + "base64-arraybuffer": "~1.0.1" + } + }, "enhanced-resolve": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", @@ -7260,6 +7319,11 @@ "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", "dev": true }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -13942,6 +14006,16 @@ } } }, + "parseqs": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", + "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==" + }, + "parseuri": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", + "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==" + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -16688,6 +16762,58 @@ } } }, + "socket.io-client": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.3.0.tgz", + "integrity": "sha512-Cg3N7UWGTT2kYFZ1yShbGyrYDcqiMMv53cLNvOvweGJojwZ3N7x4fB+fyfNaq64tRUjV0Qljyj7nMV8+myBgqg==", + "requires": { + "@socket.io/component-emitter": "~3.0.0", + "backo2": "~1.0.2", + "debug": "~4.3.2", + "engine.io-client": "~6.0.1", + "parseuri": "0.0.6", + "socket.io-parser": "~4.1.1" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "socket.io-parser": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.1.tgz", + "integrity": "sha512-USQVLSkDWE5nbcY760ExdKaJxCE65kcsG/8k5FDGZVVxpD1pA7hABYXYkCUvxUuYYh/+uQw0N/fvBzfT8o07KA==", + "requires": { + "@socket.io/component-emitter": "~3.0.0", + "debug": "~4.3.1" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "sockjs": { "version": "0.3.21", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", @@ -19169,6 +19295,11 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -19309,6 +19440,11 @@ "yargs": "^13.3.0" } }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + }, "yeoman-generator": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-5.4.2.tgz", diff --git a/package.json b/package.json index dd26abd8..c2734f48 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "npm": "^6.14.15", "paper": "^0.12.15", "roboto-fontface": "*", + "socket.io-client": "^4.3.0", "three": "^0.126.1", "trackjs": "^3.10.1", "veeno": "0.0.4", diff --git a/src/cc/control/setup.ts b/src/cc/control/setup.ts new file mode 100644 index 00000000..6d105aa7 --- /dev/null +++ b/src/cc/control/setup.ts @@ -0,0 +1,223 @@ +import { Device } from "@/app"; +import { io, Socket } from "socket.io-client"; +import AnalogValve from "../hardware/analogValve"; +import DigitalValve from "../hardware/digitalValve"; +import DispenserPump from "../hardware/dispenserpump"; +import Microfluidic from "../hardware/microfluidic"; + +export default class Setup { + private __chip: Microfluidic; + private __pumps: Array; + private __peripherals: Array; + private __hardwareMap: Map; + private socket: Socket; + + /** + * The constructor takes a json, then dynamically creates the microfluidic object from that + * @param json + */ + constructor(device: Device) { + //Initialize everything !!!!! + let chip = new Microfluidic(device); + this.__chip = chip; + this.__pumps = []; + this.__peripherals = []; + this.__hardwareMap = new Map(); + + // Create a socket server connection + console.log("Connecting to server..."); + this.socket = io("http://localhost:3000"); + + this.socket.on("connect", () => { + if (this.socket === null) { + console.log("Socket is null"); + } else { + console.log("Connected to:", this.socket.id); // x8WIv7-mJelg7on_ALbx + } + }); + this.socket.on("disconnect", () => { + if (this.socket === null) { + console.log("Socket is null"); + } else { + console.log("Disconnected from:", this.socket.id); + } + }); + } + + /** + * Returns the microfluidic used in the design + * @returns {*} + * @constructor + */ + get chip() { + return this.__chip; + } + + /** + * Attaches the pump to a given valve + * @param valveid id + * @param avalve object + */ + attachAnalogValve(valveid: string, avalve: AnalogValve) { + this.__pumps.push(avalve.dispenserPump); + this.__hardwareMap.set(valveid, avalve); + } + + attachDigitalValve(valveid: string, dvalve: DigitalValve) { + this.__hardwareMap.set(valveid, dvalve); + } + + /** + * Toggles the valve on or off + * + * @param {string} valveid + * @memberof Setup + */ + toggleValve(valveid: string) { + let pump = this.__hardwareMap.get(valveid); + let openvalue = pump.getOpen; + pump.setState(); + } + + /** + * Attaches a new dispenser pump to the microfluidic + * + * @param {string} valveid + * @returns + * @memberof Setup + */ + attachPump(pump: DispenserPump) { + this.__pumps.push(pump); + this.__hardwareMap.set(pump.ID, pump); + } + + /** + * Connects to peripheral manager + * + * @memberof Setup + */ + connect() { + this.socket = io("http://localhost:3000"); + this.socket.on("connect", () => { + console.log("Connected to:", this.socket.id); + }); + } + + disconnect() { + this.socket.disconnect(); + } + + /** + * Sends a command to the microfluidic, this assumes that the command is + * gonna be formatted in the standard comand format + * + * @param {string} deviceName + * @param {number} commandID + * @param {Array} args + * @memberof Setup + */ + sendCommand(deviceName: string, commandID: number, args: Array) { + this.socket.emit("send-command", { + name: deviceName, + "command-id": commandID, + args: args + }); + } + + /** + * Sends the raw data to the device + * + * @param {string} device + * @param {Buffer} payload + * @memberof Setup + */ + sendRaw(device: string, payload: Buffer) { + this.socket.emit("send-raw", { + device: device, + payload: payload + }); + } + + /** + * Returns a list of devices that are connected to the setup + * + * @returns {Promise} + * @memberof Setup + */ + async listDevices(): Promise { + let devicesList: Array = []; + await this.socket.emit("list-devices", (data: Array) => { + devicesList = data; + }); + + return devicesList; + } + + /** + * Renames any of the connections/devices attached to the setup in peripheral manager + * + * @param {string} oldName + * @param {string} newName + * @memberof Setup + */ + renameReference(oldName: string, newName: string) { + this.socket.emit("rename-reference", { + oldName: oldName, + newName: newName + }); + } + + /** + * Lists the connections that are active + * + * @returns {Array} + * @memberof Setup + */ + listConnections(): Array { + let connections: Array = []; + this.socket.emit("list-connections", (data: Array) => { + connections = data; + }); + + return connections; + } + + /** + * Activates a connection + * + * @param {string} deviceName + * @param {string} address + * @memberof Setup + */ + activateConnection(deviceName: string, address: string) { + this.socket.emit("activate-connection", { + name: deviceName, + address: address + }); + } + + /** + * Deactivates a connection + * + * @param {string} deviceName + * @memberof Setup + */ + deactivateConnection(deviceName: string) { + this.socket.emit("deactivate-connection", { + name: deviceName + }); + } + + /** + * Attaches a callback function to an RX connection + * + * @param {string} connectionName + * @param {(data: Buffer) => void} callback + * @memberof Setup + */ + attachCallbackToRXConnection(connectionName: string, callback: (data: Buffer) => void) { + this.socket.on(connectionName, (data: Buffer) => { + callback(data); + }); + } +} diff --git a/src/cc/hardware/analogValve.ts b/src/cc/hardware/analogValve.ts new file mode 100644 index 00000000..ab266727 --- /dev/null +++ b/src/cc/hardware/analogValve.ts @@ -0,0 +1,103 @@ +import DispenserPump from "./dispenserpump"; + +export default class AnalogValve { + protected id: number; + protected _hwshield: number; + protected _precision: number; + protected _openValue: number; + protected _closeValue: number; + protected _currentState: ValveState; + protected _deviceindex: number; + dispenserPump: DispenserPump; + + /** + * Default constructor for the Pump object + * @param id + */ + constructor(id: number, deviceIndex: number, hwShield: number, precision: number, openValue: number, closeValue: number, dispenserPump: DispenserPump) { + console.log(id); + this.id = id; + this._deviceindex = deviceIndex; + this._hwshield = hwShield; + this._precision = precision; + this._openValue = openValue; + this._closeValue = closeValue; + this._currentState = ValveState.CLOSE; + this.dispenserPump = dispenserPump; + } + + /** + * Sets the id of the Pump object + * @param value + */ + set ID(value) { + this.id = value; + } + + /** + * Returns the id of the Pump object + * @returns {*} + */ + get ID() { + return this.id; + } + + /** + * Returns the HW shield value + * @returns {number} + */ + get hwShield() { + return this._hwshield; + } // set HW_Shield(value) { // this.__hwshield = value; // } + + /** + * Sets the HW Shield value + * @param value + */ set precision(value: number) { + this._precision = value; + } + + get precision() { + return this._precision; + } + + set close(value) { + this._closeValue = value; + } + + get close() { + return this._closeValue; + } + + set open(value) { + this._openValue = value; + } + + get open() { + return this._openValue; + } + + set currentState(value) { + this._currentState = value; + } + + get currentState() { + return this._currentState; + } + + set deviceIndex(value) { + this._deviceindex = value; + } + + get deviceIndex() { + return this._deviceindex; + } + + //find +} + +//This enum describes the possible states of the pump +export enum ValveState { + OPEN, + CLOSE +} diff --git a/src/cc/hardware/digitalValve.ts b/src/cc/hardware/digitalValve.ts new file mode 100644 index 00000000..dc18bb3a --- /dev/null +++ b/src/cc/hardware/digitalValve.ts @@ -0,0 +1,66 @@ +export default class DigitalValve { + protected id: number; + protected _hwshield: number; + protected _currentState: ValveState; + protected _deviceindex: number; + + /** + * Default constructor for the Pump object + * @param id + */ + constructor(id: number, deviceIndex: number, hwShield: number, precision: number) { + console.log(id); + this.id = id; + this._deviceindex = deviceIndex; + this._hwshield = hwShield; + this._currentState = ValveState.CLOSE; + } + + /** + * Sets the id of the Pump object + * @param value + */ + set ID(value) { + this.id = value; + } + + /** + * Returns the id of the Pump object + * @returns {*} + */ + get ID() { + return this.id; + } + + /** + * Returns the HW shield value + * @returns {number} + */ + get hwShield() { + return this._hwshield; + } // set HW_Shield(value) { // this.__hwshield = value; // } + + set currentState(value) { + this._currentState = value; + } + + get currentState() { + return this._currentState; + } + + set deviceIndex(value) { + this._deviceindex = value; + } + + get deviceIndex() { + return this._deviceindex; + } + + //find +} + +//This enum describes the possible states of the pump +export enum ValveState { + OPEN, + CLOSE +} diff --git a/src/cc/hardware/dispenserpump.ts b/src/cc/hardware/dispenserpump.ts new file mode 100644 index 00000000..cde32315 --- /dev/null +++ b/src/cc/hardware/dispenserpump.ts @@ -0,0 +1,89 @@ +export default class DispenserPump { + private __id: string; + private __hwshield: number = 1; + private __Precision: number = 0.001; + private __deviceindex: number = 0; + private __Current_State: number = 0; + private __Min: number = 0; + private __Max: number = 255; + + /** + * Default constructor for the Pump object + * @param id + */ + constructor(id: string) { + console.log(id); + this.__id = id; + } + + /** + * Sets the id of the Pump object + * @param value + */ + set ID(value) { + this.__id = value; + } + + /** + * Returns the id of the Pump object + * @returns {*} + */ + get ID() { + return this.__id; + } + + /** + * Returns the HW shield value + * @returns {number} + */ + get HW_Shield() { + return this.__hwshield; + } // + + /** + * Sets the HW Shield value + * @param value + */ set HW_Shield(value) { + this.__hwshield = value; + } + + set Precision(value) { + this.__Precision = value; + } + + get Precision() { + return this.__Precision; + } + + set Min(value) { + this.__Min = value; + } + + get Min() { + return this.__Min; + } + + set Max(value) { + this.__Max = value; + } + + get Max() { + return this.__Max; + } + + set Current_State(value) { + this.__Current_State = value; + } + + get Current_State() { + return this.__Current_State; + } + + set Device_Index(value) { + this.__deviceindex = value; + } + + get Device_Index() { + return this.__deviceindex; + } +} diff --git a/src/cc/hardware/microfluidic.ts b/src/cc/hardware/microfluidic.ts new file mode 100644 index 00000000..178593a5 --- /dev/null +++ b/src/cc/hardware/microfluidic.ts @@ -0,0 +1,88 @@ +import { Device } from "@/app"; +import Component from "@/app/core/component"; + +export default class Microfluidic { + private _device: Device; + private _name: string = ""; + private _valves: Array = []; + private _ports: Array = []; + + /** + * Default constructor for the microfluidic. Accepts the microfluidic json. + * @param json + */ + constructor(device: Device) { + console.log("new microfluidic instance"); + this._device = device; + this.name = device.name; + this.__createValves(); + } + + /** + * Returns the json of the device + * @returns {*} + * @constructor + */ + get device() { + return this._device; + } + + /** + * Sets the name + * @param value + */ + set name(value) { + this._name = value; + } + + /** + * Returns the name + * @returns {*} + */ + get name() { + return this._name; + } + + /** + * Returns the number valves on the device + * @returns {number} + */ + getValveCount() { + return this._valves; + } + + /** + * This method creates valves from JSON + * @private + */ + __createValves() { + //First check for the version !!!! + + let componentcollection = this._device.components; + let connectioncollection = this.device.connections; + let valves: Array = []; + let ports: Array = []; + //Step 1: Identify the valves + for (let i in componentcollection) { + if (componentcollection[i].mint == "VALVE" || componentcollection[i].mint == "VALVE3D") { + valves.push(); + } + } + //Step 2: Traverse the connections and components to see what PORT type components are connected to these components + + //Check sources + //Check sinks + + console.log("Warning ! We still need to implement full netlist traversal to identify valves and ports for the microfluidic"); + this._valves = valves; + this._ports = ports; + } + + /** + * Returns some kind of an object + * @returns [{},{},{}] + */ + getValves() { + return [{}, {}, {}]; + } +} diff --git a/src/cc/hardware/utils.ts b/src/cc/hardware/utils.ts new file mode 100644 index 00000000..647f00d0 --- /dev/null +++ b/src/cc/hardware/utils.ts @@ -0,0 +1,18 @@ +const THETA_MAX = Math.PI / 2; +const THETA_MIN = -THETA_MAX; +export const PWM_MAX = 255; +export const PWM_MIN = 0; + +export function deg2rad(degrees: number): number { + var pi = Math.PI; + return degrees * (pi / 180); +} + +export function PWM2rad(pwm: number): number { + var deg = ((pwm - PWM_MIN) * (THETA_MAX - THETA_MIN)) / (PWM_MAX - PWM_MIN) + THETA_MIN; + return deg * (Math.PI / 180); +} + +export function displacement(thetaX: number, r: number, b: number, d: number, a: number) { + return r * Math.cos(deg2rad(thetaX)) + Math.sqrt(Math.pow(b, 2) - Math.pow(r * Math.sin(deg2rad(thetaX)) + d, 2)); +} diff --git a/src/cc/legacy-neptune-control-code/dispenser.ts b/src/cc/legacy-neptune-control-code/dispenser.ts new file mode 100644 index 00000000..ee266532 --- /dev/null +++ b/src/cc/legacy-neptune-control-code/dispenser.ts @@ -0,0 +1,222 @@ +import { deg2rad, PWM2rad, displacement, PWM_MIN, PWM_MAX } from "../hardware/utils"; + +export function initializeSetup(r: number, b: number, d: number, a: number) { + let thetaXArray = []; // create array of angles to be populated in for loop + let displacementArray = []; // create array of displacement values + let increment = 1000; // Set resolution of system; from -90 to 270 degrees, 1000 total intervals is sufficient + let stepSize = 360 / increment; // Set step size for thetas to start at -90 and end at 270, a total of 360s + for (let i = 0; i <= increment; i++) { + // Iterate from 0 to 1000 by one + let thetaX_i = -90 + stepSize * i; // Calculate theta value from i + thetaXArray.push(thetaX_i); // Add current theta value to theta array + displacementArray.push(displacement(thetaX_i, r, b, d, a)); // Add current displacement value to array + } + + let displacement_min = Math.min.apply(null, displacementArray); // Calculate value by finding minimum of displacement array + let displacement_max = Math.max.apply(null, displacementArray); // Calculate value by finding maximum of displacement array + + let theta_min = thetaXArray[displacementArray.indexOf(displacement_max)]; // Calculate theta_min by pulling theta value from theta array at the index where the displacement max was found + let theta_max = thetaXArray[displacementArray.indexOf(displacement_min)]; // Calculate theta_max by pulling theta value from theta array at the index where the displacement min was found + let X_max = displacement(theta_min, r, b, d, a); // Calculate Xmax by plugging in theta_min to displacement function + let X_min = displacement(theta_max, r, b, d, a); // Calculate Xmin by plugging in theta_max to displacement function + let mL_min = 0; // Default value for mLmax, initalized by user in Assembly step. MUST be true + let mL_max = mL_min + (X_max - X_min) / a; // Calculate mLmax by S + let uL_min = mL_min * 1000; // convert incoming letiables from mL to uL + let uL_max = mL_max * 1000; + let mL_range = mL_max - mL_min; + let uL_range = mL_range * 1000; // convert incoming letiables from mL to uL + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // Create PWM_table and find uL_precision + let PWM_table = []; // used in this inner function + let PWM_dic = {}; // passed as output to easily hash + let uL_precision = 0; + + let not_found = true; + for (let i = PWM_MIN; i <= PWM_MAX; i++) { + // From PWM_min value to PWM_max value + PWM_table.push(i); // Add current PWM value to PWM_table + let mL_temp = mL_max - (r * Math.cos(PWM2rad(i)) + Math.sqrt(Math.pow(b, 2) - Math.pow(r * Math.sin(PWM2rad(i)) + d, 2)) - X_min) / a; // Calculate mL value with formula of motion + let uL_temp = Math.round(mL_temp * 100000) / 100; // convert to uL and round to first decimal place + PWM_table.push(uL_temp); // Add uL value to PWM_table + + PWM_dic[i] = uL_temp; + + // Find uL_precision by finding the max uL difference between PWM values. + let uL_diff = PWM_table[PWM_table.length - 1] - PWM_table[PWM_table.length - 3]; + let uL_diff_prev = PWM_table[PWM_table.length - 3] - PWM_table[PWM_table.length - 5]; + if (uL_diff < uL_diff_prev && not_found) { + uL_precision = Math.round(uL_diff_prev * 100) / 100; + not_found = false; + } + } + + // create uL_table + let uL_table = []; + let uL_dic = {}; + for (let uLValue = uL_min; uLValue < uL_range + uL_min + uL_precision; uLValue = uLValue + uL_precision) { + // From uL_min (a given) to uL_min+uL_range! uL_precision added on to allow last uL value to be iterated through. Increase by steps of uL_precision + // rename i (which is current uL value) + let uL_current = uLValue; // rename i to something more readable + uL_current = Math.round(uL_current * 100) / 100; // round to 2 decimal places + + // Add uL to table + uL_table.push(uL_current); // Add the current uL value to uL_table + + // Find PWM values + // Add First PWM value, matched easily + if (uLValue == uL_min) { + // We know the first value, which can't be found with linear interpolation + uL_table.push(PWM_table[0]); + uL_dic[uL_current] = PWM_table[0]; + continue; + } + // Linear interpolation to find other PWM values + // Skip to 2nd value as we already logged the first + for (let j = 3; j <= PWM_table.length; j = j + 2) { + // Iterate through uL values in PWM_table (start at 2nd uL value, index 3. Go length of PWM table. Increase by 2 to avoid looking at PWM values) + if (PWM_table[j] >= uL_current && j % 2 > 0) { + // If uL value in PWM_table is greater than or equal to our current uL value, find PWM inbetween PWMs in PWM_table + let PWM_between = PWM_table[j - 3] + (uL_current - PWM_table[j - 2]) * ((PWM_table[j - 1] - PWM_table[j - 3]) / (PWM_table[j] - PWM_table[j - 2])); // Find PWM value via linear interpolation + let PWM_between = Math.round(PWM_between * 100) / 100; // Round calculated PWM value to 2 decimal places + uL_table.push(PWM_between); // Add calculated PWM value to table + + uL_dic[uL_current] = PWM_between; + break; + } + } + + if (uLValue >= uL_range + uL_min) { + uL_table.push(PWM_MAX); // Add last PWM value, not calculated above with linear interpolation + uL_dic[uL_current] = PWM_MAX; + } + } + + return { + theta_min: theta_min, + theta_max: theta_max, + X_min: X_min, + X_max: X_max, + uL_min: uL_min, + uL_max: uL_max, + PWM_table: PWM_table, + PWM_dic: PWM_dic, + uL_table: uL_table, + uL_dic: uL_dic, + uL_precision: uL_precision, + r: r, + b: b, + d: d, + a: a + }; +} + +export function even_uL_steps(uL_table, PWM_table, uL_precision, current_uL, goal_uL, time_sec) { + // Get uL_to_dispense + let uL_to_dispense = Math.abs(goal_uL - current_uL); + + // Get Total number of steps + let num_steps = uL_to_dispense / uL_precision; + + // Get number of steps per second and number of seconds per step + let steps_per_second = num_steps / time_sec; // send to user, not used in program + let seconds_per_step = time_sec / num_steps; // needed for delay between step + + // Find current place and goal place in uL_table + let f_found = false; // set to true when the first_uL_index is found to avoid this if statment through rest of loop + let going_up_uL = goal_uL - current_uL > 0; + let first_uL_index = 0; + let goal_uL_index = 0; + + if (going_up_uL) { + // if increasing in uL from current_uL to goal_uL + for (let i = 0; i < uL_table.length; i = i + 2) { + // From 0 through length of uL_table in steps of 2, hitting just uL values + if (uL_table[i] >= current_uL && f_found == false) { + // If uL value in uL_table is greater than or equal to our current_uL (and the first_uL_index has not been found yet) + first_uL_index = i; // log index of where the uL_currently is in uL_table (logs the uL value directly above or equal to it) + f_found = true; // indicate we found the first uL_index + } + if (uL_table[i] >= goal_uL) { + // If uL value in uL_table is greater than or equal to our goal_uL + goal_uL_index = i; // log index of where the uL_goal is in the uL_table (logs the uL value directly above or equal to it) + break; // Stop for loop, we have the info we need + } + } + } else { + // if decreasing in uL from current_uL to goal_uL + for (let i = uL_table.length - 2; i >= 0; i = i - 2) { + // From length of uL_table through 0 in steps of 2, hitting just uL values + if (uL_table[i] <= current_uL && f_found == false) { + // If uL value in uL_table is less than or equal to our current_uL (and the first_uL_index has not been found yet) + first_uL_index = i; // log index of where the uL_currently is in uL_table (logs the uL value directly above or equal to it) + f_found = true; // indicate we found the first uL_index + } + if (uL_table[i] <= goal_uL) { + // If uL value in uL_table is greater than or equal to our goal_uL + goal_uL_index = i; // log index of where the uL_goal is in the uL_table (logs the uL value directly above or equal to it) + break; // Stop for loop, we have the info we need + } + } + } + + //////////////////////////////////////////////////////////// + + // Iterate through uL_table from next uL_index and go specified number of steps + // MUST DELAY each console.log by the letiable 'seconds_per_step' + let PWM_values = []; // Used to keep track of PWM steps to move, for record keeping and debugging + if (going_up_uL) { + // if increasing in uL from current_uL to goal_uL + for (let i = first_uL_index; i <= goal_uL_index; i = i + 2) { + // From our current uL index in uL_table to goal index in uL_table + // if last step + if (i == goal_uL_index) { + // If last step + if (Math.abs(uL_table[i] - goal_uL) < Math.abs(uL_table[i - 2] - goal_uL)) { + // if this uL value from uL_table is closer to goal_uL than the previous one + let end_PWM = Math.round(uL_table[i + 1]); // used for PWM tracking + PWM_values.push(Math.round(uL_table[i + 1])); // Add rounded PWM value to PWM_values list for record keeping and debugging + //console.log(end_PWM); //////////// Send pump number and rounded PWM value to arduino + } else { + let end_PWM = Math.round(uL_table[i - 1]); // used for PWM tracking + } + } + // If normal step + else { + // Normal indexes + //console.log(Math.round(uL_table[i+1])); /////////// Send pump number and rounded PWM value to arduino + PWM_values.push(Math.round(uL_table[i + 1])); // Add rounded PWM value to PWM_values list for record keeping and debugging + } + } + } else { + // if decreasing in uL from current_uL to goal_uL + for (let i = first_uL_index; i >= goal_uL_index; i = i - 2) { + // From our current uL index in uL_table to goal index in uL_table + // if last step + if (i == goal_uL_index) { + // If last step + if (Math.abs(uL_table[i] - goal_uL) < Math.abs(uL_table[i + 2] - goal_uL)) { + // if this uL value from uL_table is closer to goal_uL than the previous one + let end_PWM = Math.round(uL_table[i + 1]); // used for PWM tracking + PWM_values.push(Math.round(uL_table[i + 1])); // Add rounded PWM value to PWM_values list for record keeping and debugging + console.log(end_PWM); //////////// Send pump number and rounded PWM value to arduino + } else { + let end_PWM = Math.round(uL_table[i + 3]); // used for PWM tracking + } + } + // If normal step + else { + // Normal indexes + console.log(Math.round(uL_table[i + 1])); /////////// Send pump number and rounded PWM value to arduino + PWM_values.push(Math.round(uL_table[i + 1])); // Add rounded PWM value to PWM_values list for record keeping and debugging + } + } + } + + return { + PWM_values: PWM_values, + seconds_per_step: seconds_per_step, + steps_per_second: steps_per_second + }; +}