Commit 0bba9053 authored by Jake Read's avatar Jake Read
Browse files

osape -> firmware mod, start tuner interface

parent a0d55428
......@@ -4,3 +4,6 @@
[submodule "controller/osapjs"]
path = controller/osapjs
url = ssh://git@gitlab.cba.mit.edu:846/jakeread/osapjs.git
[submodule "firmware/cl-step-controller/src/osape"]
path = firmware/cl-step-controller/src/osape
url = ssh://git@gitlab.cba.mit.edu:846/jakeread/osape.git
/*
clank-client.js
clank controller client side
Jake Read at the Center for Bits and Atoms
(c) Massachusetts Institute of Technology 2020
This work may be reproduced, modified, distributed, performed, and
displayed for any purpose, but must acknowledge the open systems assembly protocol (OSAP) project.
Copyright is retained and must be preserved. The work is provided as is;
no warranty is provided, and users accept all liability.
*/
'use strict'
import OSAP from '../osapjs/core/osap.js'
import { TS, PK, DK, AK, EP } from '../osapjs/core/ts.js'
import NetRunner from '../osapjs/client/netrunner/osap-render.js'
// the clank 'virtual machine'
import ClStepVM from './clStepVM.js'
import { AutoPlot } from '../osapjs/client/components/autoPlot.js'
import { Button } from '../osapjs/client/interface/button.js'
import { TextInput } from '../osapjs/client/interface/textInput.js'
console.log("hello clank controller")
// an instance of some osap capable thing (the virtual object)
// that will appear on the network, be virtually addressable
let osap = new OSAP()
osap.name = "cl-step tuner client"
// draws network,
let netrunner = new NetRunner(osap, 10, 760, false)
// -------------------------------------------------------- THE VM
// vm,
let vm = new ClStepVM(osap, TS.route().portf(0).portf(1).end())
// -------------------------------------------------------- QUERY MAG
let magDiagnosticsBtn = new Button(10, 10, 94, 14, 'mag?')
magDiagnosticsBtn.onClick(() => {
vm.getMagDiagnostics().then((result) => {
console.log(result)
magDiagnosticsBtn.good("ok", 500)
}).catch((err) => {
console.error(err)
magDiagnosticsBtn.bad("err", 500)
})
})
// -------------------------------------------------------- DO CALIB
let runCalibBtn = new Button(10, 40, 94, 14, 'calibr8')
runCalibBtn.onClick(() => {
vm.runCalib().then(() => {
runCalibBtn.good("ok", 500)
}).catch((err) => {
console.error(err)
runCalibBtn.bad("err", 500)
})
})
// -------------------------------------------------------- QUERY POSITION
let posnLp = false
let positionQueryBtn = new Button(10, 70, 94, 14, 'pos?')
positionQueryBtn.onClick(() => {
if (posnLp) {
posnLp = false
} else {
let loop = () => {
if (!posnLp) {
positionQueryBtn.bad("halted", 500)
} else {
vm.getPosition().then((pos) => {
$(positionQueryBtn.elem).text(pos.toFixed(3))
setTimeout(loop, 10)
}).catch((err) => {
console.error(err)
posnLp = false;
setTimeout(loop, 10)
})
}
}
posnLp = true
loop()
}
})
// -------------------------------------------------------- LOOP SPEC
let encPlot = new AutoPlot(130, 10, 500, 210, 'encoder')
encPlot.setHoldCount(500)
//tempPlot.setYDomain(0, 100)
encPlot.redraw()
let anglePlot = new AutoPlot(130, 230, 500, 210, 'angle reading')
anglePlot.setHoldCount(500)
//tempPlot.setYDomain(0, 100)
anglePlot.redraw()
let angleEstPlot = new AutoPlot(130, 450, 500, 210, 'angle estimate')
angleEstPlot.setHoldCount(500)
//tempPlot.setYDomain(0, 100)
angleEstPlot.redraw()
let angleDotPlot = new AutoPlot(130, 670, 500, 210, 'angle dot')
angleDotPlot.setHoldCount(500)
angleDotPlot.redraw()
let specLp = false
let specPlotCount = 0
let loopQueryBtn = new Button(10, 100, 94, 14, 'spec?')
loopQueryBtn.onClick(() => {
if (specLp) {
specLp = false
} else {
let loop = () => {
if (!specLp) {
loopQueryBtn.bad("halted", 500)
} else {
vm.getSpec().then((spec) => {
specPlotCount++
encPlot.pushPt([specPlotCount, spec.encoder]); encPlot.redraw()
anglePlot.pushPt([specPlotCount, spec.angle]); anglePlot.redraw()
angleEstPlot.pushPt([specPlotCount, spec.angleEstimate]); angleEstPlot.redraw()
setTimeout(loop, 10)
}).catch((err) => {
console.error(err)
specLp = false;
setTimeout(loop, 10)
})
}
}
specLp = true
loop()
}
})
// -------------------------------------------------------- MOTION FEED
// -------------------------------------------------------- PID
let PIDController = (xPlace, yPlace) => {
let pVal = new TextInput(xPlace, yPlace + 120, 110, 20, '-0.1')
let iVal = new TextInput(xPlace, yPlace + 150, 110, 20, '0.0')
let dVal = new TextInput(xPlace, yPlace + 180, 110, 20, '0.1')
let pidSetBtn = new Button(xPlace, yPlace + 210, 104, 14, 'set PID')
pidSetBtn.onClick(() => {
let p = parseFloat(pVal.value)
let i = parseFloat(iVal.value)
let d = parseFloat(dVal.value)
if (Number.isNaN(p) || Number.isNaN(i) || Number.isNaN(d)) {
pidSetBtn.bad("bad parse", 1000)
return
}
tvm.setPIDTerms([p, i, d]).then(() => {
pidSetBtn.good("ok", 500)
}).catch((err) => {
console.error(err)
pidSetBtn.bad("err", 1000)
})
})
let tempPlot = new AutoPlot(xPlace + 120, yPlace, 420, 230)
tempPlot.setHoldCount(500)
//tempPlot.setYDomain(0, 100)
tempPlot.redraw()
let effortPlot = new AutoPlot(xPlace + 120, yPlace + 240, 420, 150)
effortPlot.setHoldCount(500)
//effortPlot.setYDomain(-10, 10)
effortPlot.redraw()
let tempLpBtn = new Button(xPlace, yPlace + 90, 104, 14, 'plot temp')
let tempLp = false
let tempLpCount = 0
tempLpBtn.onClick(() => {
if (tempLp) {
tempLp = false
tempLpBtn.good("stopped", 500)
} else {
let poll = () => {
if (!tempLp) return
tvm.getExtruderTemp().then((temp) => {
//console.log(temp)
tempLpCount++
tempPlot.pushPt([tempLpCount, temp])
tempPlot.redraw()
return tvm.getExtruderTempOutput()
}).then((effort) => {
//console.log(effort)
effortPlot.pushPt([tempLpCount, effort])
effortPlot.redraw()
setTimeout(poll, 200)
}).catch((err) => {
tempLp = false
console.error(err)
tempLpBtn.bad("err", 500)
})
}
tempLp = true
poll()
}
})
}
//PIDController(240, 10)
// -------------------------------------------------------- STARTUP LOCAL
let wscVPort = osap.vPort()
wscVPort.name = 'websocket client'
wscVPort.maxSegLength = 1024
let LOGPHY = false
// to test these systems, the client (us) will kickstart a new process
// on the server, and try to establish connection to it.
console.log("making client-to-server request to start remote process,")
console.log("and connecting to it w/ new websocket")
// ok, let's ask to kick a process on the server,
// in response, we'll get it's IP and Port,
// then we can start a websocket client to connect there,
// automated remote-proc. w/ vPort & wss medium,
// for args, do '/processName.js?args=arg1,arg2'
jQuery.get('/startLocal/osapl-usb-bridge.js', (res) => {
if (res.includes('OSAP-wss-addr:')) {
let addr = res.substring(res.indexOf(':') + 2)
if (addr.includes('ws://')) {
let status = EP.PORTSTATUS.OPENING
wscVPort.status = () => { return status }
console.log('starting socket to remote at', addr)
let ws = new WebSocket(addr)
// opens,
ws.onopen = (evt) => {
status = EP.PORTSTATUS.OPEN
// implement rx
ws.onmessage = (msg) => {
msg.data.arrayBuffer().then((buffer) => {
let uint = new Uint8Array(buffer)
if (LOGPHY) console.log('PHY WSC Recv')
if (LOGPHY) TS.logPacket(uint)
wscVPort.receive(uint)
}).catch((err) => {
console.error(err)
})
}
// implement tx
wscVPort.send = (buffer) => {
if (LOGPHY) console.log('PHY WSC Send', buffer)
ws.send(buffer)
}
}
ws.onerror = (err) => {
status = EP.PORTSTATUS.CLOSED
console.log('sckt err', err)
}
ws.onclose = (evt) => {
status = EP.PORTSTATUS.CLOSED
console.log('sckt closed', evt)
}
}
} else {
console.error('remote OSAP not established', res)
}
})
/*
clankVirtualMachine.js
cclStepVM.js
vm for Clank-CZ
vm for closed-loop stepper motors
Jake Read at the Center for Bits and Atoms
(c) Massachusetts Institute of Technology 2021
......@@ -13,31 +13,78 @@ no warranty is provided, and users accept all liability.
*/
import { TS, PK, DK, AK, EP } from '../osapjs/core/ts.js'
import TempVM from './tempVirtualMachine.js'
import MotorVM from './motorVirtualMachine.js'
/* bus ID (osap maps +1)
X: 0
YL: 1
YR: 2, term
Z: 3
TCS: 4
E: 5
HE: 6
LC: 7
BED: 8
*/
export default function ClStepVM(osap, route) {
// ------------------------------------------------------ 0: DIAGNOSTICS QUERY
let diagnosticsQuery = osap.query(route, TS.endpoint(0, 0), 512)
this.getMagDiagnostics = () => {
return new Promise((resolve, reject) => {
diagnosticsQuery.pull().then((data) => {
let result = {
magHi: TS.read('boolean', data, 0, true),
magLo: TS.read('boolean', data, 1, true),
cordicOverflow: TS.read('boolean', data, 2, true),
compensationComplete: TS.read('boolean', data, 3, true),
angularGainCorrection: TS.read('uint8', data, 4, true),
cordicMagnitude: TS.read('uint16', data, 5, true)
}
resolve(result)
}).catch((err) => {
reject(err)
})
})
}
// ------------------------------------------------------ 1: RUN CALIB
let calibEP = osap.endpoint()
calibEP.addRoute(route, TS.endpoint(0, 1), 512)
calibEP.setTimeoutLength(10000)
this.runCalib = () => {
return new Promise((resolve, reject) => {
calibEP.write(new Uint8Array(0)).then(() => {
resolve()
}).catch((err) => { reject(err) })
})
}
// ------------------------------------------------------ 3: POSITION QUERY
let positionQuery = osap.query(route, TS.endpoint(0, 3), 512)
this.getPosition = () => {
return new Promise((resolve, reject) => {
positionQuery.pull().then((data) => {
let pos = TS.read('float32', data, 0, true)
resolve(pos)
}).catch((err) => { reject(err) })
})
}
export default function ClankVM(osap, route) {
// ------------------------------------------------------ 4: LOOP SPEC
// ------------------------------------------------------ MOTION
let specQuery = osap.query(route, TS.endpoint(0, 4), 512)
this.getSpec = () => {
return new Promise((resolve, reject) => {
specQuery.pull().then((data) => {
let result = {
encoder: TS.read('uint16', data, 0, true),
angle: TS.read('float32', data, 2, true),
angleEstimate: TS.read('float32', data, 6, true)
}
resolve(result)
}).catch((err) => { reject(err) })
})
}
/*
// ------------------------------------------------------ POS
// ok: we make an 'endpoint' that will transmit moves,
let moveEP = osap.endpoint()
// add the machine head's route to it,
moveEP.addRoute(TS.route().portf(0).portf(1).end(), TS.endpoint(0, 1), 512)
// and set a long timeout,
moveEP.setTimeoutLength(60000)
// move like: { position: {X: num, Y: num, Z: num}, rate: num }
// moveEP.setTimeoutLength(60000)
this.addMoveToQueue = (move) => {
// write the gram,
let wptr = 0
......@@ -123,19 +170,6 @@ export default function ClankVM(osap, route) {
})
}
// an endpoint to write 'wait time' on the remote,
let waitTimeEP = osap.endpoint()
waitTimeEP.addRoute(TS.route().portf(0).portf(1).end(), TS.endpoint(0, 4), 512)
this.setWaitTime = (ms) => {
return new Promise((resolve, reject) => {
let datagram = new Uint8Array(4)
TS.write('uint32', ms, datagram, 0, true)
waitTimeEP.write(datagram).then(() => {
resolve()
}).catch((err) => { reject(err) })
})
}
// endpoint to set per-axis accelerations,
let accelEP = osap.endpoint()
accelEP.addRoute(TS.route().portf(0).portf(1).end(), TS.endpoint(0, 5), 512)
......@@ -179,252 +213,5 @@ export default function ClankVM(osap, route) {
}).catch((err) => { reject(err) })
})
}
// ------------------------------------------------------ MOTORS
// clank cz:
// AXIS SPU INVERT
// X: 320 false
// YL: 320 true
// YR: 320 false
// Z: 924.4r false
// E: 550 true
// per bondtech, for BMG on 16 microsteps, do 415: we are 32 microsteps
// https://www.bondtech.se/en/customer-service/faq/
// however, this is measured & calibrated: 830 was extruding 75mm for a 50mm request
/* bus ID (osap maps +1)
X: 0
YL: 1
YR: 2, term
Z: 3
TCS: 4
E: 5
HE: 6
LC: 7
BED: 8
*/
this.motors = {
X: new MotorVM(osap, TS.route().portf(0).portf(1).busf(1, 1).end()),
YL: new MotorVM(osap, TS.route().portf(0).portf(1).busf(1, 2).end()),
YR: new MotorVM(osap, TS.route().portf(0).portf(1).busf(1, 3).end()),
Z: new MotorVM(osap, TS.route().portf(0).portf(1).busf(1, 4).end()),
E: new MotorVM(osap, TS.route().portf(0).portf(1).busf(1, 6).end()),
}
let motorCurrents = [0.5, 0.5, 0.5, 0.5, 0.5]
this.setMotorCurrents = async () => {
try {
await this.motors.X.setCScale(motorCurrents[0])
await this.motors.YL.setCScale(motorCurrents[1])
await this.motors.YR.setCScale(motorCurrents[2])
await this.motors.Z.setCScale(motorCurrents[3])
//await this.motors.E.setCScale(motorCurrents[4])
} catch (err) {
console.error('bad motor current set')
throw err
}
}
// alias...
this.enableMotors = this.setMotorCurrents
this.disableMotors = async () => {
try {
await this.motors.X.setCScale(0)
await this.motors.YL.setCScale(0)
await this.motors.YR.setCScale(0)
await this.motors.Z.setCScale(0)
await this.motors.E.setCScale(0)
} catch (err) {
console.error('bad motor disable set')
throw err
}
}
this.initMotors = async () => {
// so, really, for these & the disable / enable / set current
// could do them all parallel: like this halts if i.e. YL fails,
// where it might just be that motor with an error... that'd be catching / continuing, accumulating
// errors, and reporting them in a group
try {
await this.motors.X.setAxisPick(0)
await this.motors.X.setAxisInversion(false)
await this.motors.X.setSPU(320)
} catch (err) {
console.error('bad x motor init')
throw err
}
try {
await this.motors.YL.setAxisPick(1)
await this.motors.YL.setAxisInversion(true)
await this.motors.YL.setSPU(320)
} catch (err) {
console.error('bad yl motor init')
throw err
}
try {
await this.motors.YR.setAxisPick(1)
await this.motors.YR.setAxisInversion(false)
await this.motors.YR.setSPU(320)
} catch (err) {
console.error('bad yr motor init')
throw err
}
try {
await this.motors.Z.setAxisPick(2)
await this.motors.Z.setAxisInversion(false)
await this.motors.Z.setSPU(924.444444)
} catch (err) {
console.error('bad z motor init')
throw err
}
/*
try {
await this.motors.E.setAxisPick(3)
await this.motors.E.setAxisInversion(true)
await this.motors.E.setSPU(550)
} catch (err) {
console.error('bad e motor init')
throw err
}
*/
await this.setMotorCurrents()
}
// ------------------------------------------------------ TOOLCHANGER
let tcServoEP = osap.endpoint()
tcServoEP.addRoute(TS.route().portf(0).portf(1).busf(1, 5).end(), TS.endpoint(0, 0), 512)
this.setTCServo = (micros) => {
let wptr = 0
let datagram = new Uint8Array(4)
// write micros
wptr += TS.write('uint32', micros, datagram, wptr, true)
// do the shipment
return new Promise((resolve, reject) => {
tcServoEP.write(datagram).then(() => {
console.warn('tc set', micros)
resolve()
}).catch((err) => {
reject(err)
})
})
}
this.openTC = () => {
return this.setTCServo(2000)
}
this.closeTC = () => {
return this.setTCServo(875)
}
// ------------------------------------------------------ TOOL CHANGING
// tool localization for put-down & pickup, tool statefulness,
// from back left 0,0
// put-down HE at (23.8, -177) -> (23.8, -222.6) -> release -> (-17.8, -208.6) clear -> (-17.8, -183)
// { position: {X: num, Y: num, Z: num}, rate: num }
let delay = (ms) => {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve() }, ms)
})
}
this.delta = async (move, rate) => {
try {
if (!rate) rate = 6000
await this.setWaitTime(1)
await delay(5)
await this.awaitMotionEnd()
let cp = await this.getPos()
console.log('current', cp)
await this.addMoveToQueue({
position: { X: cp.X + move[0], Y: cp.Y + move[1], Z: cp.Z + move[2] },
rate: rate
})
await delay(5)
await this.awaitMotionEnd()
await this.setWaitTime(100)
} catch (err) {
console.error('arising during delta')
throw err