Commit ceeff7a9 authored by Jake Read's avatar Jake Read

controller calibrated, UI halfway

parent 0c223c5e
......@@ -14,9 +14,9 @@ no warranty is provided, and users accept all liability.
'use strict'
import GRIDSQUID from './drawing/gridsquid.js'
import { TS } from '../core/ts.js'
import LeastSquares from './utes/lsq.js'
import DEXUI from './dex-ui.js'
import * as dat from './libs/dat.gui.module.js'
......@@ -68,6 +68,10 @@ document.addEventListener('keydown', (evt) => {
}
})
// -------------------------------------------------------- Button Smashing
let UI = new DEXUI()
// -------------------------------------------------------- DEX Virtual Machine
let TIMEOUT_MS = 5000
......@@ -79,6 +83,14 @@ let DEXKEY_MOTORENABLE = 16
let dex = {}
let lsq = new LeastSquares()
// donate some x, y readings to calibrate the least squares
// here this was observed integer outputs from the amp, and load applied during those readings
lsq.setObservations(
[[25, 14854, 29649, 44453, 74061, 103695],
[0, -0.100, -0.200, -0.300, -0.500, -0.700]])
// loadcells need to be calibrated,
dex.readLoadcell = () => {
return new Promise((resolve, reject) => {
if(!wscPort.send){
......@@ -93,7 +105,7 @@ dex.readLoadcell = () => {
reject('oddball key after loadcell request')
} else {
let counts = TS.read('int32', data, 1, true)
resolve(counts)
resolve(lsq.predict(counts))
}
}
setTimeout(() => {
......@@ -142,9 +154,9 @@ dex.setMotorEnable = (val) => {
reject('oddball key after motor enable request')
} else {
if(data[1] > 0){
resolve('enabled')
resolve(true)
} else {
resolve('disabled')
resolve(false)
}
}
} // end recv
......@@ -155,15 +167,28 @@ dex.setMotorEnable = (val) => {
})
}
dex.step = (count) => {
// mm -> steps,
// microsteps are 16
// pinion is 20T, driven is 124
// lead screw is 1204: 4mm / revolution,
dex.spmm = (200 * 16 / (20/124)) / 4
dex.maxspeed = 0.5 //mm/s it's slow !
console.log('SPMM', dex.spmm)
dex.step = (mm) => {
// assert maximum move increment here,
if(mm > 20) mm = 20
if(mm < -20) mm = -20
return new Promise((resolve, reject) => {
if(!wscPort.send){
reject('port to dex is closed')
return
}
// calculate for spmm,
let steps = Math.floor(- dex.spmm * mm)
console.log('STEPS', steps)
let req = new Uint8Array(5)
req[0] = DEXKEY_STEPS
TS.write('int32', count, req, 1, true)
TS.write('int32', steps, req, 1, true)
wscPort.send(req)
let rejected = false
dex.recv = (data) => {
......@@ -172,13 +197,15 @@ dex.step = (count) => {
reject('oddball key after step request')
} else {
let increment = TS.read('int32', data, 1, true)
resolve(increment)
resolve(- increment / dex.spmm)
}
} // end recv
let moveTime = (Math.abs(mm / dex.maxspeed) + 5) * 1000
console.log('TIME', moveTime)
setTimeout(() => {
rejected = true
reject('timeout')
}, TIMEOUT_MS)
}, moveTime)
})
}
......@@ -227,13 +254,16 @@ jQuery.get('/startLocal/dex-usb-bridge.js', (res) => {
console.error(err)
})
}
UI.start(dex)
}
ws.onerror = (err) => {
console.error('sckt err', err)
UI.connectionError('socket closed')
}
ws.onclose = (evt) => {
console.error('sckt closed', evt)
wscPort.send = null
UI.connectionError('socket closed')
}
}
} else {
......
/*
dex-ui.js
dex interface 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.
*/
export default function DEXUI(){
// the machine object,
let dex = null
this.start = (machine) => {
console.warn('alive')
dex = machine
clear()
}
// when lower levels bail, refreshing is the move
this.connectionError = (msg) => {
console.error('HALT')
}
// elements
let dom = $('#wrapper').get(0)
// UI util
let setPosition = (div, x, y) => {
$(dom).append(div)
$(div).css('left', `${x}px`).css('top', `${y}px`)
}
// hold flag
let waitUi = $('<div>').addClass('flag').get(0)
setPosition(waitUi, 180, 50)
let waitStatus = false
let waiting = () => {
waitStatus = true
$(waitUi).css('background-color', '#f7bbb7')
}
let clear = () => {
waitStatus = false
$(waitUi).css('background-color', '#b7f7c3')
}
let isWaiting = () =>{
return waitStatus
}
clear()
// incremental stacks
let ypos = 50
let yinc = 30
// position / zero
let position = 0
let posDisplay = $('<div>').addClass('button').get(0)
setPosition(posDisplay, 50, ypos)
posDisplay.update = (num) => {
position = num
$(posDisplay).text(`${num.toFixed(5)}mm`)
}
posDisplay.update(position)
$(posDisplay).click((evt) => {
position = 0
posDisplay.update(position)
})
let enableButton = $('<div>').addClass('button').get(0)
setPosition(enableButton, 50, ypos += yinc)
let motorEnabled = false
enableButton.update = (state) => {
if(state){
motorEnabled = true
$(enableButton).text('disable motor')
$(enableButton).css('background-color', '#b7f7c3')
} else {
motorEnabled = false
$(enableButton).text('enable motor')
$(enableButton).css('background-color', '#f7bbb7')
}
}
enableButton.update(motorEnabled)
$(enableButton).click((evt) => {
if(isWaiting()) return
waiting()
let req = !motorEnabled
dex.setMotorEnable(req).then((state) => {
clear()
enableButton.update(state)
}).catch((err) => {
clear()
console.error(err)
})
})
// increment-by
let incBy = $('<input type="text" value="5">').addClass('inputwrap').get(0)
setPosition(incBy, 50, ypos += yinc)
//console.log(incBy.value)
// up-button
let incUp = $('<div>').addClass('button').get(0)
setPosition(incUp, 50, ypos += yinc)
$(incUp).text('jog up')
$(incUp).click((evt) => {
if(isWaiting()) return
waiting()
dex.step(parseFloat(incBy.value)).then((increment) => {
clear()
posDisplay.update(position += increment)
}).catch((err) => {
clear()
console.error(err)
})
})
// down-button
let incDown = $('<div>').addClass('button').get(0)
setPosition(incDown, 50, ypos += yinc)
$(incDown).text('jog down')
$(incDown).click((evt) => {
if(isWaiting()) return
waiting()
dex.step(parseFloat(incBy.value) * -1).then((increment) => {
clear()
posDisplay.update(position += increment)
}).catch((err) => {
clear()
console.error(err)
})
})
// reading / zero
let load = 0
let loadDisplay = $('<div>').addClass('button').get(0)
setPosition(loadDisplay, 50, ypos += yinc + 10)
loadDisplay.update = (num) => {
load = num
$(loadDisplay).text(`${num.toFixed(3)}N`)
}
loadDisplay.update(load)
$(loadDisplay).click((evt) => {
if(isWaiting()) return
waiting()
dex.tareLoadcell().then(() => {
clear()
loadDisplay.update(0)
}).catch((err) => {
clear()
console.error(err)
})
})
// read request
let readReq = $('<div>').addClass('button').get(0)
setPosition(readReq, 50, ypos += yinc)
$(readReq).text('read loadcell')
$(readReq).click((evt) => {
if(isWaiting()) return
waiting()
dex.readLoadcell().then((load) => {
clear()
loadDisplay.update(load)
}).catch((err) => {
clear()
console.error(err)
})
})
// testing
// test-by
let testStep = $('<input type="text" value="0.01">').addClass('inputwrap').get(0)
setPosition(incBy, 50, ypos += yinc + 10)
// start / stop testing
let testButton = $('<div>').addClass('button').get(0)
setPosition(testButton, 50, ypos += yinc)
let testing = false
testButton.update = (state) => {
if(state){
testing = true
$(testButton).text('disable motor')
$(testButton).css('background-color', '#b7f7c3')
} else {
testing = false
$(testButton).text('enable motor')
$(testButton).css('background-color', '#f7bbb7')
}
}
testButton.update(testing)
$(testButton).click((evt) => {
if(testing){
// start testing
} else {
// stop testing
}
})
}
......@@ -40,6 +40,37 @@ body {
position: absolute;
}
.flag {
position: absolute;
width: 20px;
height: 20px;
}
.button {
position: absolute;
width: 100px;
height: 20px;
padding: 5px;
background-color: #dbdbdb;
color: #000;
font-size: 15px;
text-align: center;
}
.button:hover{
background-color: #e6e6e6;
cursor: pointer;
}
.inputwrap{
position:absolute;
width: 100px;
height: 20px;
padding: 5px;
border: none;
background-color: #dbdbdb;
}
/* svg bs */
.svgcont {
......
/*
lsq.js
input previous system measurements as state (lists)
make predictions for y based on input at x, with lsq. from old data
*/
import smallmath from './smallmath.js'
export default function LeastSquares() {
// internal state
let observations = []
let m = 1
let b = 0
// setup
this.setObservations = (xy) => {
observations = JSON.parse(JSON.stringify(xy))
if(observations[0].length > 2){
let lsqr = smallmath.lsq(observations[0], observations[1])
m = lsqr.m
b = lsqr.b
}
}
// to generate human-readable interp of model
this.printFunction = () => {
if (b >= 0) {
return `${m.toExponential(2)} x + ${b.toExponential(2)}`
} else {
return `${m.toExponential(2)} x ${b.toExponential(2)}`
}
}
this.predict = (x) => {
return m * x + b
}
}
// least squares from https://medium.com/@sahirnambiar/linear-least-squares-a-javascript-implementation-and-a-definitional-question-e3fba55a6d4b
// mod to return object of m, b, values.x, values.y,
var smallmath = {
lsq: function(values_x, values_y) {
var x_sum = 0;
var y_sum = 0;
var xy_sum = 0;
var xx_sum = 0;
var count = 0;
/*
* The above is just for quick access, makes the program faster
*/
var x = 0;
var y = 0;
var values_length = values_x.length;
if (values_length != values_y.length) {
throw new Error('The parameters values_x and values_y need to have same size!');
}
/*
* Above and below cover edge cases
*/
if (values_length === 0) {
return [
[],
[]
];
}
/*
* Calculate the sum for each of the parts necessary.
*/
for (let i = 0; i < values_length; i++) {
x = values_x[i];
y = values_y[i];
x_sum += x;
y_sum += y;
xx_sum += x * x;
xy_sum += x * y;
count++;
}
/*
* Calculate m and b for the line equation:
* y = x * m + b
*/
var m = (count * xy_sum - x_sum * y_sum) / (count * xx_sum - x_sum * x_sum);
var b = (y_sum / count) - (m * x_sum) / count;
/*
* We then return the x and y data points according to our fit
*/
var result_values_x = [];
var result_values_y = [];
for (let i = 0; i < values_length; i++) {
x = values_x[i];
y = x * m + b;
result_values_x.push(x);
result_values_y.push(y);
}
return {
m: m,
b: b,
values: {
x: result_values_x,
y: result_values_y
}
}
}
}
export default smallmath
......@@ -47,7 +47,7 @@ WSSPipe.start().then((ws) => {
if(serPort.send){
if(LOGPHY) console.log('forwarding wss -> usb...')
serPort.send(msg.data)
}
}
}
ws.onerror = (err) => {
console.log('WSS ERROR', err)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment