Week 10: Machine Making

2018-11-21

The Task

Create a machine.

Our Idea

We decided to make a 360 degree panoramic camera that can be controlled from an interface.

My Role

Abdul and I are the CS majors in the Harvard section, so we decided to take charge of doing most of the coding for the week that involved writing new code or figuring out how a large body of code works. The part of the project that related to this was the programming of the stepper motor.

Learning rndmc module

This week, we were told that the best way to program the components that we are given is to use the Javascript module that Jake Read created. It is called rndmc and can be found here. One interesting thing is the very fact that the module is written in Javascript. I’m quite sure that this is a highly uncommon choice of language for programming hardware, but I guess it sort of makes sense in light of the fact that it takes advantage of Node’s philosophy of being able to use Javascript everywhere. At the end of the day, I actually didn’t mind the style of the module, especially since it’s still in development, despite quirks such as functions being used as classes (see here for instance).

We’re supposed to write this documentation largely so that future generations are able to look back and hopefully learn from the mistakes we made and also to pass down the institutional knowledge about how things work. To this end, I want to give my quick overview of how the code works, as I understand it.

First, there’s no need to pay attention really to any folders besides modules and src. modules contains the logic for interacting with the various types of components that are available. This includes hardware, motion, and much more.

src contains some useful files if you want to understand how rndmc comes together - this is definitely necessary if you are stuck on some inexplicable bug, which is what happened to us :D. In my opinion, the most useful file is jsunit.js. Here is it reproduced:

// 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.removeAllLinks = function() {
        this.calls = []
    }

    output.checkLinks = function(id) {
        console.log('checking links', this)
        for (index in this.calls) {
            if (this.calls[index].parentId == id) {
                console.log('popping null entry from', this.calls)
                this.calls.splice(index, 1)
                console.log('new record', this.calls)
            } else {
                // all good
            }
        }
    }

    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.parentId = null
    state.socket = null

    // called when change from UI
    state.onUiChange = function(item, fn) {
        this.emitters[item] = fn
    }

    state.emitUIChange = function(item) {
        if (this.emitters[item] != null) {
            this.emitters[item]()
        }
    }

    state.pushToUI = function(key) {
        if (this.socket) {
            var data = {
                id: this.parentId,
                key: key,
                val: this[key]
            }
            this.socket.send('put state change', data)
        } else {
            console.log("ERR on state update to UI, socket is", this.socket)
        }
    }

    state.init = function(parentModId, socket) {
        // get hookups from top level program
        this.parentId = parentModId
        this.socket = socket
        // and wrap state objects in getters / setters
        for (key in this) {
            if (isStateKey(key)) {
                this['_' + key] = this[key]
                this[key] = {}
                writeStateGetterSetter(this, key)
            }
        }
    }

    return state
}

function writeStateGetterSetter(state, key) {
    Object.defineProperty(state, key, {
        set: function(x) {
            // update internal value
            state['_' + key] = x
            // console.log('SET', key, this['_' + key])
            // push to external view
            state.pushToUI(key)
        }
    })
    Object.defineProperty(state, key, {
        get: function() {
            //console.log('GET', key, this['_' + key])
            return state['_' + key]
        }
    })
}

function isStateKey(key) {
    if (key.indexOf('_') == 0 || key == 'parentId' || key == 'socket' || key == 'init' || key == 'pushToUI' || key == 'emitters' || key == 'onUIChange' || key == 'emitUIChange' ) {
        return false
    } else {
        return true
    }
}

module.exports = {
    Input: Input,
    Output: Output,
    State: State,
    isStateKey: isStateKey
}

The first thing to note is again, capitalized functions are used effectively as classes. Then, note that there are two main classes of importance, Input and Output. Modules seem to have to have outputs of the Output class, while the Input class is used for logic that involves delays and repeats of particular actions, etc.. and is not mandatory. For instance, if you want the logic in some component module to be repeated 10 times at 10 second intervals, this involves the attachment of Inputs.

Please note that while these things are important to understand in order to understand the module, there is no need to hardcode any sort of inputs or outputs.

Further, there is a file called atkunit.js also in src that describes the Hardware class. I believe that most of the components must engage with this class in some way, so it’s worth it to look over it to some extent.

I’m happy to try to answer any questions about the module in future years, if you happen to come across this page. You can reach out to the gmail account jeffreyw128 (note that I’m scared that web scrapers are now capable of scraping the more common varieties of email masking).

Implementation

This week was a ton of late night wild goose chases. In short, I did not understand a particular error message and thought that it was the cause of all of our woes. This message was inputs bound to this output' and it is printed in the jsunit.js` file when there are no inputs. At the time, I did not know that this is fine - you can have outputs with inputs. So when we sent commands to our router board which in turn went to the stepper board and then to the stepper, I did not realize that this message is okay to have. Debugging this involved me trying to basically hack the code into having inputs, even though inputs are really supposed to be added through the GUI. It turns out that while I believe that outputs are named correctly, inputs are things that are only necessary when some additional GUI logic is added.

After many nights of trying many different things, we asked Jake for help and realized that our problem was actually just a busted stepper board. This was really a surprising finding because the board’s LEDs were lighting correctly and because it’s a manufactured board, it looks very pristine. Switching the board out solved our issue - the stepper was finally working! (See basically anybody else’s page from the Harvard section 2018 to see it in action) Our final GUI looked like this:

final GUI

There are two steppers as well as some input repeat logic.

Misc

Also spent a significant amount of time this week helping Lara and Jasmine with programming the cammera shutter functinonality. That is documented here and here.