Skip to content
Snippets Groups Projects
server.js 12.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
    
    SYSTEM REQUIRES
    
    */
    
    
    Jake Read's avatar
    Jake Read committed
    // file system to load / look at our available modules / edit them 
    const fs = require('fs')
    
    Jake Read's avatar
    Jake Read committed
    // express serves files, http makes the connection
    const app = require('express')()
    const http = require('http').Server(app)
    
    Jake Read's avatar
    Jake Read committed
    // websocket does websocket
    const WebSocket = require('ws')
    
    /*
    
    SERVER AND WS SETUP
    
    */
    
    
    Jake Read's avatar
    Jake Read committed
    // serving this handful of static files
    app.get('/', (req, res) => {
        console.log('client req /')
        res.sendFile(__dirname + '/client/index.html')
    })
    
    app.get('/:file', (req, res) => {
        console.log('client req', req.params.file)
        res.sendFile(__dirname + '/client/' + req.params.file)
    })
    
    
    Jake Read's avatar
    Jake Read committed
    // and listening for requests here
    const wss = new WebSocket.Server({ port: 8081 })
    
    wss.on('connection', (ws) => {
        sckt = ws
    
    Jake Read's avatar
    Jake Read committed
        socketSend('console', 'hello client')
    
    Jake Read's avatar
    Jake Read committed
        // send current config as list of all modules
        putReps()
    
    Jake Read's avatar
    Jake Read committed
        console.log('socket open on 8081')
        ws.on('message', (evt) => {
            socketRecv(evt)
        })
    })
    
    // through this window
    http.listen(8080, () => {
        console.log('listening on 8080 for static files')
    })
    
    
    Jake Read's avatar
    Jake Read committed
    // server globals 
    
    
    Jake Read's avatar
    Jake Read committed
    function socketSend(type, data) {
    
        if (sckt) {
            var msg = {
                type: type,
                data: data
            }
    
            //console.log('SEND', msg)
    
            sckt.send(JSON.stringify(msg))
    
    Jake Read's avatar
    Jake Read committed
    function socketRecv(evt) {
    
    Jake Read's avatar
    Jake Read committed
        var recv = JSON.parse(evt)
        var type = recv.type
        var data = recv.data
    
        //console.log('RECV', recv)
    
    Jake Read's avatar
    Jake Read committed
        // bang thru
    
    Jake Read's avatar
    Jake Read committed
        switch (type) {
    
    Jake Read's avatar
    Jake Read committed
            case 'console':
    
                console.log('RECV CONSOLE:', data)
    
    Jake Read's avatar
    Jake Read committed
                break
            case 'get menu':
                var tree = buildMenu()
    
    Jake Read's avatar
    Jake Read committed
                socketSend('put menu', tree)
                break
            case 'add module':
                addModule(data)
                break
    
    Jake Read's avatar
    Jake Read committed
            case 'put state':
                changeState(data)
    
    Jake Read's avatar
    Jake Read committed
                break
            case 'put link':
    
    Jake Read's avatar
    Jake Read committed
                // id:output > id:input
                break
    
            case 'put ui':
                changeUi(data)
                break
    
    Jake Read's avatar
    Jake Read committed
            case 'rm link':
                // id:output > id:input
    
    Jake Read's avatar
    Jake Read committed
                break
            default:
    
                console.log('ERR server recv with non recognized type', recv)
    
    Jake Read's avatar
    Jake Read committed
                break
    
    Jake Read's avatar
    Jake Read committed
    ------------------------------------------------------
    
    PROGRAM AS API
    
    Jake Read's avatar
    Jake Read committed
    ------------------------------------------------------
    
    */
    
    var modules = new Array()
    
    // wrap require() up, appending path used to object, and giving ID
    // use the same to load from browser 
    
    var term = addModule('./src/ui/terminal.js')
    
    Jake Read's avatar
    Jake Read committed
    var multiline = addModule('./src/ui/multiline.js')
    var button = addModule('./src/ui/button.js')
    var numOut = addModule('./src/ui/number.js')
    
    Jake Read's avatar
    Jake Read committed
    var delay = addModule('./src/util/delay.js')    
    
    Jake Read's avatar
    Jake Read committed
    
    
    var gcode = addModule('./src/parsing/gcode.js')
    
    var planner = addModule('./src/motion/planner.js')
    
    Jake Read's avatar
    Jake Read committed
    
    
    var xm = addModule('./src/hardware/stepper.js')
    var yma = addModule('./src/hardware/stepper.js')
    var ymb = addModule('./src/hardware/stepper.js')
    var zm = addModule('./src/hardware/stepper.js')
    
    Jake Read's avatar
    Jake Read committed
    
    
    var t1 = addModule('./src/hardware/test.js')
    var t2 = addModule('./src/hardware/test.js')
    var t3 = addModule('./src/hardware/test.js')
    var t4 = addModule('./src/hardware/test.js')
    var t5 = addModule('./src/hardware/test.js')
    
    
    Jake Read's avatar
    Jake Read committed
    var bridge = addModule('./src/hardware/bridge.js')
    
    Jake Read's avatar
    Jake Read committed
    // attaching an output to an input
    
    term.outputs.lineOut.attach(gcode.inputs.lineIn)
    
    multiline.outputs.lineOut.attach(gcode.inputs.lineIn)
    
    gcode.outputs.instructionOut.attach(planner.inputs.instruction)
    
    button.outputs.whammy.attach(numOut.inputs.evt)
    
    Jake Read's avatar
    Jake Read committed
    numOut.outputs.out.attach(multiline.inputs.req)
    button.outputs.whammy.attach(delay.inputs.thru)
    delay.outputs.out.attach(planner.inputs.run)
    
    
    // bi-directional hook planner / motor
    planner.outputs.moves.attach(xm.inputs.move)
    xm.outputs.ack.attach(planner.inputs.acks)
    
    planner.outputs.moves.attach(yma.inputs.move)
    yma.outputs.ack.attach(planner.inputs.acks)
    
    planner.outputs.moves.attach(ymb.inputs.move)
    
    Jake Read's avatar
    Jake Read committed
    //ymb.outputs.ack.attach(planner.inputs.acks)
    
    planner.outputs.moves.attach(zm.inputs.move)
    zm.outputs.ack.attach(planner.inputs.acks)
    
    
    Jake Read's avatar
    Jake Read committed
    planner.outputs.moveComplete.attach(multiline.inputs.req)
    
    
    // same motors / bridge 
    xm.state.axis = 'X'
    
    Jake Read's avatar
    Jake Read committed
    xm.state.spu = 100
    
    xm.outputs.packet.attach(bridge.inputs.A)
    bridge.outputs.A.attach(xm.inputs.packet)
    
    bridge.state.rA = '0,3'
    t1.outputs.packet.attach(bridge.inputs.A)
    bridge.outputs.A.attach(t1.inputs.packet)
    
    
    yma.state.axis = 'Y'
    
    Jake Read's avatar
    Jake Read committed
    yma.state.spu = -100
    
    yma.outputs.packet.attach(bridge.inputs.B)
    bridge.outputs.B.attach(yma.inputs.packet)
    
    bridge.state.rB = '0,0'
    t2.outputs.packet.attach(bridge.inputs.B)
    bridge.outputs.B.attach(t2.inputs.packet)
    
    
    ymb.state.axis = 'Y'
    
    // should have to reverse this but did it with wires :|
    
    Jake Read's avatar
    Jake Read committed
    ymb.state.spu = -100
    
    ymb.outputs.packet.attach(bridge.inputs.C)
    bridge.outputs.C.attach(ymb.inputs.packet)
    
    bridge.state.rC = '0,5'
    t3.outputs.packet.attach(bridge.inputs.C)
    bridge.outputs.C.attach(t3.inputs.packet)
    
    
    Jake Read's avatar
    Jake Read committed
    zm.state.axis = 'Z'
    
    Jake Read's avatar
    Jake Read committed
    zm.state.spu = -100
    
    zm.outputs.packet.attach(bridge.inputs.D)
    bridge.outputs.D.attach(zm.inputs.packet)
    
    bridge.state.rD = '0,1'
    t4.outputs.packet.attach(bridge.inputs.D)
    bridge.outputs.D.attach(t4.inputs.packet)
    
    t5.outputs.packet.attach(bridge.inputs.E)
    bridge.outputs.E.attach(t5.inputs.packet)
    
    bridge.state.rE = '0'
    
    Jake Read's avatar
    Jake Read committed
    bridge.init()
    
    
    Jake Read's avatar
    Jake Read committed
    multiline.load()
    
    
    // setting data w/r/t the representations they serve 
    
    Jake Read's avatar
    Jake Read committed
    setUiPos(multiline, 10, 10)
    
    setUiPos(gcode, 450, 10)
    setUiPos(term, 450, 180)
    setUiPos(button, 450, 300)
    setUiPos(numOut, 450, 400)
    setUiPos(delay, 450, 520)
    
    
    var plCol = 900
    
    setUiPos(t1, plCol, 730)
    setUiPos(t2, plCol, 850)
    setUiPos(t3, plCol, 970)
    setUiPos(t4, plCol, 1090)
    setUiPos(t5, plCol, 1210)
    setUiPos(planner, plCol, 10)
    
    Jake Read's avatar
    Jake Read committed
    
    var hwCol = 1350
    
    Jake Read's avatar
    Jake Read committed
    setUiPos(xm, hwCol, 10)
    setUiPos(yma, hwCol, 250)
    setUiPos(ymb, hwCol, 490)
    setUiPos(zm, hwCol, 730)
    
    Jake Read's avatar
    Jake Read committed
    setUiPos(bridge, hwCol, 970)
    
    // setting ui elements / state 
    
    planner.state.isRunning = 0
    
    Jake Read's avatar
    Jake Read committed
    term.state.uiInput = "G1 F100 Z10"
    
    term.state.uiInput = "G1 Y10"
    
    term.state.uiInput = "G1 X0"
    term.state.uiInput = "G1 Y0"
    
    Jake Read's avatar
    Jake Read committed
    term.state.uiInput = "G1 X10"
    
    planner.state.isRunning = 1
    
    term.state.uiInput = "G1 X0 Y10"
    term.state.uiInput = "G1 X-1 Y0"
    term.state.uiInput = "G1 X0 Y0"
    
    */
    
    function setUiPos(module, left, top){
        if(module.ui == null){
            module.ui = {}
        }
        module.ui.left = left
        module.ui.top = top
    }
    
    Jake Read's avatar
    Jake Read committed
    /*
    ------------------------------------------------------
    PROGRAM WRITING 
    ------------------------------------------------------
    
    Jake Read's avatar
    Jake Read committed
    function addModule(path) {
        // get and add to server system
        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
    
    Jake Read's avatar
    Jake Read committed
            mod.id = modules.length - 1
    
            // we need to add some top-level info to the inputs so that we can draw them 
    
    Jake Read's avatar
    Jake Read committed
            for (item in mod.inputs) {
    
                mod.inputs[item].parentId = mod.id
                mod.inputs[item].key = item
            }
    
    
            for (item in mod.state) {
                if (item == 'onChange' | item == 'emitChange' | item == 'emitters') {
                    //console.log('rolling past change fn')
                } else {
                    mod.state['_' + item] = mod.state[item]
                    mod.state[item] = {}
    
    Jake Read's avatar
    Jake Read committed
                    writeStateObject(mod, item)
    
            console.log('ADDING MODULE', mod)
    
    Jake Read's avatar
    Jake Read committed
            // now roll and return representable object 
    
    Jake Read's avatar
    Jake Read committed
            putRep(mod)
    
            // also to fn call, in case writing program ? 
            return mod
    
    Jake Read's avatar
    Jake Read committed
        } else {
    
            console.log('ERR no module found at', path)
    
    Jake Read's avatar
    Jake Read committed
    function writeStateObject(mod, item) {
        Object.defineProperty(mod.state, item, {
    
    Jake Read's avatar
    Jake Read committed
                // update internal value 
    
    Jake Read's avatar
    Jake Read committed
                //console.log('SET', item, this['_' + item])
    
    Jake Read's avatar
    Jake Read committed
                // push to internal state change handler
    
    Jake Read's avatar
    Jake Read committed
                // update server (or in some cases, a confirmation)
                putState(mod)
    
    Jake Read's avatar
    Jake Read committed
        Object.defineProperty(mod.state, item, {
    
    Jake Read's avatar
    Jake Read committed
                //console.log('GET', item, this['_' + item])
    
    Jake Read's avatar
    Jake Read committed
    /*
    
    PROGRAM UPDATING 
    
    */
    
    //console.log('modules at prgmem start', modules)
    function putReps() {
    
        var reps = new Array()
    
    Jake Read's avatar
    Jake Read committed
        for (mod in modules) {
    
            reps.push(makeRep(modules[mod]))
    
    Jake Read's avatar
    Jake Read committed
        }
    
        socketSend('add reps', reps)
    
    Jake Read's avatar
    Jake Read committed
    }
    
    
    Jake Read's avatar
    Jake Read committed
    function makeRep(mod) {
    
    Jake Read's avatar
    Jake Read committed
        // scrape for only things we'll want to represent 
        var rep = {
            id: mod.id,
            description: mod.description,
            inputs: mod.inputs,
            outputs: mod.outputs,
            state: {}
        }
    
        // holds UI information - to load mods at location
    
    Jake Read's avatar
    Jake Read committed
        if (mod.ui != null) {
    
            rep.ui = mod.ui
    
    Jake Read's avatar
    Jake Read committed
        }
    
    
    Jake Read's avatar
    Jake Read committed
        for (var key in mod.state) {
            if (isStateKey(key)) {
    
    Jake Read's avatar
    Jake Read committed
                rep.state[key] = mod.state[key]
            }
        }
    
    
        return rep
    }
    
    function putRep(mod) {
        socketSend('add rep', makeRep(mod))
    }
    
    
    Jake Read's avatar
    Jake Read committed
    function changeRep(mod) {
    
        socketSend('change rep', makeRep(mod))
    
    Jake Read's avatar
    Jake Read committed
    }
    
    // update state from UI to server 
    function changeState(data) {
    
    Jake Read's avatar
    Jake Read committed
        // should just recv all state, walk tree for differences
    
    Jake Read's avatar
    Jake Read committed
        var oldState = modules[data.id].state
        var newState = data.state
        // rep only holds proper key-values w/o emitters, _values etc
    
    Jake Read's avatar
    Jake Read committed
        for (var key in newState) {
    
    Jake Read's avatar
    Jake Read committed
            if (isStateKey(key)) {
    
    Jake Read's avatar
    Jake Read committed
                if (oldState[key].isButton) {
                    if (oldState[key].isPressed != newState[key].isPressed) {
    
                        console.log('CHANGE BUTTON STATE', key, 'to', newState[key], 'in', data.id)
    
    Jake Read's avatar
    Jake Read committed
                        // this will trigger some 'onChange' f'ns
                        // that might change some state
    
    Jake Read's avatar
    Jake Read committed
                        oldState[key] = newState[key]
    
    Jake Read's avatar
    Jake Read committed
                        // to prevent quickly changing it back, we'll exit now (one state change from UI per trx)
                        return true 
    
                } else if (oldState[key].isMultiLine) {
                    if (oldState[key].value != newState[key].value){
                        console.log('CHANGE MULTILINE STATE', key, 'to', newState[key].value, 'in', data.id)
                        oldState[key].value = newState[key].value 
                        return true 
                    }
    
    Jake Read's avatar
    Jake Read committed
                } else if (oldState[key] !== newState[key]) {
    
    Jake Read's avatar
    Jake Read committed
                    console.log('CHANGE STATE', key, 'to', newState[key], 'in', data.id)
    
    Jake Read's avatar
    Jake Read committed
                    oldState[key] = newState[key] 
                    return true 
    
    Jake Read's avatar
    Jake Read committed
                }
    
    Jake Read's avatar
    Jake Read committed
            }
    
    Jake Read's avatar
    Jake Read committed
    function changeUi(data) {
    
        var mod = modules[data.id]
    
    Jake Read's avatar
    Jake Read committed
        if (mod.ui == null) {
    
            console.log("NO UI")
            mod.ui = {}
        }
        mod.ui = data.ui
        console.log('CHANGE UI', mod.id, mod.ui)
    }
    
    
    Jake Read's avatar
    Jake Read committed
    function isStateKey(key) {
        if (key.indexOf('_') == 0 || key == 'emitters' || key == 'onChange' || key == 'emitChange') {
    
    Jake Read's avatar
    Jake Read committed
            return false
        } else {
            return true
        }
    }
    
    // push new state from server to UI
    function putState(mod) {
        // push just the state to the individual mod
    
    Jake Read's avatar
    Jake Read committed
        // copy only the keys we want to see 
        var state = {}
    
    Jake Read's avatar
    Jake Read committed
        for (var key in mod.state) {
            if (isStateKey(key)) {
    
    Jake Read's avatar
    Jake Read committed
                state[key] = mod.state[key]
            }
        }
    
    Jake Read's avatar
    Jake Read committed
        var data = {
            id: mod.id,
    
    Jake Read's avatar
    Jake Read committed
            state: state
    
    Jake Read's avatar
    Jake Read committed
        }
    
        socketSend('change state', data)
    
    Jake Read's avatar
    Jake Read committed
    }
    
    
    function putLink(data) {
        var fromModule = modules.find((module) => {
            return module.id === data.from.id
        })
    
        var toModule = modules.find((module) => {
            return module.id === data.to.id
        })
    
    
        if(fromModule.outputs[data.from.output].isLinked(toModule.inputs[data.to.input])){
        	console.log("HOOKDOWN")
        	fromModule.outputs[data.from.output].remove(toModule.inputs[data.to.input])
        } else {
        	fromModule.outputs[data.from.output].attach(toModule.inputs[data.to.input])
        }
    
        // replace it 
        changeRep(fromModule)
    
    Jake Read's avatar
    Jake Read committed
    function buildMenu() {
        var tree = {}
    
    Jake Read's avatar
    Jake Read committed
        var dir = fs.readdirSync('./src')
    
    Jake Read's avatar
    Jake Read committed
        for (i in dir) {
    
    Jake Read's avatar
    Jake Read committed
            tree[dir[i]] = {}
            var subdir = fs.readdirSync('./src/' + dir[i])
    
    Jake Read's avatar
    Jake Read committed
            for (j in subdir) {
    
    Jake Read's avatar
    Jake Read committed
                // find js files
    
    Jake Read's avatar
    Jake Read committed
                if (subdir[j].slice(-3) === '.js') {
    
    Jake Read's avatar
    Jake Read committed
                    var obj = {}
                    obj.path = './src/' + dir[i] + '/' + subdir[j]
                    tree[dir[i]][subdir[j].slice(0, -3)] = obj
                }
            }
        }
    
    Jake Read's avatar
    Jake Read committed
        return tree
    }
    
    Jake Read's avatar
    Jake Read committed
    // put