*
This commit is contained in:
@@ -0,0 +1,561 @@
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<!-- PARAMETER DROPDOWN MENU -->
|
||||
<div v-show="paramsVisible" class="dropdown">
|
||||
<div class="card dropdown-content">
|
||||
<div>
|
||||
<button class="close-button" @click="hideTree">X</button>
|
||||
Parameters
|
||||
</div>
|
||||
<div class="param-tree">
|
||||
<json-view
|
||||
:data="treeParams"
|
||||
:rootKey="'odrives'"
|
||||
v-on:selected="addVarToElement"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard_container">
|
||||
<div class="controls">
|
||||
<template v-for="(control, index) in dash.controls">
|
||||
<component
|
||||
:is="control.controlType"
|
||||
:key="index + '-control'"
|
||||
:path="control.path"
|
||||
:name="control.name"
|
||||
:options="control.options"
|
||||
:odrives="odrives"
|
||||
:dashID="dash.id"
|
||||
/>
|
||||
</template>
|
||||
<div class="control-buttons">
|
||||
<div class="add-button card" @click="addComponent('control')">Add Control</div>
|
||||
<div class="add-button card" @click="addComponent('slider')">Add Slider</div>
|
||||
<div class="add-button card" @click="addComponent('function')">Add Function</div>
|
||||
</div>
|
||||
<template v-for="(action, index) in dash.actions">
|
||||
<component
|
||||
:is="action.actionType"
|
||||
:id="action.id"
|
||||
:key="index + '-action'"
|
||||
:path="action.path"
|
||||
:odrives="odrives"
|
||||
:initVal="action.val"
|
||||
:dashID="dash.id"
|
||||
:options="action.options"
|
||||
/>
|
||||
</template>
|
||||
<div class="add-button card" @click="addComponent('action')">Add Action</div>
|
||||
</div>
|
||||
<div class="plots">
|
||||
<template v-for="(plot, index) in dash.plots">
|
||||
<plot
|
||||
:plot="plot"
|
||||
:key="index"
|
||||
:name="plot.name"
|
||||
:dashID="dash.id"
|
||||
v-on:add-var="currentPlot=plot.name;addComponent('plot')"
|
||||
/>
|
||||
</template>
|
||||
<div class="add-button card" @click="addPlot">Add Plot</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//this component will get passed a list of controls and plots
|
||||
//display controls on the left and plots on the right?
|
||||
//leave full names for plots
|
||||
//collate controls into individual cards based on the deepest common level
|
||||
import CtrlBoolean from "../components/controls/CtrlBoolean.vue";
|
||||
import CtrlNumeric from "../components/controls/CtrlNumeric.vue";
|
||||
import CtrlFunction from "../components/controls/CtrlFunction.vue";
|
||||
import CtrlSlider from "../components/controls/CtrlSlider.vue";
|
||||
import CtrlEnum from "../components/controls/CtrlEnum.vue";
|
||||
import Plot from "../components/plots/Plot.vue";
|
||||
import Action from "../components/actions/Action.vue";
|
||||
import ActionEnum from "../components/actions/ActionEnum.vue"
|
||||
import { JSONView } from "vue-json-component";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
let plotColors = [
|
||||
"#1f77b4", // blue
|
||||
"#ff7f0e", // orange
|
||||
"#2ca02c", // green
|
||||
"#d62728", // red
|
||||
"#9467bd", // purple
|
||||
"#8c564b", // brown
|
||||
"#e377c2", // pink
|
||||
"#7f7f7f", // gray
|
||||
"#bcbd22", // olive
|
||||
"#17becf", // cyan
|
||||
];
|
||||
|
||||
let odriveEnums = {
|
||||
// axis requested state
|
||||
requested_state: [
|
||||
{
|
||||
text: "Undefined",
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
text: "Idle",
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
text: "Startup Sequence",
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
text: "Full Calibration Sequence",
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
text: "Motor Calibration",
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
text: "Sensorless Control",
|
||||
value: 5,
|
||||
},
|
||||
{
|
||||
text: "Encoder Index Search",
|
||||
value: 6,
|
||||
},
|
||||
{
|
||||
text: "Encoder Offset Calibration",
|
||||
value: 7,
|
||||
},
|
||||
{
|
||||
text: "Closed Loop Control",
|
||||
value: 8,
|
||||
},
|
||||
{
|
||||
text: "Lockin Spin",
|
||||
value: 9,
|
||||
},
|
||||
{
|
||||
text: "Encoder Direction Find",
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
text: "Homing",
|
||||
value: 11,
|
||||
},
|
||||
{
|
||||
text: "Encoder Hall Polarity Calibration",
|
||||
value: 12,
|
||||
},
|
||||
{
|
||||
text: "Encoder Hall Phase Calibration",
|
||||
value: 13,
|
||||
}
|
||||
],
|
||||
current_state: [
|
||||
{
|
||||
text: "Undefined",
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
text: "Idle",
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
text: "Startup Sequence",
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
text: "Full Calibration Sequence",
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
text: "Motor Calibration",
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
text: "Sensorless Control",
|
||||
value: 5,
|
||||
},
|
||||
{
|
||||
text: "Encoder Index Search",
|
||||
value: 6,
|
||||
},
|
||||
{
|
||||
text: "Encoder Offset Calibration",
|
||||
value: 7,
|
||||
},
|
||||
{
|
||||
text: "Closed Loop Control",
|
||||
value: 8,
|
||||
},
|
||||
{
|
||||
text: "Lockin Spin",
|
||||
value: 9,
|
||||
},
|
||||
{
|
||||
text: "Encoder Direction Find",
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
text: "Homing",
|
||||
value: 11,
|
||||
},
|
||||
{
|
||||
text: "Encoder Hall Polarity Calibration",
|
||||
value: 12,
|
||||
},
|
||||
{
|
||||
text: "Encoder Hall Phase Calibration",
|
||||
value: 13,
|
||||
}
|
||||
],
|
||||
// encoder mode
|
||||
mode: [
|
||||
{
|
||||
text: "Incremental",
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
text: "Hall Effect",
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
text: "Sine Cosine",
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
text: "CUI Absolute",
|
||||
value: 256,
|
||||
},
|
||||
{
|
||||
text: "AMS Absolute",
|
||||
value: 257,
|
||||
},
|
||||
{
|
||||
text: "AEAT Absolute",
|
||||
value: 258,
|
||||
},
|
||||
{
|
||||
text: "RLS Absolute",
|
||||
value: 259,
|
||||
},
|
||||
],
|
||||
// motor type
|
||||
motor_type: [
|
||||
{
|
||||
text: "High Current",
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
text: "Gimbal",
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
text: "Induction",
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
// controller control mode
|
||||
control_mode: [
|
||||
{
|
||||
text: "Voltage Control",
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
text: "Torque Control",
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
text: "Velocity Control",
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
text: "Position Control",
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
input_mode: [
|
||||
{
|
||||
text: "Inactive",
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
text: "Passthrough",
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
text: "Velocity Ramp",
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
text: "Position Filter",
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
text: "Mix Channels",
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
text: "Trapezoidal Trajectory",
|
||||
value: 5
|
||||
},
|
||||
{
|
||||
text: "Torque Ramp",
|
||||
value: 6
|
||||
},
|
||||
{
|
||||
text: "Mirror",
|
||||
value: 7
|
||||
},
|
||||
{
|
||||
text: "Tuning",
|
||||
value: 8
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "Dashboard",
|
||||
components: {
|
||||
CtrlBoolean,
|
||||
CtrlNumeric,
|
||||
CtrlFunction,
|
||||
CtrlSlider,
|
||||
CtrlEnum,
|
||||
Plot,
|
||||
Action,
|
||||
ActionEnum,
|
||||
"json-view": JSONView,
|
||||
},
|
||||
props: ["dash", "odrives"],
|
||||
data() {
|
||||
return {
|
||||
paramsVisible: false,
|
||||
addCompType: undefined,
|
||||
currentPlot: undefined,
|
||||
treeParams: undefined,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
deleteAction(e) {
|
||||
this.$emit("delete-action", e);
|
||||
console.log(e);
|
||||
},
|
||||
deletePlot(e) {
|
||||
this.$emit("delete-plot", e);
|
||||
},
|
||||
addVar(e) {
|
||||
this.$emit("add-var", e);
|
||||
},
|
||||
showTree() {
|
||||
//show the parameter tree
|
||||
this.paramsVisible = true;
|
||||
},
|
||||
hideTree() {
|
||||
this.paramsVisible = false;
|
||||
this.addCompType = undefined;
|
||||
},
|
||||
addComponent(componentType) {
|
||||
this.addCompType = componentType;
|
||||
switch (this.addCompType) {
|
||||
case "control":
|
||||
this.treeParams = this.$store.state.odriveConfigs['params'];
|
||||
console.log(this.treeParams);
|
||||
break;
|
||||
case "plot":
|
||||
this.treeParams = this.$store.state.odriveConfigs['params'];
|
||||
break;
|
||||
case "slider":
|
||||
this.treeParams = this.$store.state.odriveConfigs['writeAbleNumeric'];
|
||||
break;
|
||||
case "action":
|
||||
this.treeParams = this.$store.state.odriveConfigs['writeAble'];
|
||||
break;
|
||||
case "function":
|
||||
this.treeParams = this.$store.state.odriveConfigs['functions'];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.paramsVisible = true;
|
||||
},
|
||||
addVarToElement(e) {
|
||||
//when the parameter tree is open and a parameter is clicked,
|
||||
//add the clicked parameter to the list of controls for the
|
||||
//current dashboard
|
||||
console.log(e);
|
||||
switch (this.addCompType) {
|
||||
case "control":
|
||||
switch (typeof e.value) {
|
||||
case "boolean":
|
||||
this.dash.controls.push({
|
||||
controlType: "CtrlBoolean",
|
||||
path: e.path,
|
||||
});
|
||||
//this.$store.commit("addSampledProperty", e.path);
|
||||
break;
|
||||
case "number":
|
||||
if (Object.keys(odriveEnums).includes(e.key)){
|
||||
this.dash.controls.push({
|
||||
controlType: "CtrlEnum",
|
||||
path: e.path,
|
||||
options: odriveEnums[e.key],
|
||||
})
|
||||
}
|
||||
else {
|
||||
this.dash.controls.push({
|
||||
controlType: "CtrlNumeric",
|
||||
path: e.path,
|
||||
});
|
||||
}
|
||||
//this.$store.commit("addSampledProperty", e.path);
|
||||
break;
|
||||
case "string":
|
||||
this.dash.controls.push({
|
||||
controlType: "CtrlFunction",
|
||||
path: e.path,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "function":
|
||||
this.dash.controls.push({
|
||||
controlType: "CtrlFunction",
|
||||
path: e.path,
|
||||
});
|
||||
break;
|
||||
case "plot":
|
||||
// add the selected element to the plot var list
|
||||
// add the selected element to the sampling var list
|
||||
// find the plot, append path to plot.vars
|
||||
console.log(e);
|
||||
for (const plot of this.dash.plots) {
|
||||
if (plot.name == this.currentPlot) {
|
||||
plot.vars.push({
|
||||
path: e.path,
|
||||
color: plotColors[plot.vars.length % plotColors.length],
|
||||
});
|
||||
this.$store.commit("addSampledProperty", e.path);
|
||||
console.log(plot);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "action":
|
||||
{
|
||||
// add an action to the current dash
|
||||
let id = uuidv4();
|
||||
console.log("action, key = " + e.key);
|
||||
if (Object.keys(odriveEnums).includes(e.key)){
|
||||
this.dash.actions.push({
|
||||
actionType: "ActionEnum",
|
||||
id: id,
|
||||
path: e.path,
|
||||
options: odriveEnums[e.key],
|
||||
})
|
||||
}
|
||||
else {
|
||||
this.dash.actions.push({
|
||||
actionType: "Action",
|
||||
id: id,
|
||||
path: e.path,
|
||||
val: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "slider":
|
||||
// add a slider to the list of controls if the selected item is valid (numeric)
|
||||
switch (typeof e.value) {
|
||||
case "number":
|
||||
this.dash.controls.push({
|
||||
controlType: "CtrlSlider",
|
||||
path: e.path,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
addPlot() {
|
||||
let plotId = uuidv4();
|
||||
this.dash.plots.push({
|
||||
name: plotId,
|
||||
vars: [],
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dashboard {
|
||||
background-color: var(--bg-color);
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
width: 100vw;
|
||||
padding-top: var(--top-height);
|
||||
padding-bottom: var(--bottom-height);
|
||||
}
|
||||
|
||||
.dashboard_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 95vh;
|
||||
}
|
||||
.controls {
|
||||
flex-grow: 1;
|
||||
border-right: 1px solid lightgrey;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
background-color: lightcyan;
|
||||
width: 120px;
|
||||
margin: 10px auto;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
text-align: center;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.add-button:active {
|
||||
background-color: lightblue;
|
||||
}
|
||||
|
||||
.plots {
|
||||
flex-grow: 1;
|
||||
max-height: 94vh;
|
||||
overflow-y: scroll;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.control-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
padding: 0 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.param-tree {
|
||||
overflow-y: scroll;
|
||||
max-height: 85vh;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<div class="logo">
|
||||
<img alt="Odrive Logo" src="../assets/odrive_logo.png">
|
||||
</div>
|
||||
<div class="home_text">
|
||||
To set up your ODrive, connect it and power it up.
|
||||
</div>
|
||||
<div class="connected-container">
|
||||
<input type="text" :class="{ notConnected: notConnected, connected: connected}" v-on:change="setUrl" :value="serverAddress">
|
||||
</div>
|
||||
<button class="show-msg-button" @click="showServerMessages = !showServerMessages;">Having trouble? Click here for debug output</button>
|
||||
<div class="server-msgs" v-if="showServerMessages">
|
||||
<div class="server-msg" v-for="(msg, index) in serverMessages" :key="msg + index">{{msg}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Home',
|
||||
components: {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showServerMessages: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
connected() {
|
||||
return this.$store.state.serverConnected == true;
|
||||
},
|
||||
notConnected() {
|
||||
return this.$store.state.serverConnected != true;
|
||||
},
|
||||
serverAddress() {
|
||||
return this.$store.state.odriveServerAddress;
|
||||
},
|
||||
serverMessages() {
|
||||
if (this.$store.state.serverOutput.length >= 10) {
|
||||
let len = this.$store.state.serverOutput.length;
|
||||
return this.$store.state.serverOutput.slice(len-10,len);
|
||||
}
|
||||
else {
|
||||
return this.$store.state.serverOutput;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setUrl(e) {
|
||||
console.log(e.target.value);
|
||||
this.$store.dispatch("setServerAddress", e.target.value);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.home {
|
||||
padding: 10% 0;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--bg-color);
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
img {
|
||||
margin: auto;
|
||||
display: block;
|
||||
max-width: 50%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.home_text {
|
||||
font-size: 1.5rem;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.connected, .notConnected {
|
||||
width: 40vw;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-bottom: 1px solid lightgrey;
|
||||
font-size: 1.5rem;
|
||||
background-color: var(--bg-color);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.connected {
|
||||
border-bottom: 1px solid lightgreen;
|
||||
background-color: lightgreen;
|
||||
}
|
||||
|
||||
.notConnected {
|
||||
border-bottom: 1px solid red;
|
||||
}
|
||||
|
||||
.show-msg-button {
|
||||
border: none;
|
||||
background-color: var(--bg-color);
|
||||
margin-top: 0.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.server-msg {
|
||||
font-family: "Roboto Mono", monospace;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,395 @@
|
||||
<template>
|
||||
<div class="wizard">
|
||||
<div class="wizard-container">
|
||||
<div class="wizard-nav card">
|
||||
<span class="wizard-nav-title">Wizard Pages</span>
|
||||
<span
|
||||
v-for="page in wizardPages"
|
||||
:key="page.title"
|
||||
class="wizard-link"
|
||||
:class="{
|
||||
'active-link': currentStep == page,
|
||||
'finished-page': page.choiceMade,
|
||||
}"
|
||||
>{{ page.link }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="wizard-page">
|
||||
<wizardPage
|
||||
:choices="currentStep.choices"
|
||||
:customComponents="currentStep.customComponents"
|
||||
:pageComponents="currentStep.pageComponents"
|
||||
:title="currentStep.title"
|
||||
:config="wizardConfig"
|
||||
:calibrating="calibrating"
|
||||
:calStatus="calStatus"
|
||||
v-on:choice="choiceHandler"
|
||||
v-on:undo-choice="undoChoice"
|
||||
v-on:page-comp-event="pageEventHandler"
|
||||
/>
|
||||
<div class="wizard-controls">
|
||||
<!-- show breadcrumbs, back, apply, next buttons -->
|
||||
<button class="wizard-button card" @click="back">Back</button>
|
||||
<button class="wizard-button card" @click="finish">Finish</button>
|
||||
<button
|
||||
class="wizard-button card"
|
||||
:class="{ 'next-green': choiceMade }"
|
||||
v-tooltip.right="{
|
||||
content: currentStep.nextTooltip,
|
||||
class: 'tooltip-custom tooltip-other-custom fade-in',
|
||||
delay: 0,
|
||||
visible: !choiceMade && currentStep.nextTooltip != null,
|
||||
}"
|
||||
@click="next"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import configTemplate from "../assets/wizard/configTemplate.json";
|
||||
import wizardPage from "../components/wizard/wizardPage.vue";
|
||||
import odriveEnums from "../assets/odriveEnums.json";
|
||||
import { pages } from "../assets/wizard/wizard.js";
|
||||
import {
|
||||
getVal,
|
||||
putVal,
|
||||
fetchParam,
|
||||
clearErrors,
|
||||
motorCalibration,
|
||||
encoderCalibration
|
||||
} from "../lib/odrive_utils.js";
|
||||
import {wait} from "../lib/utils.js"
|
||||
|
||||
export default {
|
||||
name: "Wizard",
|
||||
components: {
|
||||
wizardPage,
|
||||
},
|
||||
props: [],
|
||||
data: function () {
|
||||
return {
|
||||
currentStep: pages.ODrive,
|
||||
wizardConfig: configTemplate,
|
||||
choiceMade: false,
|
||||
calibrating: false, // for indicating that calibration is in progress
|
||||
calStatus: undefined, // for indicating the status of a calibration attempt
|
||||
odrive: "odrive0.",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
wizardPages() {
|
||||
return pages;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// certain actions, like starting motor or encoder calibration,
|
||||
// require special handling. This function is used for those events
|
||||
async pageEventHandler(e) {
|
||||
this.choiceMade = false;
|
||||
if (e.data == "motor calibration") {
|
||||
let apply = () => {
|
||||
let configStub = {};
|
||||
configStub[e.axis] = {
|
||||
motor: {
|
||||
config: {
|
||||
phase_resistance: getVal(this.odrive + e.axis + ".motor.config.phase_resistance"),
|
||||
phase_inductance: getVal(this.odrive + e.axis + ".motor.config.phase_inductance"),
|
||||
}
|
||||
}
|
||||
}
|
||||
this.choiceHandler({
|
||||
choice: "Motor Calibration",
|
||||
configStub: configStub,
|
||||
hooks: [],
|
||||
});
|
||||
this.choiceMade = true;
|
||||
};
|
||||
let updateCalInfo = () => {
|
||||
// parse error code, update calibration information
|
||||
let motorError = getVal(this.odrive + e.axis + ".motor.error");
|
||||
if (
|
||||
motorError == odriveEnums.MOTOR_ERROR_PHASE_RESISTANCE_OUT_OF_RANGE ||
|
||||
motorError == odriveEnums.MOTOR_ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE
|
||||
) {
|
||||
//we got the expected calibration error
|
||||
console.log("Calibration failed.");
|
||||
this.calStatus = false;
|
||||
}
|
||||
};
|
||||
|
||||
// result is motor.error
|
||||
this.calibrating = true;
|
||||
let result = await motorCalibration(this.odrive, e.axis);
|
||||
this.calibrating = false;
|
||||
fetchParam(this.odrive + e.axis + '.motor.is_calibrated');
|
||||
// no error, we're good!
|
||||
if (result == 0) {
|
||||
wait(250);
|
||||
apply();
|
||||
this.calStatus = true;
|
||||
} else {
|
||||
console.log("motor cal error" + result.data);
|
||||
this.calibrating = false;
|
||||
this.choiceMade = false;
|
||||
// clear errors, display info.
|
||||
updateCalInfo();
|
||||
clearErrors(this.odrive, e.axis);
|
||||
}
|
||||
} else if (e.data == "encoder calibration") {
|
||||
// get old CPR
|
||||
// apply CPR from this.wizardConfig
|
||||
// start calibration
|
||||
// wait for cal to finish
|
||||
// set odrive cpr back to oldVal
|
||||
let oldCPR = getVal(this.odrive + e.axis + ".encoder.config.cpr");
|
||||
console.log("oldCPR = " + oldCPR);
|
||||
console.log("axis = " + e.axis);
|
||||
|
||||
// set up the calib_scan_distance to a value that will work
|
||||
let pp = this.wizardConfig[e.axis].motor.config.pole_pairs;
|
||||
|
||||
// smallest multiple of 4pi that is bigger than pole_pairs * 2pi
|
||||
let scan_distance = pp % 0 ? pp * 2 * Math.PI : (pp + 1) * 2 * Math.PI;
|
||||
putVal(this.odrive + e.axis + ".encoder.config.calib_scan_distance", scan_distance);
|
||||
putVal(
|
||||
this.odrive + e.axis + ".encoder.config.cpr",
|
||||
this.wizardConfig[e.axis].encoder.config.cpr
|
||||
);
|
||||
|
||||
await wait(250);
|
||||
|
||||
this.calibrating = true;
|
||||
let result = await encoderCalibration(this.odrive, e.axis);
|
||||
this.calibrating = false;
|
||||
putVal(this.odrive + e.axis + ".encoder.config.cpr", oldCPR);
|
||||
fetchParam(this.odrive + e.axis + '.encoder.is_ready');
|
||||
if (result == 0){
|
||||
// no encoder error, we're good
|
||||
this.choiceMade = true;
|
||||
this.currentStep.choiceMade = true;
|
||||
this.calStatus = true;
|
||||
}
|
||||
else {
|
||||
clearErrors(this.odrive, e.axis);
|
||||
this.calStatus = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
async choiceHandler(e) {
|
||||
// apply static configStub
|
||||
this.updateConfig(this.wizardConfig, e.configStub);
|
||||
|
||||
// run hooks
|
||||
for (const fn of e.hooks) {
|
||||
this.wizardConfig = fn(this.wizardConfig);
|
||||
}
|
||||
|
||||
// ugly, but a special case.
|
||||
// for motors, wait for calibration to finish before giving the green light unless motor.is_calibrated == true
|
||||
// for encoders, wait for calibration to finish unless encoder.is_ready == true
|
||||
if (
|
||||
this.currentStep == pages.Motor_0 ||
|
||||
this.currentStep == pages.Motor_1
|
||||
) {
|
||||
let axis;
|
||||
if (this.currentStep == pages.Motor_0) axis = "axis0";
|
||||
if (this.currentStep == pages.Motor_1) axis = "axis1";
|
||||
fetchParam(this.odrive + axis + ".motor.is_calibrated");
|
||||
await wait(100);
|
||||
if (getVal(this.odrive + axis + ".motor.is_calibrated") == false) {
|
||||
this.choiceMade = false;
|
||||
}
|
||||
else {
|
||||
this.choiceMade = true;
|
||||
}
|
||||
}
|
||||
else if (
|
||||
this.currentStep == pages.Encoder_0 ||
|
||||
this.currentStep == pages.Encoder_1
|
||||
) {
|
||||
let axis;
|
||||
if (this.currentStep == pages.Encoder_0) axis = "axis0";
|
||||
if (this.currentStep == pages.Encoder_1) axis = "axis1";
|
||||
fetchParam(this.odrive + axis + ".encoder.is_ready");
|
||||
await wait(100);
|
||||
if (getVal(this.odrive + axis + ".encoder.is_ready") == false) {
|
||||
this.choiceMade = false;
|
||||
}
|
||||
else {
|
||||
this.choiceMade = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.choiceMade = true;
|
||||
}
|
||||
this.currentStep.choiceMade = this.choiceMade;
|
||||
console.log(JSON.parse(JSON.stringify(this.wizardConfig)));
|
||||
},
|
||||
undoChoice(e) {
|
||||
this.choiceMade = false;
|
||||
this.currentStep.choiceMade = this.choiceMade;
|
||||
this.nullConfig(this.wizardConfig, e.configStub);
|
||||
console.log(JSON.parse(JSON.stringify(this.wizardConfig)));
|
||||
},
|
||||
updateConfig(config, configStub) {
|
||||
// iterate over keys in configStub
|
||||
if (configStub != null) {
|
||||
Object.keys(configStub).forEach((key) => {
|
||||
if (typeof configStub[key] == "object") {
|
||||
this.updateConfig(config[key], configStub[key]);
|
||||
} else {
|
||||
config[key] = configStub[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
nullConfig(config, configStub) {
|
||||
// iterate over keys in configStub
|
||||
if (configStub != null) {
|
||||
console.log("NullConfig");
|
||||
Object.keys(configStub).forEach((key) => {
|
||||
console.log(key);
|
||||
if (typeof configStub[key] == "object") {
|
||||
this.nullConfig(config[key], configStub[key]);
|
||||
} else {
|
||||
config[key] = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
next() {
|
||||
console.log("next");
|
||||
if (this.choiceMade == true) {
|
||||
this.currentStep = pages[this.currentStep.next];
|
||||
this.choiceMade = false;
|
||||
}
|
||||
this.calStatus = undefined;
|
||||
},
|
||||
finish() {
|
||||
console.log("finish");
|
||||
this.currentStep = pages.End;
|
||||
},
|
||||
back() {
|
||||
console.log("back");
|
||||
this.currentStep = pages[this.currentStep.back];
|
||||
this.choiceMade = false;
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
for (const page of Object.keys(pages)) {
|
||||
pages[page].choiceMade = false;
|
||||
}
|
||||
let erase = (obj) => {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (typeof obj[key] == "object" && obj[key] != null) {
|
||||
erase(obj[key]);
|
||||
} else {
|
||||
obj[key] = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
erase(this.wizardConfig);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wizard {
|
||||
background-color: var(--bg-color);
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
width: 100vw;
|
||||
padding-top: var(--top-height);
|
||||
padding-bottom: var(--bottom-height);
|
||||
}
|
||||
|
||||
.wizard-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.wizard-nav-title {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.wizard-link {
|
||||
margin: 0.2rem;
|
||||
}
|
||||
|
||||
.active-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.wizard-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 95vh;
|
||||
}
|
||||
|
||||
.wizard-page {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.wizard-controls {
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: var(--fg-color);
|
||||
}
|
||||
|
||||
.inactive {
|
||||
background-color: var(--bg-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.wizard-button:active {
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
.next-green {
|
||||
color: green;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.vue-tooltip.tooltip-custom {
|
||||
background-color: lightyellow; /* var(--fg-color); */
|
||||
border-radius: 0px;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.4);
|
||||
font-family: "Roboto", sans-serif;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.vue-tooltip.tooltip-custom .tooltip-arrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vue-tooltip.fade-in {
|
||||
opacity: 0;
|
||||
animation: fadeIn ease 0.25s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.finished-page {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user