Commit 52d686fe authored by Jake Read's avatar Jake Read

mid teardown

parent d3f60b1c
......@@ -16,7 +16,12 @@ This project serves the developement environment / api we use to write and repre
## For MW
- fundament
- the basic unit is a .js file having inputs, outputs, and state objects
- we can compose programs of these modules, being collections of them with connections
- we can represent these programs heirarchically, or in a flat manner ... heirarchy just requires an extra wrap, we do it later
- load / save programs (req. prgmem representation)
- rewrite ui & transfers to use this rep.
- UI
- title bar descr. how to operate
- 'L' for load, 'M' for new module, type for module search ?
......@@ -39,7 +44,7 @@ Also
Modules-that-represent-remote-computing also have
Ports
that connect to
that connect to a
Link
To assemble a representation of these, we want to have a kind of 'netlist' that, for convenience, we'll treat like a JSON object. We want heirarchy, so consider the representation having 'top-level' outputs / inputs / state as well ?
......
// event system to include type-checking etc
function Input(type, fn){
var input = {
accepts: type,
fn: fn
}
return input
}
function Output(type){
var output = {
emits: type
}
output.calls = new Array()
output.attach = function(input){
this.calls.push(input)
}
output.isLinked = function(input){
// return true if already hooked up
if(this.calls.includes(input)){
return true
} else {
return false
}
}
output.remove = function(input){
if(!this.isLinked(input)){
console.log('attempt to rm input that is not attached')
return false
} else {
this.calls.splice(this.calls.indexOf(input), 1)
}
}
output.emit = function(data){
if(this.calls.length == 0){
console.log('no inputs bound to this output')
} else {
for(index in this.calls){
this.calls[index].fn(JSON.parse(JSON.stringify(data)))
}
}
}
return output
}
function State(){
var state = {
}
state.emitters = {}
state.onChange = function(item, fn){
this.emitters[item] = fn
//console.log('ADD STATE CHANGE EMITTER', this.emitters)
}
state.emitChange = function(item){
//console.log('EMIT CHANGE FOR', item)
if(this.emitters[item] != null){
this.emitters[item]()
}
}
return state
}
// within state ... or not ?
function Button(label){
var button = {
isButton: true,
isPressed: false,
label: label
}
return button
}
// now we're definitely writing a UI class
function MultiLine(label, rows){
var ml = {
isMultiLine: true,
label: label,
rows: rows,
value: ' - '
}
return ml
}
module.exports = {
Input: Input,
Output: Output,
State: State,
Button: Button,
MultiLine: MultiLine
}
/*
prjRep = {
connectivity: [
{id: 1, thisOutput: ['2:thisInput', '4:thatInput'], thatOutput: [etc]},
{id: 2, thatOutput: ['2:thisInput', '4:thatInput']}
],
states: [
{id: 1, state: {
//
}}
},
positions: [
{id: 1, posX: 12, posY: 15}
]
}
*/
/*
more like
modules = [
Class {
state: object,
inputs: object,
outputs: object, // walk this for connectivity - how to set that up?
}
]
reps = [
object {
posX: num,
posY: num
}
]
*/
\ No newline at end of file
// event system to include type-checking etc
// dataflow types for javascript objects ...
function Input(type, fn) {
var input = {
accepts: type,
fn: fn
}
return input
}
function Output(type) {
var output = {
emits: type
}
output.calls = new Array()
output.attach = function(input) {
this.calls.push(input)
}
output.isLinked = function(input) {
// return true if already hooked up
if (this.calls.includes(input)) {
return true
} else {
return false
}
}
output.remove = function(input) {
if (!this.isLinked(input)) {
console.log('attempt to rm input that is not attached')
return false
} else {
this.calls.splice(this.calls.indexOf(input), 1)
}
}
output.emit = function(data) {
if (this.calls.length == 0) {
console.log('no inputs bound to this output')
} else {
for (index in this.calls) {
this.calls[index].fn(JSON.parse(JSON.stringify(data)))
}
}
}
return output
}
function State() {
var state = {}
state.emitters = {}
state.onChange = function(item, fn) {
this.emitters[item] = fn
//console.log('ADD STATE CHANGE EMITTER', this.emitters)
}
state.emitChange = function(item) {
//console.log('EMIT CHANGE FOR', item)
if (this.emitters[item] != null) {
this.emitters[item]()
}
}
return state
}
// within state ... or not ?
function Button(label) {
var button = {
isButton: true,
isPressed: false,
label: label
}
return button
}
// now we're definitely writing a UI class
function MultiLine(label, rows) {
var ml = {
isMultiLine: true,
label: label,
rows: rows,
value: ' - '
}
return ml
}
module.exports = {
Input: Input,
Output: Output,
State: State,
Button: Button,
MultiLine: MultiLine
}
\ No newline at end of file
//
// server.js
// nnc.js
//
//
// Jake Read at the Center for Bits and Atoms
// (c) Massachusetts Institute of Technology 2018
......@@ -134,129 +135,216 @@ PROGRAM AS API
------------------------------------------------------
*/
var modules = new Array()
// nodes are objects having inputs, outputs, and state
// nodes can be made up of other nodes
// a node originates somewhere,
// a program is a list of modules,
// modules have internal lists of modules that they call
// is it a JSON, or a txt .js file ? loadProgram and loadModule ...
// it wants to be an object that gets 'read' in ...
var prgmem = {
// we want to describe all of the nodes first,
// then we can add connections once we have hooks
// to each of them
modules: {
gate: {
path: './src/util/gate.js',
conn: {
}
ui:{
pos: [100, 100]
}
},
delay: {
path: './src/util/delay.js',
state: {
ms: 500
}
},
log: {
path: './src/util.log.js'
}
var program = {
description: {
name: 'tstprgmem',
},
connections: ['gate.outputs.out', 'delay.inputs.thru']
modules: new Array()
}
function loadPrgmem(desc) {
console.log(desc)
}
// add things
loadPrgmem(prgmem)
var gate = addModuleToProgram(program, './src/util/gate.js')
var delay = addModuleToProgram(program, './src/util/delay.js')
var log = addModuleToProgram(program, './src/util/log.js')
/*
------------------------------------------------------
LOST FUNCTION
------------------------------------------------------
*/
// hookup as usual
gate.outputs.out.attach(delay.inputs.thru)
delay.outputs.out.attach(log.inputs.thru)
gate.outputs.out.attach(log.inputs.thru)
saveProgram(program, 'save/onesave.json')
program = null
delete gate
delete delay
delete log
console.log('PROGRAM', program)
var prgmem = openProgram('save/onesave.json')
saveProgram(prgmem, 'save/twosave.json')
function setUiPos(module, left, top) {
if (module.ui == null) {
module.ui = {}
}
module.ui.left = left
module.ui.top = top
}
/*
------------------------------------------------------
PROGRAM WRITING
PROGRAM ASSEMBLY
------------------------------------------------------
*/
function addModule(path) {
// get and add to server system
function addModuleToProgram(program, path) {
// program is an object seeking heirarchy, has program.modules
if (fs.existsSync(path)) {
var src = require(path) // get and return
var mod = new src() // make a new one
modules.push(mod) // add to the system
// assign and id and remember from whence it came
mod.id = modules.length - 1
mod.path = path
// we need to add some top-level info to the inputs so that we can draw them
for (item in mod.inputs) {
mod.inputs[item].parentId = mod.id
mod.inputs[item].key = item
var src = require(path)
var mod = new src()
// add to list
program.modules.push(mod)
// make unique name
mod.description.id = mod.description.name + '-' + program.modules.length
mod.description.path = path
// input need references for later hookup
for (key in mod.inputs) {
mod.inputs[key].parentId = mod.description.id
mod.inputs[key].key = key
}
for (item in mod.state) {
if (item == 'onChange' | item == 'emitChange' | item == 'emitters') {
// state updating, begs for update
for (key in mod.state) {
if (key == 'onChange' | key == 'emitChange' | key == 'emitters') {
//console.log('rolling past change fn')
} else {
mod.state['_' + item] = mod.state[item]
mod.state[item] = {}
writeStateObject(mod, item)
mod.state['_' + key] = mod.state[key]
mod.state[key] = {}
writeStateObject(mod, key)
}
}
console.log('ADDING MODULE', mod.description.name)
if (program.description.id == null) {
if (program.description.name == null) {
if (program.description == null) {
program.description = {}
}
program.description.name = 'unnamed program'
}
program.description.id = program.description.name + '-' + 0
}
// now roll and return representable object
// first to UI
putRep(mod)
// also to fn call, in case writing program ?
console.log('ADDING', mod.description.id, 'to', program.description.id)
// also return it so that we can write programs without the UI
return mod
} else {
console.log('ERR no module found at', path)
console.log('ERR no module found at ', path)
}
}
function writeStateObject(mod, item) {
Object.defineProperty(mod.state, item, {
function writeStateObject(mod, key) {
Object.defineProperty(mod.state, key, {
set: function(x) {
// update internal value
this['_' + item] = x
//console.log('SET', item, this['_' + item])
this['_' + key] = x
//console.log('SET', key, this['_' + key])
// push to internal state change handler
this.emitChange(item)
this.emitChange(key)
// update server (or in some cases, a confirmation)
putState(mod)
}
})
Object.defineProperty(mod.state, item, {
Object.defineProperty(mod.state, key, {
get: function() {
//console.log('GET', item, this['_' + item])
return this['_' + item]
//console.log('GET', key, this['_' + key])
return this['_' + key]
}
})
}
function makeRepFromModule(mdl) {
var rep = {
description: {
id: mdl.description.id,
name: mdl.description.name,
alt: mdl.description.alt,
path: mdl.description.path
}
}
// TODO: making rep. of input / output should be a f'n of that object ...
// input, outputs, state objs should be known /sysobjects
// everything else is free play
rep.inputs = {}
for (key in mdl.inputs) {
rep.inputs[key] = {}
rep.inputs[key].accepts = mdl.inputs[key].accepts
}
rep.outputs = {}
for (key in mdl.outputs) {
rep.outputs[key] = {}
rep.outputs[key].emits = mdl.outputs[key].emits
rep.outputs[key].calls = new Array()
mdl.outputs[key].calls.forEach(function(inp) {
var input = {
parentId: inp.parentId,
key: inp.key
}
rep.outputs[key].calls.push(input)
})
}
rep.state = {}
for(key in mdl.state){
if(isStateKey(key)){
rep.state[key] = mdl.state[key]
}
}
return rep
}
function saveProgram(prgmem, path) {
// ok, and we're interested in just copying the relevant things ...
var svprgmem = {
description: {
name: prgmem.description.name
},
modules: new Array()
}
var mdls = prgmem.modules
mdls.forEach(function(mdl) {
// basically, going to run a diff on this
console.log('mdl unit', mdl)
// and
var og = makeRepFromModule(mdl)
console.log('rep uniiiit', og)
svprgmem.modules.push(og)
})
fs.writeFileSync(path, JSON.stringify(svprgmem, null, 2), 'utf8')
}
function openProgram(path){
var program = {}
// get the .json file as an object
var prgRep = JSON.parse(fs.readFileSync(path, 'utf8'))
console.log('OPENING THIS REP', prgRep)
program.description = {
name: prgRep.description.name,
id: prgRep.description.name + 1,
path: path
}
program.modules = new Array()
prgRep.modules.forEach(function(rep){
addModuleToProgram(program, rep.description.path)
})
// once modules exist, link inputs / outputs / copy state ?
return program
}
/*
PROGRAM UPDATING
PROGRAM REPRESENT
*/
//console.log('modules at prgmem start', modules)
function putReps() {
var reps = new Array()
......@@ -341,6 +429,15 @@ function changeUi(data) {
console.log('CHANGE UI', mod.id, mod.ui)
}
function setUiPos(module, left, top) {
if (module.ui == null) {
module.ui = {}
}
module.ui.left = left
module.ui.top = top
}
function isStateKey(key) {
if (key.indexOf('_') == 0 || key == 'emitters' || key == 'onChange' || key == 'emitChange') {
return false
......
var gate = addModule('./src/util/gate.js')
var delay = addModule('./src/util/delay.js')
var log = addModule('./src/util/log.js')
......
[{"description":{"name":"gate","alt":"in ... out"},"state":{"emitters":{},"toggle":{"isButton":true,"isPressed":false,"label":"Open / Close"},"message":"closed","_toggle":{"isButton":true,"isPressed":false,"label":"Open / Close"},"_message":"closed"},"isOpen":false,"inputs":{"thru":{"accepts":"any","parentId":0,"key":"thru"}},"outputs":{"out":{"emits":"any","calls":[{"accepts":"any","parentId":1,"key":"thru"},{"accepts":"any","parentId":2,"key":"thru"},{"accepts":"any","parentId":3,"key":"reset"},{"accepts":"event","parentId":4,"key":"trigger"},{"accepts":"event","parentId":8,"key":"rmtrig"},{"accepts":"event","parentId":9,"key":"rmtrig"}]}},"id":0,"path":"./src/util/gate.js","ui":{"left":160,"top":130}},{"description":{"name":"delay!","alt":"in ... out"},"state":{"emitters":{},"ms":100,"_ms":100},"inputs":{"thru":{"accepts":"any","parentId":1,"key":"thru"}},"outputs":{"out":{"emits":"any","calls":[{"accepts":"any","parentId":3,"key":"A"}]}},"id":1,"path":"./src/util/delay.js","ui":{"left":160,"top":260}},{"description":{"name":"logger!","alt":"in ... out to console"},"state":{"emitters":{},"prefix":"LOGGER:","message":"---","_prefix":"LOGGER:","_message":"---"},"inputs":{"thru":{"accepts":"any","parentId":2,"key":"thru"}},"id":2,"path":"./src/util/log.js","ui":{"left":160,"top":400}},{"description":{"name":"andflow","alt":"in ... out"},"state":{"emitters":{},"toggle":{"isButton":true,"isPressed":false,"label":"Reset"},"A":0,"B":0,"_toggle":{"isButton":true,"isPressed":false,"label":"Reset"},"_A":0,"_B":0},"inputs":{"reset":{"accepts":"any","parentId":3,"key":"reset"},"A":{"accepts":"any","parentId":3,"key":"A"},"B":{"accepts":"any","parentId":3,"key":"B"}},"outputs":{"out":{"emits":"any","calls":[{"accepts":"any","parentId":0,"key":"thru"}]}},"id":3,"path":"./src/flowcontrol/and.js","ui":{"left":700,"top":300}},{"description":{"name":"CAMERA Request","alt":"webcam","isHardware":true},"state":{"emitters":{},"button":{"isButton":true,"isPressed":false,"label":"REQUEST IMAGE"},"counter":0,"_button":{"isButton":true,"isPressed":false,"label":"REQUEST IMAGE"},"_counter":0},"inputs":{"trigger":{"accepts":"event","parentId":4,"key":"trigger"}},"outputs":{"image":{"emits":"image","calls":[]},"callback":{"emits":"event","calls":[{"accepts":"any","parentId":3,"key":"B"}]}},"id":4,"path":"./src/hardware/webcam.js","ui":{"left":500,"top":700}},{"description":{"name":"Breadboard ADC Request","alt":"bbadc","isHardware":true},"state":{"emitters":{},"button":{"isButton":true,"isPressed":false,"label":"REQUEST CONVERSION"},"adcVal":0,"_button":{"isButton":true,"isPressed":false,"label":"REQUEST CONVERSION"},"_adcVal":0},"inputs":{"packet":{"accepts":"headless packet","parentId":5,"key":"packet"},"request":{"accepts":"event","parentId":5,"key":"request"}},"outputs":{"packet":{"emits":"number","calls":[{"accepts":"headless packet","parentId":7,"key":"B"}]}},"id":5,"path":"./src/hardware/adc.js","ui":{"left":1629,"top":180}},{"description":{"name":"Test Packet","alt":"net state","isHardware":true},"state":{"emitters":{},"button":{"isButton":true,"isPressed":false,"label":"TEST"},"message":"no test started","_button":{"isButton":true,"isPressed":false,"label":"TEST"},"_message":"no test started"},"inputs":{"packet":{"accepts":"headless packet","parentId":6,"key":"packet"},"trigger":{"accepts":"event","parentId":6,"key":"trigger"}},"outputs":{"packet":{"emits":"number","calls":[{"accepts":"headless packet","parentId":7,"key":"B"}]}},"id":6,"path":"./src/hardware/test.js","ui":{"left":1060,"top":918}},{"description":{"name":"ATK Hardware Bridge","alt":"talks over serialport","isHardware":true},"state":{"emitters":{},"rA":"0,0","rB":"0,1","rC":"0,2","rD":"0,3","rE":"0,4","rF":"0,5","rG":"0","findPort":{"isButton":true,"isPressed":false,"label":"click to find atk port"},"portName":"---","connect":{"isButton":true,"isPressed":false,"label":"click to connect"},"portStatus":"closed","terminal":"address | key:values","sendRawPacket":{"isButton":true,"isPressed":false,"label":"sendRaw"},"_rA":"0,0","_rB":"0,1","_rC":"0,2","_rD":"0,3","_rE":"0,4","_rF":"0,5","_rG":"0","_findPort":{"isButton":true,"isPressed":false,"label":"click to find atk port"},"_portName":"---","_connect":{"isButton":true,"isPressed":false,"label":"click to connect"},"_portStatus":"closed","_terminal":"address | key:values","_sendRawPacket":{"isButton":true,"isPressed":false,"label":"sendRaw"}},"inputs":{"A":{"accepts":"headless packet","parentId":7,"key":"A"},"B":{"accepts":"headless packet","parentId":7,"key":"B"},"C":{"accepts":"headless packet","parentId":7,"key":"C"},"D":{"accepts":"headless packet","parentId":7,"key":"D"},"E":{"accepts":"headless packet","parentId":7,"key":"E"},"F":{"accepts":"headless packet","parentId":7,"key":"F"},"G":{"accepts":"headless packet","parentId":7,"key":"G"}},"outputs":{"A":{"emits":"headless packet","calls":[{"accepts":"headless packet","parentId":8,"key":"packet"},{"accepts":"headless packet","parentId":10,"key":"packet"}]},"B":{"emits":"headless packet","calls":[{"accepts":"headless packet","parentId":5,"key":"packet"},{"accepts":"headless packet","parentId":6,"key":"packet"}]},"C":{"emits":"headless packet","calls":[]},"D":{"emits":"headless packet","calls":[]},"E":{"emits":"headless packet","calls":[]},"F":{"emits":"headless packet","calls":[{"accepts":"headless packet","parentId":9,"key":"packet"},{"accepts":"headless packet","parentId":11,"key":"packet"}]},"G":{"emits":"headless packet","calls":[]}},"pairs":{"A":{"route":"0,0","output":{"emits":"headless packet","calls":[{"accepts":"headless packet","parentId":8,"key":"packet"},{"accepts":"headless packet","parentId":10,"key":"packet"}]}},"B":{"route":"0,1","output":{"emits":"headless packet","calls":[{"accepts":"headless packet","parentId":5,"key":"packet"},{"accepts":"headless packet","parentId":6,"key":"packet"}]}},"C":{"route":"0,2","output":{"emits":"headless packet","calls":[]}},"D":{"route":"0,3","output":{"emits":"headless packet","calls":[]}},"E":{"route":"0,4","output":{"emits":"headless packet","calls":[]}}},"id":7,"path":"./src/hardware/bridge.js","ui":{"left":1629,"top":890}},{"description":{"name":"ATK Network Stepper Driver","alt":"software representation of stepper","isHardware":true},"state":{"emitters":{},"axis":"Y","spu":75,"rawMove":0.25,"makeMove":{"isButton":true,"isPressed":false,"label":"test move"},"lead":0,"position":0,"_axis":"Y","_spu":75,"_rawMove":0.25,"_makeMove":{"isButton":true,"isPressed":false,"label":"test move"},"_lead":0,"_position":0},"inputs":{"move":{"accepts":"move instruction","parentId":8,"key":"move"},"packet":{"accepts":"headless packet","parentId":8,"key":"packet"},"rmtrig":{"accepts":"event","parentId":8,"key":"rmtrig"}},"outputs":{"ack":{"emits":"move acknowledgement","calls":[]},"q":{"emits":"number","calls":[]},"packet":{"emits":"headless packet","calls":[{"accepts":"headless packet","parentId":7,"key":"A"}]}},"id":8,"path":"./src/hardware/stepper.js","ui":{"left":1629,"top":372}},{"description":{"name":"ATK Network Stepper Driver","alt":"software representation of stepper","isHardware":true},"state":{"emitters":{},"axis":"Y","spu":-75,"rawMove":0.25,"makeMove":{"isButton":true,"isPressed":false,"label":"test move"},"lead":0,"position":0,"_axis":"Y","_spu":-75,"_rawMove":0.25,"_makeMove":{"isButton":true,"isPressed":false,"label":"test move"},"_lead":0,"_position":0},"inputs":{"move":{"accepts":"move instruction","parentId":9,"key":"move"},"packet":{"accepts":"headless packet","parentId":9,"key":"packet"},"rmtrig":{"accepts":"event","parentId":9,"key":"rmtrig"}},"outputs":{"ack":{"emits":"move acknowledgement","calls":[]},"q":{"emits":"number","calls":[]},"packet":{"emits":"headless packet","calls":[{"accepts":"headless packet","parentId":7,"key":"F"}]}},"id":9,"path":"./src/hardware/stepper.js","ui":{"left":1629,"top":626}},{"description":{"name":"Test Packet","alt":"net state","isHardware":true},"state":{"emitters":{},"button":{"isButton":true,"isPressed":false,"label":"TEST"},"message":"no test started","_button":{"isButton":true,"isPressed":false,"label":"TEST"},"_message":"no test started"},"inputs":{"packet":{"accepts":"headless packet","parentId":10,"key":"packet"},"trigger":{"accepts":"event","parentId":10,"key":"trigger"}},"outputs":{"packet":{"emits":"number","calls":[{"accepts":"headless packet","parentId":7,"key":"A"}]}},"id":10,"path":"./src/hardware/test.js","ui":{"left":1060,"top":1050}},{"description":{"name":"Test Packet","alt":"net state","isHardware":true},"state":{"emitters":{},"button":{"isButton":true,"isPressed":false,"label":"TEST"},"message":"no test started","_button":{"isButton":true,"isPressed":false,"label":"TEST"},"_message":"no test started"},"inputs":{"packet":{"accepts":"headless packet","parentId":11,"key":"packet"},"trigger":{"accepts":"event","parentId":11,"key":"trigger"}},"outputs":{"packet":{"emits":"number","calls":[{"accepts":"headless packet","parentId":7,"key":"F"}]}},"id":11,"path":"./src/hardware/test.js","ui":{"left":1060,"top":1170}}]
\ No newline at end of file
{
"description":
{
"name": "tstprgmem"
},
"modules": [
{
"description":
{
"id": "gate-1",
"name": "gate",
"alt": "in ... out",
"path": "./src/util/gate.js"
},
"inputs":
{
"thru":
{
"accepts": "any"
}
},
"outputs":
{
"out":
{
"emits": "any",
"calls": [
{
"parentId": "delay-2",
"key": "thru"
},
{
"parentId": "logger-3",
"key": "thru"
}]
}
},
"state":
{
"toggle":
{
"isButton": true,
"isPressed": false,
"label": "Open / Close"
},
"message": "closed"
}
},
{
"description":
{
"id": "delay-2",
"name": "delay",
"alt": "in ... out",
"path": "./src/util/delay.js"
},
"inputs":
{
"thru":
{
"accepts": "any"
}
},
"outputs":
{
"out":
{
"emits": "any",
"calls": [
{
"parentId": "logger-3",
"key": "thru"
}]
}
},
"state":
{
"ms": 100
}
},
{
"description":
{
"id": "logger-3",
"name": "logger",
"alt": "in ... out to console",
"path": "./src/util/log.js"
},
"inputs":
{
"thru":
{
"accepts": "any"
}
},
"outputs":
{},
"state":
{
"prefix": "LOGGER:",
"message": "---"
}
}]
}
\ No newline at end of file
{
"description":
{
"name": "tstprgmem"
},