Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft PR for manual PID tuning page #161

Draft
wants to merge 29 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
df23c3c
Started docs for manual pid tuning
CoryNessCTR Mar 23, 2024
2ce6d46
Adds WPI's pid-tuning javascript for dynamic tuning
TytanRock Mar 25, 2024
7ca4537
Revamp flywheel to be torque-based
CoryNessCTR Apr 16, 2024
f062dd8
Merge remote-tracking branch 'origin/main' into manual_pid_tuning
CoryNessCTR Apr 16, 2024
b2f330c
Change graph to plot amps instead of Volts
CoryNessCTR Apr 16, 2024
948c054
Address comments
CoryNessCTR Apr 16, 2024
cdf33eb
Remove two .. paths from script sourcing
CoryNessCTR Apr 16, 2024
392c6f6
Change flywheel to be direct-driven by Kraken FOC
CoryNessCTR Apr 16, 2024
aadea05
Fix typo using mNm/A to use Nm/A
CoryNessCTR Apr 16, 2024
4af81b8
Change units to be RPS & reduce delayline to 3 samples
CoryNessCTR Apr 16, 2024
ad8c1fc
Change size of arrows to match magnitude they were at before
CoryNessCTR Apr 16, 2024
5197f5d
Restrict current output based on back EMF
bhall-ctre Apr 16, 2024
4bc6fdd
Fix variable name for input units
bhall-ctre Apr 16, 2024
2526ec5
Fix glitch in flywheel ball angle at high RPS
bhall-ctre Apr 16, 2024
b81bfb4
Expanded manual tuning to include the steps and a walkthrough
CoryNessCTR Jul 1, 2024
9432a47
Adds turret tuning
CoryNessCTR Jul 1, 2024
c507166
Make control name match
CoryNessCTR Jul 1, 2024
713456f
Adds verify step for turret tuning
CoryNessCTR Jul 1, 2024
405b7fd
Add a tuning walkthrough and fix noise
CoryNessCTR Jul 2, 2024
68fa135
Adds arm simulation & some small fixes for the normal guide
CoryNessCTR Jul 8, 2024
abaab9e
Added guide and tuning process example for arm tuning
CoryNessCTR Jul 8, 2024
24adc0b
Fix step numbers
CoryNessCTR Jul 8, 2024
77af767
Added a "Why" for each set of steps, and a short summary of what the
CoryNessCTR Jul 9, 2024
eb4af63
Addresses review comments
CoryNessCTR Jul 9, 2024
b3d3c4d
Merge branch 'main' into manual_pid_tuning
CoryNessCTR Jul 9, 2024
ca88ccc
Adds profiled simulator
CoryNessCTR Jul 12, 2024
9137c8b
Some physics changes and add instructions on tuning
CoryNessCTR Jul 12, 2024
61f094d
Tweaks simulation & adds a "how to" and "why" section for profiled pid
CoryNessCTR Jul 16, 2024
57e836c
Redo flywheel process example with new static friction
CoryNessCTR Jul 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions source/_extensions/controls_js_sim/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from pathlib import Path
from typing import Any, Dict

import os, glob
from jsmin import jsmin
from sphinx.application import Sphinx

# Handle custom javascript
# Groups, sorts, merges, and minifies the JS files assocated with
# the controls documentation

debugJS = False # flip to true to make the output js more readable

# Include Order - specifies which folders to pull in and in which order
# This must be kept in sync with the dependencies within the javascript itself
# Ideally, in the future, the javascript should be allowed to include other .js files
# but for now, we'll just make one mega file
FOLDER_INCS = [
"fastchart",
"utils",
"base",
"plant",
"profiles",
"visualization",
"sim",
".",
]


def mergeAndMinify(out_folder):
if not os.path.isdir(out_folder):
os.makedirs(out_folder)

outputFile = os.path.join(out_folder, "pid-tune.js")

with open(outputFile, "w") as outf:
for folder in FOLDER_INCS:
jsRoot = os.path.dirname(__file__)
# find all js files in the specific folder
inFileNames = glob.glob(os.path.join(jsRoot, folder, "*.js"))

# sort file names alphabetically
# this allows a within-folder sort by number prefix if needed.
inFileNames.sort()

for inFileName in inFileNames:
with open(inFileName, "r") as inf:
if not debugJS:
# Minify each file independently - again, low bar solution for now
minified = jsmin(inf.read())
outf.write(minified)
outf.write("\n")
else:
# Verbose, no minify, and add debug markers.
outf.write("\n\n\n")
outf.write(
"//*******************************************************\n"
)
outf.write(
"//*******************************************************\n"
)
outf.write("//** {}\n".format(inFileName))
outf.write(
"//*******************************************************\n"
)
outf.write(
"//*******************************************************\n"
)
outf.write("\n")
outf.write(inf.read())
outf.write("\n")

return outputFile


def setup(app: Sphinx) -> Dict[str, Any]:
print("Generating and adding controls javascript...")

# Perform controls js setup
static_dir = Path(__file__).parent / "_static"

# everything written to this new static folder in this `setup` will be copied to the build static folder as is
app.connect(
"builder-inited",
(lambda app: app.config.html_static_path.append(static_dir.as_posix())),
)

# Generate merged/minified PID tuning source
mergeAndMinify(static_dir)

# Add interactive PID tuning
# app.add_js_file("pid-tune.js")
# app.add_css_file("pid-tune.css")

return {
"parallel_read_safe": True,
"parallel_write_safe": True,
}
2 changes: 2 additions & 0 deletions source/_extensions/controls_js_sim/_static/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto-Generated files
pid-tune.js
32 changes: 32 additions & 0 deletions source/_extensions/controls_js_sim/_static/pid-tune.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.flex-grid {
display: flex;
margin: 5px;

}

@media (max-width: 800px) {
.flex-grid {
display: block;
margin: 5px;

}
}

.col {
flex: 1;
min-height: 300px;
max-height: 300px;
margin: 5px;
}

controlTable {
margin: 5px;
}

.viz-div {
margin:10px;
border-style: solid;
border-width: 3px;

}

135 changes: 135 additions & 0 deletions source/_extensions/controls_js_sim/base/base-sim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
class BaseSim {
constructor(divIdPrefix, processVariableUnits) {
this.divIdPrefix = divIdPrefix;
this.speedGraph = null;
this.voltsGraph = null;
this.containerDiv = document.getElementById(divIdPrefix + "_container");
let plotDrawDivVals = document.getElementById(divIdPrefix + "_plotVals");
let plotDrawDivAmps = document.getElementById(divIdPrefix + "_plotAmps");

// Set up process variable chart - two signals indicating "actual" and "desired"
// values for the simulation
this.procVarPlot = new Plot(plotDrawDivVals);
this.procVarActualSignal = new Signal("Actual", processVariableUnits);
this.procVarDesiredSignal = new Signal("Desired", processVariableUnits);
this.procVarPlot.addSignal(this.procVarActualSignal, "purple");
this.procVarPlot.addSignal(this.procVarDesiredSignal, "red");
this.procVarPlot.setNumValueAxes(1);


// Set up "volts" chart - indicates control effort applied to the system
this.ampsPlot = new Plot(plotDrawDivAmps);
this.ampsSignal = new Signal("Control Effort", "A");
this.ampsPlot.addSignal(this.ampsSignal, "green");
this.ampsPlot.setNumValueAxes(1);

this.visualizationDrawDiv = document.getElementById(divIdPrefix + "_viz");

this.animationStartTimeS = null;
window.requestAnimationFrame((t) => this.animate(t));

this.controlDrawDiv = document.getElementById(divIdPrefix + "_ctrls");

this.animationReset = true;

this.simRunning = true;

this.curSimTimeS = 0.0;

//Register mouseover & scrollwheel callbacks.
// This is still tentative, as these won't work if the sim is running
this.procVarPlot.chart.mouseoverAtTimeCallback = this.onChartMouseOver.bind(this);
this.ampsPlot.chart.mouseoverAtTimeCallback = this.onChartMouseOver.bind(this);
this.procVarPlot.chart.zoomRangeUpdateCallback = this.onChartZoomAction.bind(this);
this.ampsPlot.chart.zoomRangeUpdateCallback = this.onChartZoomAction.bind(this);


}

resetData() {

}

begin() {
this.resetCustom(); //Callback to fixed point in specific sim classes
this.procVarActualSignal.clearValues();
this.procVarDesiredSignal.clearValues();
this.ampsSignal.clearValues();
this.curSimTimeS = 0.0;
this.animationReset = true;
this.simRunning = true;
}

end() {
this.simRunning = false;
}

iterate(){
this.iterateCustom(); //callback to fixed point in the specific sim class
}

animate(currentTimeMs) {

//Main animation loop
let currentTimeS = currentTimeMs / 1000.0;

if (this.animationReset) {
//Sim has restarted, reset things to nominal
this.animationStartTimeS = currentTimeS;
this.animationReset = false;
this.procVarPlot.setDrawRange(0, this.simDurationS);
this.ampsPlot.setDrawRange(0, this.simDurationS);
}

if(this.simRunning){
//Calculate the current time index, looping once we get past the end of the simulated duration
let animationTimeS = (currentTimeS - this.animationStartTimeS);

//run the simulation up to the current animation time
while(this.curSimTimeS <= animationTimeS && this.simRunning){
this.iterate();
}

this.procVarPlot.setCursorPos(animationTimeS);
this.ampsPlot.setCursorPos(animationTimeS);
}

//Redraw only if visible.
if(this.isInViewport()){
this.visualization.drawDynamic();
this.ampsPlot.drawDataToChart();
this.procVarPlot.drawDataToChart();
}


// Tell the browser to animate another frame for us later
window.requestAnimationFrame((t) => this.animate(t));
}


isInViewport() {
const rect = this.containerDiv.getBoundingClientRect();
var topIn = rect.top >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight);
var bottomIn = rect.bottom >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)
return topIn || bottomIn;
}

///////////////////////////
// Mouse Events

onChartMouseOver(timeAtMouse){
if(!this.simRunning){
this.procVarPlot.setCursorPos(timeAtMouse);
this.ampsPlot.setCursorPos(timeAtMouse);
}
}


onChartZoomAction(startTime, endTime){
if(!this.simRunning){
this.procVarPlot.setDrawRange(startTime, endTime);
this.ampsPlot.setDrawRange(startTime, endTime);
}
}

}
73 changes: 73 additions & 0 deletions source/_extensions/controls_js_sim/base/base-visualization.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
class BaseVisualization {
constructor(div_in) {
this.drawDiv = div_in;

div_in.style.position = "relative";

this.animatedCanvas = document.createElement("canvas");
this.staticCanvas = document.createElement("canvas");

this.staticCanvas.style.position = "absolute";
this.staticCanvas.style.top = "0px";
this.staticCanvas.style.left = "0px";

this.animatedCanvas.style.position = "absolute";
this.animatedCanvas.style.top = "0px";
this.animatedCanvas.style.left = "0px";

this.teamNumber = Math.floor(Math.random() * 9999).toFixed(0);

this.animatedCanvasContext = this.animatedCanvas.getContext("2d");
this.staticCanvasContext = this.staticCanvas.getContext("2d");

div_in.appendChild(this.staticCanvas);
div_in.appendChild(this.animatedCanvas);

this.updateSize();

window.addEventListener("resize", this.updateSize.bind(this));
window.addEventListener("load", this.updateSize.bind(this));
}

updateSize() {
this.width = Math.min(this.drawDiv.offsetWidth, 500);
this.height = Math.max(this.drawDiv.offsetHeight, 100);
this.staticCanvas.width = this.width;
this.staticCanvas.height = this.height;
this.animatedCanvas.width = this.width;
this.animatedCanvas.height = this.height;
this.drawStatic();
}

setCurTime(timeS) {
this.timeS = timeS;
}

setCurPos(positionRad) {
this.positionRad = positionRad;
}

setCurSetpoint(setpoint) {
this.setpoint = setpoint;
}

setCurOutput(output) {
this.output = output;
}

setCurControlEffort(controlEffortVolts) {
this.controlEffortVolts = controlEffortVolts;
}

drawStatic() {
this.staticCanvasContext.clearRect(0, 0, this.width, this.height);

this.drawStaticCustom();
}

drawDynamic() {
this.animatedCanvasContext.clearRect(0, 0, this.width, this.height);

this.drawDynamicCustom();
}
}
22 changes: 22 additions & 0 deletions source/_extensions/controls_js_sim/base/z_base-profiled-sim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class BaseProfiledSim extends BaseSim {
constructor(divIdPrefix, processVariableUnits) {
super(divIdPrefix, processVariableUnits);

this.procVelocity = new Signal("Velocity", processVariableUnits + "/s");
this.measVelocity = new Signal("MeasVelocity", processVariableUnits + "/s");
this.procAcceleration = new Signal("Acceleration", processVariableUnits + "/s²");
this.measAcceleration = new Signal("MeasAcceleration", processVariableUnits + "/s²");
this.procVarPlot.addSignal(this.procVelocity, "blue");
this.procVarPlot.addSignal(this.measVelocity, "brown");
this.procVarPlot.addSignal(this.procAcceleration, "yellow");
this.procVarPlot.addSignal(this.measAcceleration, "orange");
this.procVarPlot.setNumValueAxes(2);
}
begin() {
super.begin();
this.procVelocity.clearValues();
this.measVelocity.clearValues();
this.procAcceleration.clearValues();
this.measAcceleration.clearValues();
}
}
14 changes: 14 additions & 0 deletions source/_extensions/controls_js_sim/fastchart/0-sample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/////////////////////////////////////////////////////////////////////////
// Sample - one timestamped value of data. A signal is made of many
// samples.
/////////////////////////////////////////////////////////////////////////


class Sample {

constructor(time_in, value_in) {
this.time = time_in;
this.value = value_in;
}

}
Loading
Loading