diff --git a/docs/reStructuredText/_build/doctrees/analog-input.doctree b/docs/reStructuredText/_build/doctrees/analog-input.doctree new file mode 100644 index 00000000..800915bb Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/analog-input.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/ascii-protocol.doctree b/docs/reStructuredText/_build/doctrees/ascii-protocol.doctree new file mode 100644 index 00000000..bfc7cf39 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/ascii-protocol.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/can-guide.doctree b/docs/reStructuredText/_build/doctrees/can-guide.doctree new file mode 100644 index 00000000..306443a5 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/can-guide.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/can-protocol.doctree b/docs/reStructuredText/_build/doctrees/can-protocol.doctree new file mode 100644 index 00000000..99316305 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/can-protocol.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/commands.doctree b/docs/reStructuredText/_build/doctrees/commands.doctree new file mode 100644 index 00000000..ac07ce7a Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/commands.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/configuring-eclipse.doctree b/docs/reStructuredText/_build/doctrees/configuring-eclipse.doctree new file mode 100644 index 00000000..0ceae3a4 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/configuring-eclipse.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/configuring-vscode.doctree b/docs/reStructuredText/_build/doctrees/configuring-vscode.doctree new file mode 100644 index 00000000..9c991d32 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/configuring-vscode.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/control-modes.doctree b/docs/reStructuredText/_build/doctrees/control-modes.doctree new file mode 100644 index 00000000..53348106 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/control-modes.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/control.doctree b/docs/reStructuredText/_build/doctrees/control.doctree new file mode 100644 index 00000000..46857c12 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/control.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/developer-guide.doctree b/docs/reStructuredText/_build/doctrees/developer-guide.doctree new file mode 100644 index 00000000..18fbee18 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/developer-guide.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/encoders.doctree b/docs/reStructuredText/_build/doctrees/encoders.doctree new file mode 100644 index 00000000..9b5a0fb1 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/encoders.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/endstops.doctree b/docs/reStructuredText/_build/doctrees/endstops.doctree new file mode 100644 index 00000000..5ee7f2e0 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/endstops.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/environment.pickle b/docs/reStructuredText/_build/doctrees/environment.pickle new file mode 100644 index 00000000..4bb15655 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/environment.pickle differ diff --git a/docs/reStructuredText/_build/doctrees/fibre_types/com_odriverobotics_ODrive.doctree b/docs/reStructuredText/_build/doctrees/fibre_types/com_odriverobotics_ODrive.doctree new file mode 100644 index 00000000..2325f1ad Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/fibre_types/com_odriverobotics_ODrive.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/getting-started.doctree b/docs/reStructuredText/_build/doctrees/getting-started.doctree new file mode 100644 index 00000000..373446da Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/getting-started.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/ground-loops.doctree b/docs/reStructuredText/_build/doctrees/ground-loops.doctree new file mode 100644 index 00000000..038980be Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/ground-loops.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/hoverboard.doctree b/docs/reStructuredText/_build/doctrees/hoverboard.doctree new file mode 100644 index 00000000..030214b7 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/hoverboard.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/index.doctree b/docs/reStructuredText/_build/doctrees/index.doctree new file mode 100644 index 00000000..1b343e2e Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/index.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/migration.doctree b/docs/reStructuredText/_build/doctrees/migration.doctree new file mode 100644 index 00000000..26720b78 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/migration.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/native-protocol.doctree b/docs/reStructuredText/_build/doctrees/native-protocol.doctree new file mode 100644 index 00000000..dae0bf38 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/native-protocol.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/odrivetool.doctree b/docs/reStructuredText/_build/doctrees/odrivetool.doctree new file mode 100644 index 00000000..95977184 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/odrivetool.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/pinout.doctree b/docs/reStructuredText/_build/doctrees/pinout.doctree new file mode 100644 index 00000000..8e088133 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/pinout.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/protocol.doctree b/docs/reStructuredText/_build/doctrees/protocol.doctree new file mode 100644 index 00000000..218407d2 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/protocol.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/rc-pwm.doctree b/docs/reStructuredText/_build/doctrees/rc-pwm.doctree new file mode 100644 index 00000000..227a51a1 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/rc-pwm.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/specifications.doctree b/docs/reStructuredText/_build/doctrees/specifications.doctree new file mode 100644 index 00000000..dc5b846f Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/specifications.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/step-direction.doctree b/docs/reStructuredText/_build/doctrees/step-direction.doctree new file mode 100644 index 00000000..8e45653a Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/step-direction.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/testing.doctree b/docs/reStructuredText/_build/doctrees/testing.doctree new file mode 100644 index 00000000..0fd05cc3 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/testing.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/thermistors.doctree b/docs/reStructuredText/_build/doctrees/thermistors.doctree new file mode 100644 index 00000000..fc3aad87 Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/thermistors.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/troubleshooting.doctree b/docs/reStructuredText/_build/doctrees/troubleshooting.doctree new file mode 100644 index 00000000..94fe55ef Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/troubleshooting.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/uart.doctree b/docs/reStructuredText/_build/doctrees/uart.doctree new file mode 100644 index 00000000..106dbf4d Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/uart.doctree differ diff --git a/docs/reStructuredText/_build/doctrees/usb.doctree b/docs/reStructuredText/_build/doctrees/usb.doctree new file mode 100644 index 00000000..5e974c5e Binary files /dev/null and b/docs/reStructuredText/_build/doctrees/usb.doctree differ diff --git a/docs/reStructuredText/_build/html/.buildinfo b/docs/reStructuredText/_build/html/.buildinfo new file mode 100644 index 00000000..88149904 --- /dev/null +++ b/docs/reStructuredText/_build/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 8b85c9846c0b4107ef4d52be5bee233a +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/reStructuredText/_build/html/_images/CAN_Bus_Drawing.png b/docs/reStructuredText/_build/html/_images/CAN_Bus_Drawing.png new file mode 100644 index 00000000..8be3fc6e Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/CAN_Bus_Drawing.png differ diff --git a/docs/reStructuredText/_build/html/_images/CodeAsMakefile.png b/docs/reStructuredText/_build/html/_images/CodeAsMakefile.png new file mode 100644 index 00000000..5d846bda Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/CodeAsMakefile.png differ diff --git a/docs/reStructuredText/_build/html/_images/Endstop_configuration.png b/docs/reStructuredText/_build/html/_images/Endstop_configuration.png new file mode 100644 index 00000000..56a618ff Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/Endstop_configuration.png differ diff --git a/docs/reStructuredText/_build/html/_images/ImportLaunch.png b/docs/reStructuredText/_build/html/_images/ImportLaunch.png new file mode 100644 index 00000000..3270ee15 Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/ImportLaunch.png differ diff --git a/docs/reStructuredText/_build/html/_images/LaunchConfigFilter.png b/docs/reStructuredText/_build/html/_images/LaunchConfigFilter.png new file mode 100644 index 00000000..4a43bcfc Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/LaunchConfigFilter.png differ diff --git a/docs/reStructuredText/_build/html/_images/ODriveBasicWiring.png b/docs/reStructuredText/_build/html/_images/ODriveBasicWiring.png new file mode 100644 index 00000000..4b17bd2b Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/ODriveBasicWiring.png differ diff --git a/docs/reStructuredText/_build/html/_images/TrapTrajPosVel.PNG b/docs/reStructuredText/_build/html/_images/TrapTrajPosVel.PNG new file mode 100644 index 00000000..59c5bbe9 Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/TrapTrajPosVel.PNG differ diff --git a/docs/reStructuredText/_build/html/_images/controller_with_ff.png b/docs/reStructuredText/_build/html/_images/controller_with_ff.png new file mode 100644 index 00000000..212b1f26 Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/controller_with_ff.png differ diff --git a/docs/reStructuredText/_build/html/_images/endstop_figure.png b/docs/reStructuredText/_build/html/_images/endstop_figure.png new file mode 100644 index 00000000..aaec20dd Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/endstop_figure.png differ diff --git a/docs/reStructuredText/_build/html/_images/ground_loop_bad.png b/docs/reStructuredText/_build/html/_images/ground_loop_bad.png new file mode 100644 index 00000000..a88bba3d Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/ground_loop_bad.png differ diff --git a/docs/reStructuredText/_build/html/_images/ground_loop_fix.png b/docs/reStructuredText/_build/html/_images/ground_loop_fix.png new file mode 100644 index 00000000..26619383 Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/ground_loop_fix.png differ diff --git a/docs/reStructuredText/_build/html/_images/liveplotter-iq-omega.png b/docs/reStructuredText/_build/html/_images/liveplotter-iq-omega.png new file mode 100644 index 00000000..036ca262 Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/liveplotter-iq-omega.png differ diff --git a/docs/reStructuredText/_build/html/_images/liveplotter-pos-estimate.png b/docs/reStructuredText/_build/html/_images/liveplotter-pos-estimate.png new file mode 100644 index 00000000..4289544d Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/liveplotter-pos-estimate.png differ diff --git a/docs/reStructuredText/_build/html/_images/mech_dimensions.png b/docs/reStructuredText/_build/html/_images/mech_dimensions.png new file mode 100644 index 00000000..4ae33885 Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/mech_dimensions.png differ diff --git a/docs/reStructuredText/_build/html/_images/secondOrderResponse.PNG b/docs/reStructuredText/_build/html/_images/secondOrderResponse.PNG new file mode 100644 index 00000000..7199b37f Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/secondOrderResponse.PNG differ diff --git a/docs/reStructuredText/_build/html/_images/stlink-wiring.jpg b/docs/reStructuredText/_build/html/_images/stlink-wiring.jpg new file mode 100644 index 00000000..368524e9 Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/stlink-wiring.jpg differ diff --git a/docs/reStructuredText/_build/html/_images/thermistor-voltage-divider.png b/docs/reStructuredText/_build/html/_images/thermistor-voltage-divider.png new file mode 100644 index 00000000..e01c7cf9 Binary files /dev/null and b/docs/reStructuredText/_build/html/_images/thermistor-voltage-divider.png differ diff --git a/docs/reStructuredText/_build/html/_panels_static/panels-bootstrap.5fd3999ee7762ccc51105388f4a9d115.css b/docs/reStructuredText/_build/html/_panels_static/panels-bootstrap.5fd3999ee7762ccc51105388f4a9d115.css new file mode 100644 index 00000000..1b057df2 --- /dev/null +++ b/docs/reStructuredText/_build/html/_panels_static/panels-bootstrap.5fd3999ee7762ccc51105388f4a9d115.css @@ -0,0 +1 @@ +.badge{border-radius:.25rem;display:inline-block;font-size:75%;font-weight:700;line-height:1;padding:.25em .4em;text-align:center;vertical-align:baseline;white-space:nowrap}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{border-radius:10rem;padding-left:.6em;padding-right:.6em}.badge-primary{background-color:#007bff;color:#fff}.badge-primary[href]:focus,.badge-primary[href]:hover{background-color:#0062cc;color:#fff;text-decoration:none}.badge-secondary{background-color:#6c757d;color:#fff}.badge-secondary[href]:focus,.badge-secondary[href]:hover{background-color:#545b62;color:#fff;text-decoration:none}.badge-success{background-color:#28a745;color:#fff}.badge-success[href]:focus,.badge-success[href]:hover{background-color:#1e7e34;color:#fff;text-decoration:none}.badge-info{background-color:#17a2b8;color:#fff}.badge-info[href]:focus,.badge-info[href]:hover{background-color:#117a8b;color:#fff;text-decoration:none}.badge-warning{background-color:#ffc107;color:#212529}.badge-warning[href]:focus,.badge-warning[href]:hover{background-color:#d39e00;color:#212529;text-decoration:none}.badge-danger{background-color:#dc3545;color:#fff}.badge-danger[href]:focus,.badge-danger[href]:hover{background-color:#bd2130;color:#fff;text-decoration:none}.badge-light{background-color:#f8f9fa;color:#212529}.badge-light[href]:focus,.badge-light[href]:hover{background-color:#dae0e5;color:#212529;text-decoration:none}.badge-dark{background-color:#343a40;color:#fff}.badge-dark[href]:focus,.badge-dark[href]:hover{background-color:#1d2124;color:#fff;text-decoration:none}.border-0{border:0 !important}.border-top-0{border-top:0 !important}.border-right-0{border-right:0 !important}.border-bottom-0{border-bottom:0 !important}.border-left-0{border-left:0 !important}.p-0{padding:0 !important}.pt-0,.py-0{padding-top:0 !important}.pr-0,.px-0{padding-right:0 !important}.pb-0,.py-0{padding-bottom:0 !important}.pl-0,.px-0{padding-left:0 !important}.p-1{padding:.25rem !important}.pt-1,.py-1{padding-top:.25rem !important}.pr-1,.px-1{padding-right:.25rem !important}.pb-1,.py-1{padding-bottom:.25rem !important}.pl-1,.px-1{padding-left:.25rem !important}.p-2{padding:.5rem !important}.pt-2,.py-2{padding-top:.5rem !important}.pr-2,.px-2{padding-right:.5rem !important}.pb-2,.py-2{padding-bottom:.5rem !important}.pl-2,.px-2{padding-left:.5rem !important}.p-3{padding:1rem !important}.pt-3,.py-3{padding-top:1rem !important}.pr-3,.px-3{padding-right:1rem !important}.pb-3,.py-3{padding-bottom:1rem !important}.pl-3,.px-3{padding-left:1rem !important}.p-4{padding:1.5rem !important}.pt-4,.py-4{padding-top:1.5rem !important}.pr-4,.px-4{padding-right:1.5rem !important}.pb-4,.py-4{padding-bottom:1.5rem !important}.pl-4,.px-4{padding-left:1.5rem !important}.p-5{padding:3rem !important}.pt-5,.py-5{padding-top:3rem !important}.pr-5,.px-5{padding-right:3rem !important}.pb-5,.py-5{padding-bottom:3rem !important}.pl-5,.px-5{padding-left:3rem !important}.m-0{margin:0 !important}.mt-0,.my-0{margin-top:0 !important}.mr-0,.mx-0{margin-right:0 !important}.mb-0,.my-0{margin-bottom:0 !important}.ml-0,.mx-0{margin-left:0 !important}.m-1{margin:.25rem !important}.mt-1,.my-1{margin-top:.25rem !important}.mr-1,.mx-1{margin-right:.25rem !important}.mb-1,.my-1{margin-bottom:.25rem !important}.ml-1,.mx-1{margin-left:.25rem !important}.m-2{margin:.5rem !important}.mt-2,.my-2{margin-top:.5rem !important}.mr-2,.mx-2{margin-right:.5rem !important}.mb-2,.my-2{margin-bottom:.5rem !important}.ml-2,.mx-2{margin-left:.5rem !important}.m-3{margin:1rem !important}.mt-3,.my-3{margin-top:1rem !important}.mr-3,.mx-3{margin-right:1rem !important}.mb-3,.my-3{margin-bottom:1rem !important}.ml-3,.mx-3{margin-left:1rem !important}.m-4{margin:1.5rem !important}.mt-4,.my-4{margin-top:1.5rem !important}.mr-4,.mx-4{margin-right:1.5rem !important}.mb-4,.my-4{margin-bottom:1.5rem !important}.ml-4,.mx-4{margin-left:1.5rem !important}.m-5{margin:3rem !important}.mt-5,.my-5{margin-top:3rem !important}.mr-5,.mx-5{margin-right:3rem !important}.mb-5,.my-5{margin-bottom:3rem !important}.ml-5,.mx-5{margin-left:3rem !important}.btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;color:#212529;cursor:pointer;display:inline-block;font-size:1rem;font-weight:400;line-height:1.5;padding:.375rem .75rem;text-align:center;transition:color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none;vertical-align:middle}.btn:hover{color:#212529;text-decoration:none}.btn:visited{color:#212529}.btn.focus,.btn:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,0.25);outline:0}.btn.disabled,.btn:disabled{opacity:.65}@media (prefers-reduced-motion: reduce){.btn{transition:none}}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{background-color:#007bff;border-color:#007bff;color:#fff}.btn-primary:visited{color:#fff}.btn-primary:hover{background-color:#0069d9;border-color:#0062cc;color:#fff}.btn-primary.focus,.btn-primary:focus{background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(0,123,255,0.5);color:#fff}.btn-primary.disabled,.btn-primary:disabled{background-color:#007bff;border-color:#007bff;color:#fff}.btn-primary.active:not(:disabled):not(.disabled),.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{background-color:#0062cc;border-color:#005cbf;color:#fff}.btn-primary.active:not(:disabled):not(.disabled):focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,0.5)}.btn-secondary{background-color:#6c757d;border-color:#6c757d;color:#fff}.btn-secondary:visited{color:#fff}.btn-secondary:hover{background-color:#5a6268;border-color:#545b62;color:#fff}.btn-secondary.focus,.btn-secondary:focus{background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(108,117,125,0.5);color:#fff}.btn-secondary.disabled,.btn-secondary:disabled{background-color:#6c757d;border-color:#6c757d;color:#fff}.btn-secondary.active:not(:disabled):not(.disabled),.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{background-color:#545b62;border-color:#4e555b;color:#fff}.btn-secondary.active:not(:disabled):not(.disabled):focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,0.5)}.btn-success{background-color:#28a745;border-color:#28a745;color:#fff}.btn-success:visited{color:#fff}.btn-success:hover{background-color:#218838;border-color:#1e7e34;color:#fff}.btn-success.focus,.btn-success:focus{background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(40,167,69,0.5);color:#fff}.btn-success.disabled,.btn-success:disabled{background-color:#28a745;border-color:#28a745;color:#fff}.btn-success.active:not(:disabled):not(.disabled),.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{background-color:#1e7e34;border-color:#1c7430;color:#fff}.btn-success.active:not(:disabled):not(.disabled):focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,0.5)}.btn-info{background-color:#17a2b8;border-color:#17a2b8;color:#fff}.btn-info:visited{color:#fff}.btn-info:hover{background-color:#138496;border-color:#117a8b;color:#fff}.btn-info.focus,.btn-info:focus{background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(23,162,184,0.5);color:#fff}.btn-info.disabled,.btn-info:disabled{background-color:#17a2b8;border-color:#17a2b8;color:#fff}.btn-info.active:not(:disabled):not(.disabled),.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{background-color:#117a8b;border-color:#10707f;color:#fff}.btn-info.active:not(:disabled):not(.disabled):focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,0.5)}.btn-warning{background-color:#ffc107;border-color:#ffc107;color:#212529}.btn-warning:visited{color:#212529}.btn-warning:hover{background-color:#e0a800;border-color:#d39e00;color:#212529}.btn-warning.focus,.btn-warning:focus{background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(255,193,7,0.5);color:#212529}.btn-warning.disabled,.btn-warning:disabled{background-color:#ffc107;border-color:#ffc107;color:#212529}.btn-warning.active:not(:disabled):not(.disabled),.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{background-color:#d39e00;border-color:#c69500;color:#212529}.btn-warning.active:not(:disabled):not(.disabled):focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,0.5)}.btn-danger{background-color:#dc3545;border-color:#dc3545;color:#fff}.btn-danger:visited{color:#fff}.btn-danger:hover{background-color:#c82333;border-color:#bd2130;color:#fff}.btn-danger.focus,.btn-danger:focus{background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(220,53,69,0.5);color:#fff}.btn-danger.disabled,.btn-danger:disabled{background-color:#dc3545;border-color:#dc3545;color:#fff}.btn-danger.active:not(:disabled):not(.disabled),.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{background-color:#bd2130;border-color:#b21f2d;color:#fff}.btn-danger.active:not(:disabled):not(.disabled):focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,0.5)}.btn-light{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-light:visited{color:#212529}.btn-light:hover{background-color:#e2e6ea;border-color:#dae0e5;color:#212529}.btn-light.focus,.btn-light:focus{background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(248,249,250,0.5);color:#212529}.btn-light.disabled,.btn-light:disabled{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-light.active:not(:disabled):not(.disabled),.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{background-color:#dae0e5;border-color:#d3d9df;color:#212529}.btn-light.active:not(:disabled):not(.disabled):focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,0.5)}.btn-dark{background-color:#343a40;border-color:#343a40;color:#fff}.btn-dark:visited{color:#fff}.btn-dark:hover{background-color:#23272b;border-color:#1d2124;color:#fff}.btn-dark.focus,.btn-dark:focus{background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(52,58,64,0.5);color:#fff}.btn-dark.disabled,.btn-dark:disabled{background-color:#343a40;border-color:#343a40;color:#fff}.btn-dark.active:not(:disabled):not(.disabled),.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{background-color:#1d2124;border-color:#171a1d;color:#fff}.btn-dark.active:not(:disabled):not(.disabled):focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,0.5)}.btn-outline-primary{border-color:#007bff;color:#007bff}.btn-outline-primary:visited{color:#007bff}.btn-outline-primary:hover{background-color:#007bff;border-color:#007bff;color:#fff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,0.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{background-color:transparent;color:#007bff}.btn-outline-primary.active:not(:disabled):not(.disabled),.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{background-color:#007bff;border-color:#007bff;color:#fff}.btn-outline-primary.active:not(:disabled):not(.disabled):focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,0.5)}.btn-outline-secondary{border-color:#6c757d;color:#6c757d}.btn-outline-secondary:visited{color:#6c757d}.btn-outline-secondary:hover{background-color:#6c757d;border-color:#6c757d;color:#fff}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,0.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{background-color:transparent;color:#6c757d}.btn-outline-secondary.active:not(:disabled):not(.disabled),.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{background-color:#6c757d;border-color:#6c757d;color:#fff}.btn-outline-secondary.active:not(:disabled):not(.disabled):focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,0.5)}.btn-outline-success{border-color:#28a745;color:#28a745}.btn-outline-success:visited{color:#28a745}.btn-outline-success:hover{background-color:#28a745;border-color:#28a745;color:#fff}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,0.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{background-color:transparent;color:#28a745}.btn-outline-success.active:not(:disabled):not(.disabled),.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{background-color:#28a745;border-color:#28a745;color:#fff}.btn-outline-success.active:not(:disabled):not(.disabled):focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,0.5)}.btn-outline-info{border-color:#17a2b8;color:#17a2b8}.btn-outline-info:visited{color:#17a2b8}.btn-outline-info:hover{background-color:#17a2b8;border-color:#17a2b8;color:#fff}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,0.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{background-color:transparent;color:#17a2b8}.btn-outline-info.active:not(:disabled):not(.disabled),.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{background-color:#17a2b8;border-color:#17a2b8;color:#fff}.btn-outline-info.active:not(:disabled):not(.disabled):focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,0.5)}.btn-outline-warning{border-color:#ffc107;color:#ffc107}.btn-outline-warning:visited{color:#ffc107}.btn-outline-warning:hover{background-color:#ffc107;border-color:#ffc107;color:#212529}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,0.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{background-color:transparent;color:#ffc107}.btn-outline-warning.active:not(:disabled):not(.disabled),.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{background-color:#ffc107;border-color:#ffc107;color:#212529}.btn-outline-warning.active:not(:disabled):not(.disabled):focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,0.5)}.btn-outline-danger{border-color:#dc3545;color:#dc3545}.btn-outline-danger:visited{color:#dc3545}.btn-outline-danger:hover{background-color:#dc3545;border-color:#dc3545;color:#fff}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,0.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{background-color:transparent;color:#dc3545}.btn-outline-danger.active:not(:disabled):not(.disabled),.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{background-color:#dc3545;border-color:#dc3545;color:#fff}.btn-outline-danger.active:not(:disabled):not(.disabled):focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,0.5)}.btn-outline-light{border-color:#f8f9fa;color:#f8f9fa}.btn-outline-light:visited{color:#f8f9fa}.btn-outline-light:hover{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,0.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{background-color:transparent;color:#f8f9fa}.btn-outline-light.active:not(:disabled):not(.disabled),.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-outline-light.active:not(:disabled):not(.disabled):focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,0.5)}.btn-outline-dark{border-color:#343a40;color:#343a40}.btn-outline-dark:visited{color:#343a40}.btn-outline-dark:hover{background-color:#343a40;border-color:#343a40;color:#fff}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,0.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{background-color:transparent;color:#343a40}.btn-outline-dark.active:not(:disabled):not(.disabled),.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{background-color:#343a40;border-color:#343a40;color:#fff}.btn-outline-dark.active:not(:disabled):not(.disabled):focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,0.5)}.btn-link{color:#007bff;font-weight:400;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{box-shadow:none;text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{border-radius:.3rem;font-size:1.25rem;line-height:1.5;padding:.5rem 1rem}.btn-group-sm>.btn,.btn-sm{border-radius:.2rem;font-size:.875rem;line-height:1.5;padding:.25rem .5rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input.btn-block[type=button],input.btn-block[type=reset],input.btn-block[type=submit]{width:100%}.stretched-link::after{background-color:rgba(0,0,0,0);bottom:0;content:'';left:0;pointer-events:auto;position:absolute;right:0;top:0;z-index:1}.text-wrap{white-space:normal !important}.card{background-clip:border-box;background-color:#fff;border:1px solid rgba(0,0,0,0.125);border-radius:.25rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;position:relative;word-wrap:break-word}.card>hr{margin-left:0;margin-right:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-bottom:0;margin-top:-.375rem}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{background-color:rgba(0,0,0,0.03);border-bottom:1px solid rgba(0,0,0,0.125);margin-bottom:0;padding:.75rem 1.25rem}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{background-color:rgba(0,0,0,0.03);border-top:1px solid rgba(0,0,0,0.125);padding:.75rem 1.25rem}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{border-bottom:0;margin-bottom:-.75rem;margin-left:-.625rem;margin-right:-.625rem}.card-header-pills{margin-left:-.625rem;margin-right:-.625rem}.card-img-overlay{bottom:0;left:0;padding:1.25rem;position:absolute;right:0;top:0}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-left-radius:calc(.25rem - 1px);border-bottom-right-radius:calc(.25rem - 1px)}.w-100{width:100% !important}.shadow{box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15) !important}.bg-primary{background-color:#007bff !important}button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc !important}a.bg-primary:focus,a.bg-primary:hover{background-color:#0062cc !important}a.text-primary:focus,a.text-primary:hover{color:#121416 !important}.bg-secondary{background-color:#6c757d !important}button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62 !important}a.bg-secondary:focus,a.bg-secondary:hover{background-color:#545b62 !important}a.text-secondary:focus,a.text-secondary:hover{color:#121416 !important}.bg-success{background-color:#28a745 !important}button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34 !important}a.bg-success:focus,a.bg-success:hover{background-color:#1e7e34 !important}a.text-success:focus,a.text-success:hover{color:#121416 !important}.bg-info{background-color:#17a2b8 !important}button.bg-info:focus,button.bg-info:hover{background-color:#117a8b !important}a.bg-info:focus,a.bg-info:hover{background-color:#117a8b !important}a.text-info:focus,a.text-info:hover{color:#121416 !important}.bg-warning{background-color:#ffc107 !important}button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00 !important}a.bg-warning:focus,a.bg-warning:hover{background-color:#d39e00 !important}a.text-warning:focus,a.text-warning:hover{color:#121416 !important}.bg-danger{background-color:#dc3545 !important}button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130 !important}a.bg-danger:focus,a.bg-danger:hover{background-color:#bd2130 !important}a.text-danger:focus,a.text-danger:hover{color:#121416 !important}.bg-light{background-color:#f8f9fa !important}button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5 !important}a.bg-light:focus,a.bg-light:hover{background-color:#dae0e5 !important}a.text-light:focus,a.text-light:hover{color:#121416 !important}.bg-dark{background-color:#343a40 !important}button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124 !important}a.bg-dark:focus,a.bg-dark:hover{background-color:#1d2124 !important}a.text-dark:focus,a.text-dark:hover{color:#121416 !important}.bg-white{background-color:#fff !important}button.bg-white:focus,button.bg-white:hover{background-color:#e6e6e6 !important}a.bg-white:focus,a.bg-white:hover{background-color:#e6e6e6 !important}a.text-white:focus,a.text-white:hover{color:#121416 !important}.text-primary{color:#007bff !important}.text-secondary{color:#6c757d !important}.text-success{color:#28a745 !important}.text-info{color:#17a2b8 !important}.text-warning{color:#ffc107 !important}.text-danger{color:#dc3545 !important}.text-light{color:#f8f9fa !important}.text-dark{color:#343a40 !important}.text-white{color:#fff !important}.text-body{color:#212529 !important}.text-muted{color:#6c757d !important}.text-black-50{color:rgba(0,0,0,0.5) !important}.text-white-50{color:rgba(255,255,255,0.5) !important}.bg-transparent{background-color:transparent !important}.text-justify{text-align:justify !important}.text-left{text-align:left !important}.text-right{text-align:right !important}.text-center{text-align:center !important}.font-weight-light{font-weight:300 !important}.font-weight-lighter{font-weight:lighter !important}.font-weight-normal{font-weight:400 !important}.font-weight-bold{font-weight:700 !important}.font-weight-bolder{font-weight:bolder !important}.font-italic{font-style:italic !important}.container{margin-left:auto;margin-right:auto;padding-left:15px;padding-right:15px;width:100%}@media (min-width: 576px){.container{max-width:540px}}@media (min-width: 768px){.container{max-width:720px}}@media (min-width: 992px){.container{max-width:960px}}@media (min-width: 1200px){.container{max-width:1140px}}.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{margin-left:auto;margin-right:auto;padding-left:15px;padding-right:15px;width:100%}@media (min-width: 576px){.container,.container-sm{max-width:540px}}@media (min-width: 768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width: 992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width: 1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-15px;margin-right:-15px}.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{padding-left:15px;padding-right:15px;position:relative;width:100%}@media (min-width: 576px){.col-sm{flex-basis:0;flex-grow:1;-ms-flex-positive:1;-ms-flex-preferred-size:0;max-width:100%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:100%;width:auto}.col-sm-1{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-sm-2{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-sm-5{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-sm-8{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-sm-11{-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}}@media (min-width: 768px){.col-md{flex-basis:0;flex-grow:1;-ms-flex-positive:1;-ms-flex-preferred-size:0;max-width:100%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:100%;width:auto}.col-md-1{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-md-2{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-md-5{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-md-8{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-md-11{-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}}@media (min-width: 992px){.col-lg{flex-basis:0;flex-grow:1;-ms-flex-positive:1;-ms-flex-preferred-size:0;max-width:100%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:100%;width:auto}.col-lg-1{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-lg-2{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-lg-5{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-lg-8{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-lg-11{-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}}@media (min-width: 1200px){.col-xl{flex-basis:0;flex-grow:1;-ms-flex-positive:1;-ms-flex-preferred-size:0;max-width:100%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:100%;width:auto}.col-xl-1{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%;max-width:8.33333%}.col-xl-2{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%;max-width:16.66667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.col-xl-5{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%;max-width:41.66667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%;max-width:58.33333%}.col-xl-8{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%;max-width:66.66667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.33333%;flex:0 0 83.33333%;max-width:83.33333%}.col-xl-11{-ms-flex:0 0 91.66667%;flex:0 0 91.66667%;max-width:91.66667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}}.d-flex{display:-ms-flexbox !important;display:flex !important}.sphinx-bs,.sphinx-bs *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.sphinx-bs p{margin-top:0} diff --git a/docs/reStructuredText/_build/html/_panels_static/panels-main.c949a650a448cc0ae9fd3441c0e17fb0.css b/docs/reStructuredText/_build/html/_panels_static/panels-main.c949a650a448cc0ae9fd3441c0e17fb0.css new file mode 100644 index 00000000..fc14abc8 --- /dev/null +++ b/docs/reStructuredText/_build/html/_panels_static/panels-main.c949a650a448cc0ae9fd3441c0e17fb0.css @@ -0,0 +1 @@ +details.dropdown .summary-title{padding-right:3em !important;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}details.dropdown:hover{cursor:pointer}details.dropdown .summary-content{cursor:default}details.dropdown summary{list-style:none;padding:1em}details.dropdown summary .octicon.no-title{vertical-align:middle}details.dropdown[open] summary .octicon.no-title{visibility:hidden}details.dropdown summary::-webkit-details-marker{display:none}details.dropdown summary:focus{outline:none}details.dropdown summary:hover .summary-up svg,details.dropdown summary:hover .summary-down svg{opacity:1}details.dropdown .summary-up svg,details.dropdown .summary-down svg{display:block;opacity:.6}details.dropdown .summary-up,details.dropdown .summary-down{pointer-events:none;position:absolute;right:1em;top:.75em}details.dropdown[open] .summary-down{visibility:hidden}details.dropdown:not([open]) .summary-up{visibility:hidden}details.dropdown.fade-in[open] summary~*{-moz-animation:panels-fade-in .5s ease-in-out;-webkit-animation:panels-fade-in .5s ease-in-out;animation:panels-fade-in .5s ease-in-out}details.dropdown.fade-in-slide-down[open] summary~*{-moz-animation:panels-fade-in .5s ease-in-out, panels-slide-down .5s ease-in-out;-webkit-animation:panels-fade-in .5s ease-in-out, panels-slide-down .5s ease-in-out;animation:panels-fade-in .5s ease-in-out, panels-slide-down .5s ease-in-out}@keyframes panels-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes panels-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.octicon{display:inline-block;fill:currentColor;vertical-align:text-top}.tabbed-content{box-shadow:0 -.0625rem var(--tabs-color-overline),0 .0625rem var(--tabs-color-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.tabbed-content>:first-child{margin-top:0 !important}.tabbed-content>:last-child{margin-bottom:0 !important}.tabbed-content>.tabbed-set{margin:0}.tabbed-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.tabbed-set>input{opacity:0;position:absolute}.tabbed-set>input:checked+label{border-color:var(--tabs-color-label-active);color:var(--tabs-color-label-active)}.tabbed-set>input:checked+label+.tabbed-content{display:block}.tabbed-set>input:focus+label{outline-style:auto}.tabbed-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.tabbed-set>label{border-bottom:.125rem solid transparent;color:var(--tabs-color-label-inactive);cursor:pointer;font-size:var(--tabs-size-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .tabbed-set>label:hover{color:var(--tabs-color-label-active)} diff --git a/docs/reStructuredText/_build/html/_panels_static/panels-variables.06eb56fa6e07937060861dad626602ad.css b/docs/reStructuredText/_build/html/_panels_static/panels-variables.06eb56fa6e07937060861dad626602ad.css new file mode 100644 index 00000000..adc61662 --- /dev/null +++ b/docs/reStructuredText/_build/html/_panels_static/panels-variables.06eb56fa6e07937060861dad626602ad.css @@ -0,0 +1,7 @@ +:root { +--tabs-color-label-active: hsla(231, 99%, 66%, 1); +--tabs-color-label-inactive: rgba(178, 206, 245, 0.62); +--tabs-color-overline: rgb(207, 236, 238); +--tabs-color-underline: rgb(207, 236, 238); +--tabs-size-label: 1rem; +} \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/_sources/analog-input.rst.txt b/docs/reStructuredText/_build/html/_sources/analog-input.rst.txt new file mode 100644 index 00000000..7df9b6df --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/analog-input.rst.txt @@ -0,0 +1,12 @@ +================================================================================ +Analog Input +================================================================================ + +Analog inputs can be used to measure voltages between 0 and 3.3V. +ODrive uses a 12 bit ADC (4096 steps) and so has a maximum resolution of 0.8 mV. +A GPIO must be configured with :code:`.config.gpioX_mode = GPIO_MODE_ANALOG_IN` before it can be used as an analog input. +To read the voltage on GPIO1 in odrivetool the following would be entered: :code:`odrv0.get_adc_voltage(1)`. + +Similar to RC PWM input, analog inputs can also be used to feed any of the numerical properties that are visible in :code:`odrivetool`. +This is done by configuring :code:`odrv0.config.gpio3_analog_mapping` and :code:`odrv0.config.gpio4_analog_mapping`. +Refer to :ref:`RC PWM ` for instructions on how to configure the mappings. diff --git a/docs/reStructuredText/_build/html/_sources/ascii-protocol.rst.txt b/docs/reStructuredText/_build/html/_sources/ascii-protocol.rst.txt new file mode 100644 index 00000000..d4cb5aef --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/ascii-protocol.rst.txt @@ -0,0 +1,180 @@ +.. _ascii-protocol: + +================================================================================ +ASCII Protocol +================================================================================ + +.. contents:: + :depth: 1 + :local: + +Sending Commands +------------------------------------------------------------------------------- + + * **Via USB:** + * **Windows:** Use `PuTTY `_ to manually send commands or open the COM port using your favorite programming language + * **Linux/macOS:** Run :code:`/dev/tty*` to list all serial ports. The ODrive will show up as :code:`/dev/ttyACM0` (or similar) on Linux and :code:`/dev/tty.usbmodem[...]` on macOS. + Once you know the name, you can use :code:`screen /dev/ttyACM0` (with the correct name) to send commands manually or open the device using your favorite programming language. + Serial ports on Unix can be opened, written to and read from like a normal file. + * **Via UART:** Connect the ODrive's TX (GPIO1) to your host's RX. Connect your ODrive's RX (GPIO2) to your host's TX. See :ref:`UART ` for more info. + * **Arduino:** You can use the `ODrive Arduino library `_ to talk to the ODrive. + * **Windows/Linux/macOS:** You can use an FTDI USB-UART cable to connect to the ODrive. + +The ODrive does not echo commands. That means that when you type commands into a program like :code:`screen`, the characters you type won't show up in the console. + +.. admonition:: Arduino + + There is an Arduino library that gives some examples on how to use the ASCII protocol to communicate with the ODrive. + Check it out `here <../Arduino/ODriveArduino>`. + +Command Format +------------------------------------------------------------------------------- + +The ASCII protocol is human-readable and line-oriented, with each line having the following format: + +Format: :code:`command *42 ; comment [new line character]` + + * :code:`*42` stands for a GCode compatible checksum and can be omitted. If and only if a checksum is provided, the device will also include a checksum in the response, if any. + If the checksum is provided but is not valid, the line is ignored. The checksum is calculated as the bitwise xor of all characters before the asterisk (`*`). + Example of a valid checksum: :code:`r vbus_voltage *93`. + * comments are supported for GCode compatibility + * the command is interpreted once the new-line character is encountered + + +.. _acsii-cmd-ref: + +Command Reference +------------------------------------------------------------------------------- + +.. _motor_traj-cmd: + +Motor Trajectory +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Format: :code:`t motor destination` + +* :code:`t` for trajectory. +* :code:`motor` is the motor number, :code:`0` or :code:`1`. +* :code:`destination` is the goal position, in [turns]. + +Example:: + + t 0 -2 + +For general moving around of the axis, this is the recommended command. + +This command updates the watchdog timer for the motor. + +Motor Position +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For basic use where you send one setpoint at at a time, use the :code:`q` command. + +Format: :code:`q motor position velocity_lim torque_lim` + +* :code:`q` for position. +* :code:`motor` is the motor number, :code:`0` or :code:`1`. +* :code:`position` is the desired position, in [turns]. +* :code:`velocity_lim` is the velocity limit, in [turns/s] (optional). +* :code:`torque_lim` is the torque limit, in [Nm] (optional). + +Example:: + + q 0 -2 1 0.1 + +If you have a realtime controller that is streaming setpoints and tracking a trajectory, use the :code:`p` command. + +Format: :code:`p motor position velocity_ff torque_ff` + +* :code:`p` for position +* :code:`motor` is the motor number, :code:`0` or :code:`1`. +* :code:`position` is the desired position, in [turns]. +* :code:`velocity_ff` is the velocity feed-forward term, in [turns/s] (optional). +* :code:`torque_ff` is the torque feed-forward term, in [Nm] (optional). + +Example:: + + p 0 -2 0 0 + +This command updates the watchdog timer for the motor. + +.. note:: If you don't know what feed-forward is or what it's used for, simply omit it. + +Motor Velocity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Format: :code:`v motor velocity torque_ff` + +* :code:`v` for velocity +* :code:`motor` is the motor number, :code:`0` or :code:`1`. +* :code:`velocity` is the desired velocity in [turns/s]. +* :code:`torque_ff` is the torque feed-forward term, in [Nm] (optional). + +Example:: + + v 0 1 0 + +This command updates the watchdog timer for the motor. + + +Motor Current +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Format: :code:`c motor torque` + +* :code:`c` for torque +* :code:`motor` is the motor number, :code:`0` or :code:`1`. +* :code:`torque` is the desired torque in [Nm]. + +This command updates the watchdog timer for the motor. + + +Request Feedback +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +imput format: :code:`f motor` + +response format: :code:`pos vel` + +* :code:`f` for feedback. +* :code:`pos` is the encoder position in [turns] (float). +* :code:`vel` is the encoder velocity in [turns/s] (float). + +Update Motor Watchdog +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Format: :code:`u motor` + +* :code:`u` for /u/pdate. + +* :code:`motor` is the motor number, :code:`0` or :code:`1`. + +This command updates the watchdog timer for the motor, without changing any +setpoints. + +Parameter Reading/Writing +------------------------------------------------------------------------------- + +Not all parameters can be accessed via the ASCII protocol but at least all parameters with float and integer type are supported. + + * Reading format: :code:`r [property]` + + * :code:`property` name of the property, as seen in ODrive Tool + * response: text representation of the requested value + * Example: :code:`r vbus_voltage` => response: :code:`24.087744` + + * Writing format: :code:`w [property] [value]` + + * :code:`property` name of the property, as seen in ODrive Tool + * :code:`value` text representation of the value to be written + * Example:: + + w axis0.controller.input_pos -123.456 + +System Commands +------------------------------------------------------------------------------- + +* :code:`ss` - Save config +* :code:`se` - Erase config +* :code:`sr` - Reboot +* :code:`sc` - Clear errors diff --git a/docs/reStructuredText/_build/html/_sources/can-guide.rst.txt b/docs/reStructuredText/_build/html/_sources/can-guide.rst.txt new file mode 100644 index 00000000..9451251f --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/can-guide.rst.txt @@ -0,0 +1,210 @@ +.. _can-guide: + +================================================================================ +CAN Bus Guide for ODrive +================================================================================ + +.. contents:: + :depth: 1 + :local: + +ODrive v3 supports CAN 2.0b. We've built a :ref:`simple protocol ` (named CANSimple) so that most ODrive functions can be controlled without a full CAN Open or similar stack. +This guide is intended for beginners to set up CAN on the ODrive and on their host device. +We will be focusing on Raspberry Pi and Arduino-compatible devices using the MCP2515 CAN Controller. + +What is CAN bus? +-------------------------------------------------------------------------------- + +Borrowing from `Wikipeda `_: + +> A Controller Area Network (CAN bus) is a robust vehicle bus standard designed to allow microcontrollers and devices to communicate with each other's applications without a host computer. +It is a message-based protocol, designed originally for multiplex electrical wiring within automobiles to save on copper, but it can also be used in many other contexts. +For each device, the data in a frame is transmitted sequentially but in such a way that if more than one device transmits at the same time, the highest priority device can continue while the others back off. +Frames are received by all devices, including by the transmitting device. + +In simple terms, CAN is a way of communicating between many devices over a single twisted pair of wires. +The signal is transmitted as the difference in voltage between the two wires (differential signalling), which makes it very robust against noise. +Instead of using a unique address (like I2C) or a select pin (like SPI), CAN *messages* have a unique ID that also acts as the priority. +At the beginning of a message frame, all devices talk and read at the same time. As the message ID is transmitted, the lowest value "wins" and that message will be transmitted (ID **0** has the *highest* priority). +All other devices will wait for the next chance to send. If two devices send the same message ID at the same time, they will conflict and a bus failure may occur. +Make sure your devices can never send the same message ID at the same time! + +See also `this great article `_ from Danfoss that quickly describes how to put together the wiring for a CAN bus + +.. image:: figures/CAN_Bus_Drawing.png + :scale: 60 % + :align: center + :alt: CAN picture + + + +Why use CAN? +-------------------------------------------------------------------------------- + +CAN is convenient for its simple and robust Physical Layer (PHY) that requires only a twisted pair of wires and a 120ohm termination resistor at each end. +It has low jitter and low latency, because there is no host computer. It is relatively fast (CAN 2.0b supports 1 Mbps). Messages are easy to configure and load with data. +Transceivers and controllers are inexpensive and widely available, thanks to its use in automotive. + +Hardware Setup +-------------------------------------------------------------------------------- + +ODrive assumes the CAN PHY is a standard differential twisted pair in a linear bus configuration with 120 ohm termination resistance at each end. +ODrive versions less than V3.5 include a soldered 120 ohm termination resistor, but ODrive versions V3.5 and greater implement a dip switch to toggle the termination. +ODrive uses 3.3v as the high output, but conforms to the CAN PHY requirement of achieving a differential voltage > 1.5V to represent a "0". +As such, it is compatible with standard 5V bus architectures. + +Setting up CAN on ODrive +-------------------------------------------------------------------------------- + +CANSimple breaks the CAN Message ID into two parts: An axis ID and a command ID. By default, CAN is enabled on the ODrive, where Axis 0 has ID 0, and Axis 1 has ID 1. +The ID of each axis should be unique; each should be set via :code:`odrivetool` before connecting to the bus with the command: + +.. code:: iPython + + ..config.can.node_id = + +By default, ODrive supports a value up to 63 (:code:`0x3F`). See :ref:`can-protocol ` for more information. + +You should also set the CAN bus speed on ODrive with the command + +.. code:: iPython + + .can.config.baud_rate = + +.. list-table:: + :widths: 25 25 + :header-rows: 1 + + * - Speed + - value + * - 125 kbps + - 125000 + * - 250 kbps + - 250000 + * - 500 kbps + - 500000 + * - 1000 kbps + - 1000000 + +That's it! You're ready to set up your host device. + +Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: iPython + + odrv0.axis0.config.can.node_id = 0 + odrv0.axis1.config.can.node_id = 1 + odrv0.can.config.baud_rate = 250000 + + +Setting up a Raspberry Pi for CAN Communications +-------------------------------------------------------------------------------- + +First, you will need a CAN Hat for your Raspberry Pi. +We are using `this CAN hat `_. + +Setting up the Raspberry Pi essentially involves the following: +#. Enable SPI communications to the MCP2515 +#. Install :code:`can-utils` with :code:`apt-get install can-utils` +#. Creating a connection between your application and the :code:`can0` socket + +There are many tutorials for this process. +`This one is pretty good `_, and `this recent forum post `_ also works. +However, be careful. You have to set the correct parameters for the particular CAN hat you're using! + +#. Set the correct oscillator value + +We configure the MCP2515 in section 2.2 of the tutorial, but the hat we recommend uses a 12MHz crystal instead of a 16 MHz crystal. +If you're not sure what value to use, the top of the `oscillator `_ will have the value printed on it in MHz. + +My Settings: + +.. code:: Bash + + dtparam=spi-on + dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25 + dtoverlay=spi0-hw-cs + + +#. Use the correct CAN baud rate + +By default, ODrive uses 250 kbps (250000) but the tutorial is using 500 kbps. Make sure you use the value set earlier on the ODrive. + +.. code:: Bash + + sudo ip link set can0 up type can bitrate 250000 + + +Wiring ODrive to CAN +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The CANH and CANL pins on J2 are used for CAN communication. Connect CANH to CANH on all other devices, and CANL to CANL. + +If your ODrive is the "last" (furthest) device on the bus, you can use the on-board 120 Ohm termination resistor by switching the DIP switch to "CAN 120R". +Otherwise, add an external resistor. + + + +Verifying Communcation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, each ODrive axis will send a heartbeat message at 10Hz. We can confirm our ODrive communication is working by starting the :code:`can0` interface, and then reading from it: + +.. code:: Bash + + sudo ip link set can0 up type can bitrate 250000 + candump can0 -xct z -n 10 + +This will read the first 10 messages from the ODrive and stop. If you'd like to see all messages, remove the :code:`-n 10` part (hit CTRL+C to exit). +The other flags (x, c, t) are adding extra information, colouring, and a timestamp, respectively. + +.. code:: Bash + + candump can0 -xct z -n 10 + (000.000000) can0 RX - - 001 [8] 00 00 00 00 01 00 00 00 + (000.001995) can0 RX - - 021 [8] 00 00 00 00 08 00 00 00 + (000.099978) can0 RX - - 001 [8] 00 00 00 00 01 00 00 00 + (000.101963) can0 RX - - 021 [8] 00 00 00 00 08 00 00 00 + (000.199988) can0 RX - - 001 [8] 00 00 00 00 01 00 00 00 + (000.201980) can0 RX - - 021 [8] 00 00 00 00 08 00 00 00 + (000.299986) can0 RX - - 001 [8] 00 00 00 00 01 00 00 00 + (000.301976) can0 RX - - 021 [8] 00 00 00 00 08 00 00 00 + (000.399986) can0 RX - - 001 [8] 00 00 00 00 01 00 00 00 + (000.401972) can0 RX - - 021 [8] 00 00 00 00 08 00 00 00 + + +Alternatively, if you have python can installed (:code:`pip3 install python-can`), you can use the can.viewer script: + +:code:`python3 -m can.viewer -c "can0" -i "socketcan"` which will give you a nice readout. +See the `python-can docs `_ for an example. + +Commanding the ODrive +-------------------------------------------------------------------------------- + +Now that we've verified the communication is working, we can try commanding the ODrive. +Make sure your ODrive is configured and working properly over USB with:code:`odrivetool` before continuing. +See the :ref:`Getting Started Guide ` for help with first-time configuration. + +To move the ODrive, we use the command :code:`Set Input Pos`, or cmd ID :code:`0x00C`. First we create a message with this ID, and then "OR" in the axis ID. +Then we create an 8-byte array of data with input position that we want, with a float value turned into bytes... this can be a pain though. + +DBC Files +-------------------------------------------------------------------------------- + +A DBC file (.dbc) is a database of all the messages and signals in a CAN protocol. +This file can be used with Python cantools to serialize and deserialize messages without having to handle the bitshifting etc yourself. +We have generated a .dbc for CANSimple for you! + +* `CANSimple DBC File `_ +* `CANSimple DBC Generator Script `_ + +Instead of manually writing values into the data, we can create a dictionary of signal:value pairs and serialize the data according to the database definition. + +#. Load the database into memory +#. Use :code:`encode_message()` to get a byte array representation of data for sending +#. Use :code:`decode_message()` to get a dictionary representation of data for receiving + +The `CAN DBC Example `_ script shows you how this can be used. This is the recommended method of serializing and deserializing. + +If you're using C++, then you can use the `CANHelpers `_ single-header library to do this instead, although the DBC file isn't used. diff --git a/docs/reStructuredText/_build/html/_sources/can-protocol.rst.txt b/docs/reStructuredText/_build/html/_sources/can-protocol.rst.txt new file mode 100644 index 00000000..d3c71b02 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/can-protocol.rst.txt @@ -0,0 +1,89 @@ +.. _can-protocol: + +================================================================================ +CAN Protocol +================================================================================ + +.. contents:: + :depth: 1 + :local: + +This document describes the CAN Protocol. For examples of usage, check out our :ref:`CAN Guide! ` + + +Configuring ODrive for CAN +-------------------------------------------------------------------------------- + +Configuration of the CAN parameters should be done via USB before putting the device on the bus. + +To set the desired baud rate, use :code:`.can.config.baud_rate = `. + +Each axis looks like a separate node on the bus. +Thus, they both have the two properties :code:`can_node_id` and :code:`can_node_id_extended`. +The node ID can be from 0 to 63 (0x3F) inclusive, or, if extended CAN IDs are used, from 0 to 16777215 (0xFFFFFF). +If you want to connect more than one ODrive on a CAN bus, you must set different node IDs for the second ODrive or they will conflict and crash the bus. + +Example Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: iPython + + odrv0.axis0.config.can_node_id = 3 + odrv0.axis1.config.can_node_id = 1 + odrv0.can.config.baud_rate = 500000 + odrv0.save_configuration() + odrv0.reboot() + +Transport Protocol +-------------------------------------------------------------------------------- + +We've implemented a very basic CAN protocol that we call "CAN Simple" to get users going with ODrive. +This protocol is sufficiently abstracted that it is straightforward to add other protocols such as CANOpen, J1939, or Fibre over ISO-TP in the future. +Unfortunately, implementing those protocols is a lot of work, and we wanted to give users a way to control ODrive's basic functions via CAN sooner rather than later. + +CAN Frame +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +At its most basic, the CAN Simple frame looks like this: + +* Upper 6 bits - Node ID - max 0x3F (or 0xFFFFFF when using extended CAN IDs) +* Lower 5 bits - Command ID - max 0x1F + +To understand how the Node ID and Command ID interact, let's look at an example + +The 11-bit Arbitration ID is setup as follows: + +:code:`can_id = axis_id << 5 | cmd_id` + +For example, an Axis ID of :code:`0x01` with a command of :code:`0x0C` would be result in :code:`0x2C`: + +:code:`0x01 << 5 | 0x0C = 0x2C` + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +All multibyte values are little endian (aka Intel format, aka least significant byte first). + +.. note:: + + * These messages are call & response. The Master node sends a message with the RTR bit set, and the axis responds with the same ID and specified payload. + * These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple. + * These messages can be sent to either address on a given ODrive board. + + +Interoperability with CANopen +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can deconflict with CANopen like this: + +* :code:`odrv0.axis0.config.can.node_id = 0x010` - Reserves messages 0x200 through 0x21F +* :code:`odrv0.axis1.config.can.node_id = 0x018` - Reserves messages 0x300 through 0x31F + +It may not be obvious, but this allows for some compatibility with CANOpen. +Although the address space 0x200 and 0x300 correspond to receive PDO base addresses, we can guarantee they will not conflict if all CANopen node IDs are >= 32. E.g.: + +* CANopen nodeID = 35 = 0x23 +* Receive PDO 0x200 + nodeID = 0x223, which does not conflict with the range [0x200 : 0x21F] + +Be careful that you don't assign too many nodeIDs per PDO group. Four CAN Simple nodes (32*4) is all of the available address space of a single PDO. +If the bus is strictly ODrive CAN Simple nodes, a simple sequential Node ID assignment will work fine. + diff --git a/docs/reStructuredText/_build/html/_sources/commands.rst.txt b/docs/reStructuredText/_build/html/_sources/commands.rst.txt new file mode 100644 index 00000000..f4d59fb8 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/commands.rst.txt @@ -0,0 +1,145 @@ + +.. _commands-doc: + +================================================================================ +Parameters & Commands +================================================================================ + +We will use the :code:`` as a placeholder for any ODrive object. Every ODrive controller is an ODrive object. In :code:`odrivetool` this is usually :code:`odrv0`. Furthermore we use `` as a placeholder for any axis, which is an attribute of an ODrive object (for example `odrv0.axis0`). An axis represents where the motors are connected. (axis0 for M0 or axis1 for M1) + +.. contents:: + :depth: 1 + :local: + +Per-Axis Commands +------------------------------------------------------------------------------- + +For the most part, both axes on the ODrive can be controlled independently. + +State Machine +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The current state of an axis is indicated by :attr:`.current_state `. +The user can request a new state by assigning a new value to :attr:`.requested_state `. +The default state after startup is :code:`AXIS_STATE_IDLE`. A description of all states can be found :attr:`here `. + +.. _commands-startup-procedure: + +Startup Procedure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default the ODrive takes no action at startup and goes to idle immediately. +In order to change what startup procedures are used, set the startup procedures you want to `True`. +The ODrive will sequence all enabled startup actions selected in the order shown below. + +* :code:`.config.startup_motor_calibration` +* :code:`.config.startup_encoder_index_search` +* :code:`.config.startup_encoder_offset_calibration` +* :code:`.config.startup_closed_loop_control` + +See :attr:`here ` for a description of each state. + +Control Mode +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default control mode is position control. +If you want a different mode, you can change :code:`.controller.config.control_mode`. +Possible values are listed :attr:`here `. + +Input Mode +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As of version v0.5.0, ODrive now intercepts the incoming commands and can apply filters to them. +The old protocol values :code:`pos_setpoint`, :code:`vel_setpoint`, and `current_setpoint` are still used internally by the closed-loop cascade control, but the user cannot write to them directly. +This allows us to condense the number of ways the ODrive accepts motion commands. + + +Control Commands +******************************************************************************** + +* :code:`.controller.input_pos = ` +* :code:`.controller.input_vel = ` +* :code:`.controller.input_torque = ` + +Modes can be selected by changing :code:`.controller.config.input_mode`. +The default input mode is :code:`INPUT_MODE_PASSTHROUGH`. +Possible values are listed :attr:`here `. + +System Monitoring Commands +------------------------------------------------------------------------------- + +Encoder Position and Velocity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* View encoder position with :code:`.encoder.pos_estimate` [turns] or :code:`.encoder.pos_est_counts` [counts] +* View rotational velocity with :code:`.encoder.vel_estimate` [turn/s] or :code:`.encoder.vel_est_counts` [count/s] + +Motor Current and Torque Estimation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* View the commanded motor current with :code:`.motor.current_control.Iq_setpoint` [A] +* View the measured motor current with :code:`.motor.current_control.Iq_measured` [A]. + If you find that this returns noisy data then use the command motor current instead. + The two values should be close so long as you are not approaching the maximum achievable rotational velocity of your motor for a given supply voltage, in which case the commanded current may become larger than the measured current. + +Using the motor current and the known KV of your motor you can estimate the motors torque using the following relationship: Torque [N.m] = 8.27 * Current [A] / KV. + +General System Commands +------------------------------------------------------------------------------- + +Saving the Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +All variables that are part of a :code:`[...].config` object can be saved to non-volatile memory on the ODrive so they persist after you remove power. +The relevant commands are: + + * :code:`.save_configuration()`: Stores the configuration to persistent memory on the ODrive. + * :code:`.erase_configuration()`: Resets the configuration variables to their factory defaults. This also reboots the device. + +Diagnostics +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + * :code:`.serial_number`: A number that uniquely identifies your device. When printed in upper case hexadecimal (:code:`hex(.serial_number).upper()`), this is identical to the serial number indicated by the USB descriptor. + * :code:`.fw_version_major`, :code:`.fw_version_minor`, :code:`.fw_version_revision`: The firmware version that is currently running. + * :code:`.hw_version_major`, :code:`.hw_version_minor`, :code:`.hw_version_revision`: The hardware version of your ODrive. + +.. _sensorless-setup: + +Setting up Sensorless +------------------------------------------------------------------------------- + +The ODrive can run without encoder/hall feedback, but there is a minimum speed, usually around a few hundred RPM. +In other words, sensorless mode does not support stopping or changing direction! + +Sensorless mode starts by ramping up the motor speed in open loop control and then switches to closed loop control automatically. +The sensorless speed ramping parameters are in :code:`axis.config.sensorless_ramp`. +The :code:`vel` and :code:`accel` (in [radians/s] and [radians/s^2]) parameters control the speed that the ramp tries to reach and how quickly it gets there. +When the ramp reaches :code:`sensorless_ramp.vel`, :code:`controller.input_vel` is automatically set to the same velocity, in [turns/s], and the state switches to closed loop control. + +If your motor comes to a stop after the ramp, try incrementally raising the :code:`vel` parameter. +The goal is to be above the minimum speed necessary for sensorless position and speed feedback to converge - this is not well-parameterized per motor. +The parameters suggested below work for the D5065 motor, with 270KV and 7 pole pairs. +If your motor grinds and skips during the ramp, lower the :code:`accel` parameter until it is tolerable. + +Below are some suggested starting parameters that you can use for the ODrive D5065 motor. +Note that you **must** set the :code:`pm_flux_linkage` correctly for sensorless mode to work. +Motor calibration and setup must also be completed before sensorless mode will work. + + +.. code:: iPython + + odrv0.axis0.controller.config.vel_gain = 0.01 + odrv0.axis0.controller.config.vel_integrator_gain = 0.05 + odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL + odrv0.axis0.controller.config.vel_limit = )> + odrv0.axis0.motor.config.current_lim = 2 * odrv0.axis0.config.sensorless_ramp.current + odrv0.axis0.sensorless_estimator.config.pm_flux_linkage = 5.51328895422 / ( * ) + odrv0.axis0.config.enable_sensorless_mode = True + + +To start the motor: + +.. code:: iPython + + odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL + diff --git a/docs/reStructuredText/_build/html/_sources/configuring-eclipse.rst.txt b/docs/reStructuredText/_build/html/_sources/configuring-eclipse.rst.txt new file mode 100644 index 00000000..81e8854c --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/configuring-eclipse.rst.txt @@ -0,0 +1,59 @@ +.. _configuring-eclipse: + +================================================================================ +Setting up Eclipse development environment +================================================================================ + +.. contents:: + :depth: 1 + :local: + +Install +------------------------------------------------------------------------------- + +* Install `Eclipse IDE for C/C++ Developers. `_ +* Install the `OpenOCD Eclipse plugin. `_ + +Import Project +------------------------------------------------------------------------------- + +* **File** -> **Import** -> **C/C++** -> Existing Code as Makefile Project +* Browse for existing code location, find the OdriveFirmware root. +* In the Toolchain options, select `Cross GCC` +* Hit **Finish** +* Build the project (press :kbd:`ctrl-B`) + +.. figure:: figures/CodeAsMakefile.png + :scale: 50 % + :alt: Toolchain options + +Load the Launch Configuration +------------------------------------------------------------------------------- + +* **File** -> **Import** -> **Run/Debug** -> **Launch Configurations** -> **Next** +* Highlight (don't tick) the OdriveFirmare folder in the left column +* Tick OdriveFirmware.launch in the right column +* Hit **Finish** + +.. figure:: figures/ImportLaunch.png + :scale: 50 % + :alt: Launch Configurations + +Launch! +------------------------------------------------------------------------------- + +* Make sure the programmer is connected to the board as per :ref:`Flashing the firmware `. +* Press the down-arrow of the debug symbol in the toolbar, and hit Debug Configurations + * You can also hit **Run** -> **Debug Configurations** + +* Highlight the debug configuration you imported, called OdriveFirmware. + If you do not see the imported launch configuration rename your project to `ODriveFirmware` or edit the launch configuration to match your project name by unfiltering unavailable projects: + +.. figure:: figures/LaunchConfigFilter.png + :scale: 50 % + :alt: Launch Configurations Filters + +* Hit **Debug** +* Eclipse should flash the board for you and the program should start halted on the first instruction in `Main` +* Set beakpoints, step, hit Resume, etc. +* Make some cool features! ;D \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/_sources/configuring-vscode.rst.txt b/docs/reStructuredText/_build/html/_sources/configuring-vscode.rst.txt new file mode 100644 index 00000000..44175225 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/configuring-vscode.rst.txt @@ -0,0 +1,82 @@ +.. _configuring-vscode: + +================================================================================ +Configuring Visual Studio Code +================================================================================ + +.. contents:: + :depth: 1 + :local: + +VSCode is the recommended IDE for working with the ODrive codebase. +It is a light-weight text editor with Git integration and GDB debugging functionality. + +Before doing the VSCode setup, make sure you've installed all of your :ref:`prerequisites. ` + +Setup Procedure +-------------------------------------------------------------------------------- + +#. Clone the ODrive repository +#. `Download VSCode `_ +#. Open VSCode +#. Install extensions. This can be done directly from VSCode (:kbd:`Ctrl` **+** :kbd:`Shift` **+** :kbd:`X`) + + * Required extensions: + * C/C++ :code:`ext install ms-vscode.cpptools` + * Cortex-Debug :code:`ext install marus25.cortex-debug` + * Cortex-Debug: Device Support Pack - STM32F4 :code:`ext install marus25.cortex-debug-dp-stm32f4` + + * Recommended Extensions: + * Include Autocomplete + * Path Autocomplete + * Auto Comment Blocks + +#. Create an environment variable named :code:`ARM_GCC_ROOT` whose value is the location of the :code:`GNU Arm Embedded Toolchain` (.e.g :code:`C:\Program Files (x86)\GNU Tools Arm Embedded\7 2018-q2-update`) that you installed in the prerequisites section of the developer's guide. + This is not strictly needed for Linux or Mac, and you can alternatively use the :code:`Cortex-debug: Arm Toolchain Path` setting in VSCode extension settings. +#. Relaunch VSCode +#. Open the VSCode Workspace file, which is located in the root of the ODrive repository. It is called :code:`ODrive_Workspace.code-workspace`. + The first time you open it, VSCode will install some dependencies. If it fails, you may need to `change your proxy settings `_. + +You should now be ready to compile and test the ODrive project. + +Building the Firmware +-------------------------------------------------------------------------------- + +* **Terminal** -> **Run Build Task** (:kbd:`Ctrl1` **+** :kbd:`Shift` **+** :kbd:`B`) + +A terminal window will open with your native shell. VSCode is configured to run the command :code:`make -j4` in this terminal. + +Flashing the Firmware +-------------------------------------------------------------------------------- + +* **Terminal** -> **Run Task** -> **flash** + +A terminal window will open with your native shell. VSCode is configured to run the command :code:`make flash` in this terminal. + +If the flashing worked, you can connect to the board using the :ref:`odrivetool `. + +Debugging +-------------------------------------------------------------------------------- + +An extension called Cortex-Debug has recently been released which is designed specifically for debugging ARM Cortex projects. +You can read more on Cortex-Debug `here. `_ + +Note: If developing on Windows, you should have :code:`arm-none-eabi-gdb` and :code:`openOCD` on your PATH. + + * Make sure you have the Firmware folder as your active folder + * Set :code:`CONFIG_DEBUG=true` in the tup.config file + * Flash the board with the newest code (starting debug session doesn't do this) + * In the **Run** tab (:kbd:`Ctrl` **+** :kbd:`Shift` **+** :kbd:`D`), select "Debug ODrive (Firmware)" + * Press **Start Debugging** (or press :kbd:`F5`) + * The processor will reset and halt. + * Set your breakpoints. Note: you can only set breakpoints when the processor is halted, if you set them during run mode, they won't get applied. + * **Continue** (press :kbd:`F5`) + * Stepping over/in/out, restarting, and changing breakpoints can be done by first pressing the "pause" (F6) button at the top the screen. + * When done debugging, simply stop (:kbd:`Shift` **+** :kbd:`F5`) the debugger. It will kill your openOCD process too. + +Cleaning the Build +-------------------------------------------------------------------------------- + +This sometimes needs to be done if you change branches. + +* Open a terminal (**View** -> **Integrated Terminal**) and enter :code:`make clean` diff --git a/docs/reStructuredText/_build/html/_sources/control-modes.rst.txt b/docs/reStructuredText/_build/html/_sources/control-modes.rst.txt new file mode 100644 index 00000000..d8ae09f2 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/control-modes.rst.txt @@ -0,0 +1,239 @@ +.. _control-modes-doc: + +================================================================================ +Control Modes +================================================================================ + +The default control mode is unfiltered position control in the absolute encoder reference frame. +You may wish to use a controlled trajectory instead. +Or you may wish to control position in a circular frame to allow continuous rotation forever without growing the numeric value of the setpoint too large. + +You may also wish to control velocity (directly or with a ramping filter). +You can also directly control the current of the motor, which is proportional to torque. + +.. contents:: + :depth: 1 + :local: + + +Filtered Position Control +-------------------------------------------------------------------------------- + +Asking the ODrive controller to go as hard as it can to raw setpoints may result in jerky movement. +Even if you are using a planned trajectory generated from an external source, if that is sent at a modest frequency, the ODrive may chase each stair in the incoming staircase in a jerky way. +In this case, a good starting point for tuning the filter bandwidth is to set it to one half of your setpoint command rate. + +You can use the second order position filter in these cases. +Set the filter bandwidth [Hz]: + +.. code:: iPython + + axis.controller.config.input_filter_bandwidth = 2.0 + + +Activate the setpoint filter: + +.. code:: iPython + + axis.controller.config.input_mode = INPUT_MODE_POS_FILTER + + +You can now control the position with + +.. code:: iPython + + axis.controller.input_pos = 1 + + +.. figure:: figures/secondOrderResponse.PNG + :alt: secondOrderResponse + + Step response of a 1000 to 0 position input with a filter bandwidth of 1.0 Hz + + +Trajectory Control +-------------------------------------------------------------------------------- + +See the :ref:`Usage ` section for usage details. + +This mode lets you smoothly accelerate, coast, and decelerate the axis from one position to another. With raw position control, the controller simply tries to go to the setpoint as quickly as possible. Using a trajectory lets you tune the feedback gains more aggressively to reject disturbance, while keeping smooth motion. + +.. figure:: figures/TrapTrajPosVel.PNG + :alt: TrapTrajPosVel + :scale: 130 % + + Position (blue) and velocity (orange) vs. time using trajectory control. + +Parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: iPython + + odrv0.axis0.trap_traj.config.vel_limit = + +.. code:: iPython + + odrv0.axis0.trap_traj.config.accel_limit = + +.. code:: iPython + + odrv0.axis0.trap_traj.config.decel_limit = + +.. code:: iPython + + odrv0.axis0.controller.config.inertia = + + +* :code:`vel_limit` is the maximum planned trajectory speed. This sets your coasting speed. +* :code:`accel_limit` is the maximum acceleration in turns / sec^2 +* :code:`decel_limit` is the maximum deceleration in turns / sec^2 +* :code:`controller.config.inertia` is a value which correlates acceleration (in turns / sec^2) and motor torque. It is 0 by default. It is optional, but can improve response of your system if correctly tuned. Keep in mind this will need to change with the load / mass of your system. + + +.. note:: All values should be strictly positive (>= 0). + + +Keep in mind that you must still set your safety limits as before. It is recommended you set these a little higher ( > 10%) than the planner values, to give the controller enough control authority. + +.. code:: iPython + + odrv0.axis0.motor.config.current_lim = + +.. code:: iPython + + odrv0.axis0.controller.config.vel_limit = + + +.. _usage: + +Usage +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Make sure you are in position control mode. To activate the trajectory module, set the input mode to trajectory: + +.. code:: iPython + + odrv0.axis0.controller.config.input_mode = INPUT_MODE_TRAP_TRAJ + + +Simply send a position command to execute the move: + +.. code:: iPython + + odrv0.axis0.controller.input_pos = + + +Use the :code:`move_incremental` function to move to a relative position. + +.. code:: iPython + + odrv0.axis0.controller.move_incremental(pos_increment, from_goal_point) + + +To set the goal relative to the current actual position, use :code:`from_goal_point = False` +To set the goal relative to the previous destination, use :code:`from_goal_point = True` + + +You can also execute a move with the :ref:`appropriate ascii command `. + + +Circular Position Control +-------------------------------------------------------------------------------- + +To enable Circular position control, set + +.. code:: iPython + + odrv0.axis0.controller.config.circular_setpoints = True + + +This mode is useful for continuous incremental position movement. +For example a robot rolling indefinitely, or an extruder motor or conveyor belt moving with controlled increments indefinitely. +In the regular position mode, the :code:`input_pos` would grow to a very large value and would lose precision due to floating point rounding. + +In this mode, the controller will try to track the position within only one turn of the motor. Specifically, :code:`input_pos` is expected in the range `[0, 1)`. +If the :code:`input_pos` is incremented to outside this range (say via step/dir input), it is automatically wrapped around into the correct value. +Note that in this mode :code:`encoder.pos_circular` is used for feedback instead of :code:`encoder.pos_estimate`. + +If you try to increment the axis with a large step in one go that exceeds `1` turn, the motor will go to the same angle around the wrong way. +This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a larger circular range. +Set + +.. code:: iPython + + odrv0.axis0.controller.config.circular_setpoints_range = + + +Choose N to give you an appropriate circular space for your application. + +Velocity Control +-------------------------------------------------------------------------------- + +Set the control mode + +.. code:: iPython + + odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL + + +You can now control the velocity [turn/s] with + +.. code:: iPython + + odrv0.axis0.controller.input_vel = 1 + + +Ramped Velocity Control +-------------------------------------------------------------------------------- + +Set the control mode + +.. code:: iPython + + odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL + + +Set the velocity ramp rate (acceleration in turn/s^2): + +.. code:: iPython + + odrv0.axis0.controller.config.vel_ramp_rate = 0.5 + +Activate the ramped velocity mode: + +.. code:: iPython + + odrv0.axis0.controller.config.input_mode = INPUT_MODE_VEL_RAMP + + +You can now control the velocity (turn/s) with + +.. code:: iPython + + odrv0.axis0.controller.input_vel = 1 + + +Torque Control +-------------------------------------------------------------------------------- + +Set the control mode + +.. code:: iPython + + odrv0.axis0.controller.config.control_mode = CONTROL_MODE_TORQUE_CONTROL + + +You can now control the torque (Nm) with + +.. code:: iPython + + odrv0.axis0.controller.input_torque = 0.1 + + +.. note:: + If you exceed :code:`vel_limit` in torque control mode, the current is reduced. + To disable this, set + + .. code:: iPython + + odrv0.axis0.controller.enable_torque_mode_vel_limit = False diff --git a/docs/reStructuredText/_build/html/_sources/control.rst.txt b/docs/reStructuredText/_build/html/_sources/control.rst.txt new file mode 100644 index 00000000..e5430561 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/control.rst.txt @@ -0,0 +1,144 @@ +.. _control-doc: + +================================================================================ + Control Structure and Tuning +================================================================================ + +.. contents:: + :depth: 1 + :local: + +The motor controller is a cascaded style position, velocity and current control loop, as per the diagram below. +When the control mode is set to position control, the whole loop runs. +When running in velocity control mode, the position control part is removed and the velocity command is fed directly in to the second stage input. +In torque control mode, only the current controller is used. + +.. figure:: figures/controller_with_ff.png + :alt: controller_with_ff + + Cascaded position and velocity I loops + +Each stage of the control loop is a variation on a `PID controller `_. +A PID controller is a mathematical model that can be adapted to control a wide variety of systems. +This flexibility is essential as it allows the ODrive to be used to control all kinds of mechanical systems. + +.. note:: + The controller has been updated to use `torque` in Newton-meters instead of current at the "system" level. + There is a :code:`torque_constant` parameter which converts between torque and current, after which the rest of this explanation still holds. + + +Position Control Loop +-------------------------------------------------------------------------------- +The position controller is a P loop with a single proportional gain, + + .. math:: + + \text{pos_error} &= \text{pos_setpoint} - \text{pos_feedback}, + + \text{vel_cmd} &= \text{pos_error} * \text{pos_gain} + \text{vel_feedforward}. + + +Velocity Control Loop +-------------------------------------------------------------------------------- + +The velocity controller is a PI loop where + + .. math:: + + \text{vel_error} &= \text{vel_cmd} - \text{vel_feedback}, + + \text{current_integral} &+= \text{vel_error} * \text{vel_integrator gain}, + + \text{current_cmd} &= \text{vel_error} * \text{vel_gain} + \text{current_integral} + \text{current_feedforward}. + + +Current Control Loop +-------------------------------------------------------------------------------- + +The current controller is also a PI loop, + + .. math:: + + \text{current_error} &= \text{current_cmd} - \text{current_feedback}, + + \text{voltage_integral} &+= \text{current_error} * \text{current_integrator gain}, + + \text{voltage_cmd} &= \text{current_error} * \text{current_gain} + \text{voltage_integral} + ... + + & ... + \text{voltage_feedforward} \textbf{ (when we have motor model)}. + +.. note:: + `current_gain` and `current_integrator_gain` are automatically set according to :code:`motor.config.current_control_bandwidth` + +For more detail refer to `controller.cpp `_. + +Controller Details +-------------------------------------------------------------------------------- + +The ultimate output of the controller is the voltage applied to the gate of each FET to deliver current through each coil of the motor. +The current through the motor linearly relates to the torque output of the motor. +This means that the inputs to the cascaded controller are theoretically the position (angle), velocity (angle/time), and acceleration (angle/time/time) of the motor. +Note that when thinking about the controller from the perpective of the physics of the motor you would expect to see the time in the Velocity and Current loops, but it is absent because the time difference between iterations is always 125 microseconds (8kHz). +Because the time difference between controller loops is a constant and can simply be wrapped into the controller gains. + +The output of each stage of the controller is clamped before being fed into the next stage. +So after the `vel_cmd` is calculated from the position controller, the `vel_cmd` is clamped to the velocity limit. +The `torque_cmd` output of the velocity controller is then clamped and fed to the current controller. +Oddly enough the controller class does not contain the current controller, but instead the current controller is housed in the motor class due to the complexity of the motor driver schema. + +The feedforward terms available when using the position or velocity control mode are meant to enable better performance when the dynamics of a system are known and the host controller can predict the motion based on the load. +A perfect example of this is the use of the trajectory controller that sets the position, velocity, and torque based on the desired position, velocity, and acceleration. +If you take a trapezoidal velocity profile for example, you can imagine on the ramp upward the velocity will be increasing over time, while the torque is a non-zero constant. +At the flat portion of the profile the velocity will be a non-zero constant, but the acceleration will be zero. +This trajectory controller use case uses the cascaded controller with multiple inputs to achieve the desired motion with the best performance. + +.. _control-tuning: + + +Tuning +-------------------------------------------------------------------------------- + +Tuning the motor controller is an essential step to unlock the full potential of the ODrive. +Tuning allows for the controller to quickly respond to disturbances or changes in the system (such as an external force being applied or a change in the setpoint) without becoming unstable. +Correctly setting the three tuning parameters (called gains) ensures that ODrive can control your motors in the most effective way possible. +The three (starting) values are: + +* pos_gain [(turn/s) / turn]: + + .. code:: iPython + + odrv0.axis0.controller.config.pos_gain = 20.0 + +* vel_gain [Nm/(turn/s)]: + + .. code:: iPython + + odrv0.axis0..controller.config.vel_gain = 0.16 + +* vel_integrator_gain [Nm/((turn/s) * s)]: + + .. code:: iPython + + odrv0.axis0.controller.config.vel_integrator_gain = 0.32 + +An upcoming feature will enable automatic tuning. Until then, here is a rough tuning procedure: + #. Set vel_integrator_gain gain to 0 + #. Make sure you have a stable system. If it is not, decrease all gains until you have one. + #. Increase :code:`vel_gain` by around 30% per iteration until the motor exhibits some vibration. + #. Back down :code:`vel_gain` to 50% of the vibrating value. + #. Increase :code:`pos_gain` by around 30% per iteration until you see some overshoot. + #. Back down :code:`pos_gain` until you do not have overshoot anymore. + #. The integrator can be set to :code:`0.5 * bandwidth * vel_gain`, where :code:`bandwidth` is the overall resulting tracking bandwidth of your system. + Say your tuning made it track commands with a settling time of 100ms (the time from when the setpoint changes to when the system arrives at the new setpoint); this means the bandwidth was :math:`\frac{1}{100ms} = \frac{1}{0.1s} = 10Hz`. + In this case you should set + + .. code:: iPython + + vel_integrator_gain = 0.5 * 10 * + +The liveplotter tool can be immensely helpful in dialing in these values. +To display a graph that plots the position setpoint vs the measured position value run the following in the ODrive tool: + +.. code:: iPython + + start_liveplotter(lambda:[odrv0.axis0.encoder.pos_estimate, odrv0.axis0.controller.pos_setpoint]) diff --git a/docs/reStructuredText/_build/html/_sources/developer-guide.rst.txt b/docs/reStructuredText/_build/html/_sources/developer-guide.rst.txt new file mode 100644 index 00000000..cdcf4085 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/developer-guide.rst.txt @@ -0,0 +1,479 @@ +.. _developer-guide-doc: + +================================================================================ +ODrive Firmware Developer Guide +================================================================================ + +.. contents:: + :depth: 1 + :local: + + +This guide is intended for developers who wish to modify the firmware of the ODrive. +As such it assumes that you know things like how to use Git, what a compiler is, etc. If that sounds scary, turn around now. + +The official releases are maintained on the `master` branch. However since you are a developer, you are encouraged to use the `devel` branch, as it contains the latest features. + +The project is under active development, so make sure to check the `Changelog `_ to keep track of updates. + +.. _dev-prereq: + +Prerequisites +------------------------------------------------------------------------------- + +The recommended tools for ODrive development are: + + * **make**: Used to invoke tup + * **Tup**: The build system used to invoke the compile commands + * **ARM GNU Compiler**: For cross-compiling code + * **ARM GDB**: For debugging the code and stepping through on the device + * **OpenOCD**: For flashing the ODrive with the STLink/v2 programmer + * **Python 3**, along with the packages :code:`PyYAML`, :code:`Jinja2` and :code:`jsonschema`: For running the Python tools (:code:`odrivetool`). Also required for compiling firmware. + +See below for specific installation instructions for your OS. + +Depending on what you're gonna do, you may not need all of the components. + +Once you have everything, you can verify the correct installation by running: + +.. code:: Bash + + arm-none-eabi-gcc --version + arm-none-eabi-gdb --version + openocd --version # should be 0.10.0 or later + tup --version # should be 0.7.5 or later + python --version # should be 3.7 or later + +**Installing Prerequisites** + +.. tabs:: + .. tab:: Linux (Ubuntu < 20.04) + + .. code:: Bash + + sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa + + .. code:: Bash + + sudo apt-get update + + .. code:: Bash + + sudo apt-get install gcc-arm-embedded + + .. code:: Bash + + sudo apt-get install openocd + + .. code:: Bash + + sudo apt-get install git-lfs + + .. code:: Bash + + sudo add-apt-repository ppa:jonathonf/tup && sudo apt-get update && sudo apt-get install tup + + .. code:: Bash + + sudo apt-get install python3 python3-yaml python3-jinja2 python3-jsonschema + + .. tab:: Linux (Ubuntu >= 20.04) + + .. code:: Bash + + sudo apt install gcc-arm-none-eabi + + .. code:: Bash + + sudo apt install openocd + + .. code:: Bash + + sudo apt install git-lfs + + .. code:: Bash + + sudo apt install tup + + .. code:: Bash + + sudo apt install python3 python3-yaml python3-jinja2 python3-jsonschema + + .. tab:: Arch Linux + + .. code:: Bash + + sudo pacman -S arm-none-eabi-gcc arm-none-eabi-binutils + + .. code:: Bash + + sudo pacman -S arm-none-eabi-gdb + + .. code:: Bash + + sudo pacman -S git-lfs + + .. code:: Bash + + sudo pacman -S tup + + .. code:: Bash + + sudo pacman -S python python-yaml python-jinja python-jsonschema + + * `OpenOCD AUR package `__ + + .. tab:: Mac + First install `Homebrew `__ Then you can run these commands in Terminal: + + .. code:: Bash + + brew install armmbed/formulae/arm-none-eabi-gcc + + .. code:: Bash + + brew install --cask osxfuse && brew install tup + + .. code:: Bash + + brew install openocd + + .. code:: Bash + + brew install git-lfs + + .. code:: Bash + + pip3 install PyYAML Jinja2 jsonschema + + .. tab:: Windows + + .. note:: make sure these programs are not only installed but also added to your `PATH`. + + Some instructions in this document may assume that you're using a bash command prompt, such as the Windows 10 built-in bash or `Git `__ bash. + + * `ARM compiler `__ + + .. note:: + + After installing, create an environment variable named `ARM_GCC_ROOT` whose value is the path you installed to. e.g. :code:`C:\Program Files (x86)\GNU Tools Arm Embedded\7 2018-q2-update`. + This variable is used to locate include files for the c/c++ Visual Studio Code extension. + + .. note:: 8-2018-q4-major seems to have a bug on Windows. Please use 7-2018-q2-update. + * `Tup `__ + * `GNU MCU Eclipse's Windows Build Tools `__ + * `Python 3 `__ + * Install Python packages: :code:`pip install PyYAML Jinja2 jsonschema` + * `OpenOCD `__ + * `ST-Link/V2 Drivers `__ + + +Configuring the Build +-------------------------------------------------------------------------------- + +To customize the compile time parameters, copy or rename the file :code:`Firmware/tup.config.default` to :code:`Firmware/tup.config` and edit the following parameters in that file: + +* **CONFIG_BOARD_VERSION** The board version you're using. Can be `v3.1`, `v3.2`, `v3.3`, `v3.4-24V`, `v3.4-48V`, `v3.5-24V`, `v3.5-48V`, etc. Check for a label on the upper side of the ODrive to find out which version you have. + Some ODrive versions don't specify the voltage: in that case you can read the value of the main capacitors: 120uF are 48V ODrives, 470uF are 24V ODrives. + +* **CONFIG_DEBUG** Defines whether debugging will be enabled when compiling the firmware; specifically the :code:`-g -gdwarf-2` flags. + Note that printf debugging will only function if your tup.config specifies the :code:`USB_PROTOCOL` or :code:`UART_PROTOCOL` as stdout and :code:`DEBUG_PRINT` is defined. + See the IDE specific documentation for more information. + +You can also modify the compile-time defaults for all :code:`.config` parameters. +You will find them if you search for :code:`AxisConfig`, :code:`MotorConfig`, etc. + +.. _build-and-flash: + +Building and Flashing the Firmware +-------------------------------------------------------------------------------- + +#. Run :code:`make` in the :code:`Firmware` directory. +#. Connect the ODrive via USB and power it up. +#. Flash the firmware using :ref:`odrivetool dfu `. + +.. _flashing-with-an-stlink: + +Flashing using an STLink/v2 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Connect `GND`, `SWD`, and `SWC` on connector J2 to the programmer. + + .. note:: Always plug in `GND` first! + +* You need to power the board by only **ONE** of the following: VCC(3.3v), 5V, or the main power connection (the DC bus). The USB port (J1) does not power the board. +* Run :code:`make flash` in the :code:`Firmware` directory. + +.. note:: If you receive the error `can't find target interface/stlink-v2.cfg` or similar, create and set an environment variable named :code:`OPENOCD_SCRIPTS` to the location of the openocd scripts directory. + +If the flashing worked, you can connect to the board using the :ref:`odrivetool `. + +Testing +-------------------------------------------------------------------------------- + +.. include:: testing.rst + +Debugging +-------------------------------------------------------------------------------- + +If you're using VSCode, make sure you have the Cortex Debug extension, OpenOCD, and the STLink. +You can verify that OpenOCD and STLink are working by ensuring you can flash code. +Open the ODrive_Workspace.code-workspace file, and start a debugging session (F5). +VSCode will pick up the correct settings from the workspace and automatically connect. +Breakpoints can be added graphically in VSCode. + +* Run :code:`make gdb`. This will reset and halt at program start. Now you can set breakpoints and run the program. If you know how to use gdb, you are good to go. + +Setting up an IDE +-------------------------------------------------------------------------------- + +For working with the ODrive code you don't need an IDE, but the open-source IDE VSCode is recommended. +It is also possible to use Eclipse. If you'd like to go that route, please see the respective configuration document: + +* :ref:`Configuring VSCode ` +* :ref:`Configuring Eclipse ` + + +STM32CubeMX +-------------------------------------------------------------------------------- + +This project uses the STM32CubeMX tool to generate startup code and to ease the configuration of the peripherals. +You can download it from `here `___. +All CubeMX related files are in :code:`Firmware/Board/v3`. + +You will likely want the pinout for this process. It is available `here `__. + +Maintaining Modified Generated Code +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When generating the code, STM32CubeMX will nuke everything except some special sections that they provide. +These sections are marked like `USER CODE BEGIN`...`USER CODE END`. +We used to try to make sure all edits we made to the generated code would only go in these sections, so some code structrure may reflect that. +However over time we realized this will not be tenable, so instead we use git to rebase all changes of the generated code whenever we need to regenerate it. +We use two special branches that will help us to do this, they are :code:`STM32CubeMX-start` and :code:`STM32CubeMX-end`. +How to use these is shown in the following example. + +.. note:: + Due to how this rebasing is done, all development that changes the generated code should be done directly on :code:`STM32CubeMX-end`, and not based on :code:`devel`, then follow step 4 below to carry them over to your feature branch. If you did some changes to the generated code based from :code:`devel`, you need to cherry pick just those changes over to :code:`STM32CubeMX-end`. + +#. **Ensuring a clean slate** + + * We do all changes to the STM32CubeMX config and regenerate the code on top of :code:`STM32CubeMX-start`. + * :code:`git checkout STM32CubeMX-start` + + * Run stm32cubeMX and load the :code:`Firmware/Board/v3/Odrive.ioc` project file. + * If the tool asks if you wish to migrate to a new version, choose to download the old firmware package (unless you want to use the latest libraries) + + * Without changing any settings, press :code:`Project -> Generate code`. + * You may need to let it download some drivers and such. + * STM32CubeMX may now have a newer version of some of the libraries, so there may be changes to the generated code even though we didn't change any settings. We need to check that everything is still working, and hence check in the changes: + * :code:`git config --local core.autocrlf input` - This will tell git that all files should be checked in with LF endings (CubeMX generates CRLF endings). + * :code:`git diff` - Ignore the pile of line ending warnings. + * If you feel qualified: you can now ispect if CubeMX introduced something stupid. If there were any changes, and they look acceptable, we should commit them: + + * :code:`git commit -am "Run STM32CubeMX v1.21"` - Replace with actual version of CubeMX + +#. **Making Changes to the STM32CubeMX Config** + + * After completing the above steps, make sure the working directory is clean: + * :code:`git status` should include "nothing to commit, working tree clean" + + * Make your changes in STM32CubeMX, save the project and generate the code. (:code:`Project -> Generate code`) + * :code:`git diff` - Check that the introduced changes are as expected + * If everything looks ok, you can commit your changes. + +#. **Rebasing the Modifications to the Generated Code** + + * :code:`git checkout STM32CubeMX-end` + * :code:`git rebase STM32CubeMX-start` + * Make sure the rebase finishes, fixing any conflicts that may arise + +#. **Merge New STM32CubeMX Code to your Feature Branch** + + Simply merge the new state at:code: `STM32CubeMX-end` into your feature branch. + * :code:`git checkout your-feature` + * :code:`git merge STM32CubeMX-end` + +#. **Pushing back Upstream** + + * Generate a PR like normal for your feature. + * Make sure youhave pushed to the :code:`STM32CubeMX-start` and :code:`STM32CubeMX-end` branches on your fork. + * Make a note in your PR to the maintainer that they need to update the STM32CubeMX branches when they merge the PR. + +Troubleshooting +-------------------------------------------------------------------------------- + +:code:`LIBUSB_ERROR_IO` when flashing with the STLink/v2 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Problem:** when I try to flash the ODrive with the STLink using :code:`make flash` I get this error: + +.. code:: Bash + + Open On-Chip Debugger 0.10.0 + Licensed under GNU GPL v2 + For bug reports, read + http://openocd.org/doc/doxygen/bugs.html + Info : auto-selecting first available session transport "hla_swd". To override use 'transport select '. + Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD + adapter speed: 2000 kHz + adapter_nsrst_delay: 100 + none separate + Info : Unable to match requested speed 2000 kHz, using 1800 kHz + Info : Unable to match requested speed 2000 kHz, using 1800 kHz + Info : clock speed 1800 kHz + Error: libusb_open() failed with LIBUSB_ERROR_IO + Error: open failed + in procedure 'init' + in procedure 'ocd_bouncer' + +**Solution:** +This happens from time to time. + +#. Unplug the STLink and all ODrives from your computer +#. Power off the ODrive you're trying to flash +#. Plug in the STLink into your computer +#. Power on the ODrive +#. Run :code:`make flash` again + +:code:`Cannot identify target as a STM32 family` when flashing using openocd +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Problem:** When I try to flash ODrive v4.1 with :code:`make flash` then I get: + +.. code:: Bash + + [...] + ** Programming Started ** + auto erase enabled + Info : device id = 0x10006452 + Warn : Cannot identify target as a STM32 family. + Error: auto_probe failed + embedded:startup.tcl:487: Error: ** Programming Failed ** + in procedure 'program' + in procedure 'program_error' called at file "embedded:startup.tcl", line 543 + at file "embedded:startup.tcl", line 487 + + +**Solution:** +Compile and install a recent version of openocd from source. +The latest official release (0.10.0 as of Nov 2020) doesn't support the STM32F722 yet. + +.. code:: Bash + + sudo apt-get install libtool libusb-1.0 + git clone https://git.code.sf.net/p/openocd/code openocd + cd openocd/ + ./bootstrap + ./configure --enable-stlink + make + sudo make install + + +Documentation +-------------------------------------------------------------------------------- +.. admonition:: TODO + + **Documentation refactor in progress, changing significantly** + + * prerequisites + * Install Sphinx: :code:`pip install -U sphinx` + * Install packages: :code:`pip install sphinx-copybutton sphinx-panels sphinx-rtd-theme` + + * Run :code:`make html` within the :kbd:`./docs/reStructuredText` folder + * Open :kbd:`./docs/reStructuredText/_build/index.html` to view + +.. All `*.md` files in the `docs/` directory of the master branch are served up by GitHub Pages on `this domain `__. + +.. * Theme: `minimal https://github.com/pages-themes/minimal>`__ by `orderedlist](https://github.com/orderedlist) +.. * HTML layout: `docs/_layouts/default.html` +.. * CSS style: `docs/assets/css/styles.scss` +.. * Site index: `docs/_data/index.yaml` + +.. To run the docs server locally: + +.. ```bash +.. cd docs +.. gem install bundler # The gem command typically comes with a Ruby installation +.. #export PATH="$PATH:~/.gem/ruby/2.7.0/bin" # or similar (depends on OS) +.. rm Gemfile.lock # only if below commands cause trouble +.. bundle config path ruby-bundle +.. bundle install +.. mkdir -p _api _includes +.. python ../Firmware/interface_generator_stub.py --definitions ../Firmware/odrive-interface.yaml --template _layouts/api_documentation_template.j2 --outputs _api/'#'.md && python ../Firmware/interface_generator_stub.py --definitions ../Firmware/odrive-interface.yaml --template _layouts/api_index_template.j2 --output _includes/apiindex.html +.. bundle exec jekyll serve --incremental --host=0.0.0.0 +.. ``` + +.. On Ubuntu 18.04, prerequisites are: `ruby ruby-dev zlib1g-dev`. + +.. _modifying-libfibre: + +Modifying libfibre +-------------------------------------------------------------------------------- + +If you need to modify libfibre add :code:`CONFIG_BUILD_LIBFIBRE=true` to your tup.config and rerun :code:`make`. +After this you can start :code:`odrivetool` (on your local PC) and it will use the updated libfibre. + +To cross-compile libfibre for the Raspberry Pi, run :code:`make libfibre-linux-armhf` or :code:`make libfibre-all`. +This will require a docker container. See ;:ref:`fibre-cpp readme <../Firmware/fibre-cpp/README.md>` for details. + +.. code:: Bash + + docker run -it -v "$(pwd)":/build -v /tmp/build:/build/build -w /build fibre-compiler configs/linux-armhf.config + +If you're satisfied with the changes don't forget to generate binaries for all +supported systems using :code:`make libfibre-all`. + +Releases +-------------------------------------------------------------------------------- + +We use GitHub Releases to provide firmware releases. + +#. Cut off the changelog to reflect the new release +#. Merge the release candidate into master. +#. Push a (lightweight) tag to the master branch. Follow the existing naming convention. +#. If you changed something in libfibre, regenerate the binaries using :code:`make libfibre-all`. + See :ref:`Modifying libfibre ` for details. +#. Push the python tools to PyPI (see setup.py for details). +#. Edit the release on GitHub to add a title and description (copy&paste from changelog). + +Code Maintenance Notes +-------------------------------------------------------------------------------- + +The cortex M4F processor has hardware single precision float unit. However double precision operations are not accelerated, and hence should be avoided. +The following regex is helpful for cleaning out double constants: + +find: + +.. code:: + + ([-+]?[0-9]+\.[0-9]+(?:[eE][-+]?[0-9]+)?)([^f0-9e]) + +replace: + + .. code:: + + \1f\2 + +Notes for Contributors +-------------------------------------------------------------------------------- + +In general the project uses the `Google C++ Style Guide `__ with a few exceptions: + + * The default indentation is 4 spaces. + * The 80 character limit is not very strictly enforced, merely encouraged. + * The file extensions `*.cpp` and `*.hpp` are used instead of `*.cc` and `*.h`. + +Your help is welcome! However before you start working on a feature/change that will take you a non-negligible amount of time and that you plan to upstream please discuss your plans with us on GitHub or Discord. +This will ensure that your implementation is in line with the direction that ODrive is going. + +When filing a PR please go through this checklist: + + * Make sure you adhere to the same coding style that we use (see note above). + * Update CHANGELOG.md. + * If you removed/moved/renamed things in :code:`odrive-interface.yaml` make sure to add corresponding bullet points tp the "API migration notes" section in the changelog. Use git to compare against the :code:`devel` branch. + * Also, for each removed/moved/renamed API item use your IDE's search feature to search for occurrences of this name. Update the places you found (this will usually be documentation and test scripts). + * If you added things to :code:`odrive-interface.yaml` make sure the new things have decent documentation in the YAML file. We don't expect 100% coverage but use good sense of what to document. + * Make sure your PR doesn't contain spurious changes that unnecessarily add or remove whitespace. These add noise and make the reviewer's lifes harder. + * If you changed any enums in :code:`odrive-interface.yaml`, make sure you update `enums.py `_ and `ODriveEnums.h `_. + The file includes instructions on how to do this. Check the diff to verify that none of the existing enumerators changed their value. diff --git a/docs/reStructuredText/_build/html/_sources/encoders.rst.txt b/docs/reStructuredText/_build/html/_sources/encoders.rst.txt new file mode 100644 index 00000000..cfd22383 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/encoders.rst.txt @@ -0,0 +1,366 @@ +================================================================================ +Encoders +================================================================================ + +.. contents:: + :depth: 1 + :local: + +Known and Supported Encoders +-------------------------------------------------------------------------------- + +Be sure to read the `ODrive Encoder Guide `_. + +-------------------------------------------------------------------------------- + +All encoder types supported by ODrive require that you do some sort of encoder calibration. This requires the following: + +* Selecting an encoder and mounting it to your motor +* Choosing an interface (e.g., AB, ABI or SPI) +* Connecting the pins to the odrive +* Loading the correct odrive firmware (the default will work in many cases) +* Motor calibration +* Saving the settings in the odrive for correct bootup + +.. _encoder-without-index-signal: + +Encoder Without Index Signal +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +During encoder offset calibration the rotor must be allowed to rotate without any biased load during startup. +That means mass and weak friction loads are fine, but gravity or spring loads are not okay. + +In the :code:`odrivetool`, run + +.. code:: iPython + + .requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION + + +To verify everything went well, check the following variables: + + * :code:`.error` should be 0. + * :code:`.encoder.config.phase_offset` - This should print a number, like -326 or 1364. + * :code:`.encoder.config.direction` - This should print 1 or -1. + +.. _encoder-with-index-signal: + +Encoder With Index Signal +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you have an encoder with an index (Z) signal, you can avoid doing the offset calibration on every startup, and instead use the index signal to re-sync the encoder to a stored calibration. + +Below are the steps to do the one-time calibration and configuration. +Note that you can follow these steps with one motor at a time, or all motors together, as you wish. + +* Since you will only do this once, it is recommended that you mechanically disengage the motor from anything other than the encoder, so that it can spin freely. +* Set :code:`.encoder.config.use_index` to `True`. +* Run :code:`.requested_state = AXIS_STATE_ENCODER_INDEX_SEARCH`. + This will make the motor turn in one direction until it finds the encoder index. +* Follow the calibration instructions for an :ref:`encoder without index signal `. +* Set :code:`.encoder.config.pre_calibrated` to `True` to confirm that the offset is valid with respect to the index pulse. +* If you would like to search for the index at startup, set :code:`.config.startup_encoder_index_search` to `True`. + * If you'd rather do it manually, just run :code:`.requested_state = AXIS_STATE_ENCODER_INDEX_SEARCH` on every bootup. +* If you are looking to start your machine as quickly as possible on bootup, also set :code:`.motor.config.pre_calibrated` to `True` to save the current motor calibration and avoid doing it again on bootup. +* Save the configuration by typing :code:`.save_configuration()` :kbd:`Enter`. + +That's it, now on every reboot the motor will turn in one direction until it finds the encoder index. + +* If your motor has problems reaching the index location due to the mechanical load, you can increase :code:`.motor.config.calibration_current`. + +Reversing Index Search +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes you would like the index search to only happen in a particular direction (the reverse of the default). +Instead of swapping the motor leads, you can ensure that the following three values are negative: + +* :code:`.config.calibration_lockin.vel` +* :code:`.config.calibration_lockin.accel` +* :code:`.config.calibration_lockin.ramp_distance` + + +.. important:: + Your motor should find the same rotational position when the ODrive performs an index search if the index signal is working properly. + This means that the motor should spin, and stop at the same position if you have set :code:`.config.startup_encoder_index_search` so the search starts on reboot, or you if call the command :code:`.requested_state = AXIS_STATE_ENCODER_INDEX_SEARCH` after reboot. + You can test this. Send the :code:`.reboot()` command, and while it's rebooting turn your motor, then make sure the motor returns back to the correct position each time when it comes out of reboot. + Try this procedure a couple of times to be sure. + +.. _encoders-hall-effect: + +Hall Effect Encoders +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Hall effect encoders can also be used with ODrive. The encoder CPR should be set to `6 * <# of motor pole pairs>`. +Due to the low resolution of hall effect encoders compared to other types of encoders, low speed performance will be worse than other encoder types. + +When the encoder mode is set to hall feedback, the pinout on the encoder port is as follows: + + +.. list-table:: + :widths: 25 25 + :header-rows: 1 + + * - Label on ODrive + - Hall feedback + * - A + - Hall A + * - B + - Hall B + * - Z + - Hall C + +To use hall effect encoders, the calibration sequence is different than incremental or absolute encoders. +You must first run :code:`AXIS_STATE_ENCODER_HALL_POLARITY_CALIBRATION` before :code:`AXIS_STATE_ENCODER_OFFSET_CALIBRATION` The hall polarity calibration will automatically determine the order and polarity of the hall signals. +When using :code:`AXIS_STATE_FULL_CALIBRATION_SEQUENCE`, these steps are automatically used if the encoder is set to hall mode. + +Startup Sequence Notes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following are variables that MUST be set up for your encoder configuration. Your values will vary depending on your encoder: + +.. code:: iPython + + .encoder.config.cpr = 8192 + +.. code:: iPython + + .encoder.config.mode = ENCODER_MODE_INCREMENTAL + +The following are examples of values that can impact the success of calibration. +These are not all of the variables you have to set for startup. +Only change these when you understand why they are needed; your values will vary depending on your setup: + +* :code:`.motor.config.motor_type = MOTOR_TYPE_HIGH_CURRENT` The type of motor you have. Valid choices are high current or gimbal. +* :code:`.encoder.config.calib_range = 0.05` Helps to relax the accuracy of encoder counts during calibration +* :code:`.motor.config.calibration_current = 10.0` The motor current used for calibration. For large motors, this value can be increased to overcome friction and cogging. +* :code:`.motor.config.resistance_calib_max_voltage = 12.0` Max motor voltage used for measuring motor resistance. For motor calibration, it must be possible for the motor current to reach the calibration current without the applied voltage exceeding this config setting. +* :code:`.controller.config.vel_limit = 5` [turn/s] low values result in the spinning motor stopping abruptly during calibration. + +Lots of other values can get you. It's a process. Thankfully there are a lot of good people that will help you debug calibration problems. + +If calibration works, congratulations. + +Now try: + + +.. code:: iPython + + .requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL + .controller.input_vel = 1.5 + +let it loop a few times and then set: + + +.. code:: iPython + + .requested_state = AXIS_STATE_IDLE + +Do you still have no errors? Awesome. Now, setup the motor and encoder to use known calibration values. +This allows you to skip motor calibration and encoder offset calibration before using closed loop control. +Note that this only works if you are using an absolute encoder or the encoder index input (see "Encoder with index signal" above). + + +.. code:: iPython + + .encoder.config.pre_calibrated = True + .motor.config.pre_calibrated = True + +And see if ODrive agrees that the calibration worked by just running + + +.. code:: iPython + + .encoder.config.pre_calibrated + +.. note:: (using no "= True" ). Make sure that 'pre_calibrated' is in fact True. + +Also, if you have calibrated and encoder.pre_calibrated is equal to true, and you had no errors so far, run this: + + +.. code:: iPython + + odrv0.save_configuration() + odrv0.reboot() + +and now see if after a reboot you can run: + +.. code:: iPython + + .requested_state = AXIS_STATE_ENCODER_INDEX_SEARCH + +without getting errors. + +What Happens if Calibration Fails +-------------------------------------------------------------------------------- + +There are subtle ways that encoder problems will impact your ODrive. +For example, ODrive may not complete the calibrate sequence when you go to: + +.. code:: iPython + + .requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE + +Or, ODrive may complete the calibrate sequence after: + +.. code:: iPython + + .requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE + +but then it fails after you go to: + +.. code:: iPython + + .requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL + +Or ODrive may just vibrate in an entertaining way. See `this video. `_ + +Encoder Testing +-------------------------------------------------------------------------------- + +There are things you can test to make sure your encoder is properly connected. +:code:`shadow_count` tracks encoder motion, even before the encoder or motor are calibrated. +If your encoder is working, you should see this value change when you turn the motor. +Run the command: + + +.. code:: iPython + + .encoder.shadow_count + +and look at your value. Then turn your motor by hand and see if that value changes. Also, notice that the command: + + +.. code:: iPython + + .encoder.config.cpr = 4000 + +must reflect the number of counts ODrive receives after one complete turn of the motor. +So use shadow_count to test if that is working properly. + +You will probably never be able to properly debug if you have problems unless you use an oscilloscope. +If you have one, try the following: +Connect to the AB pins, see if you get square waves as you turn the motor. +Connect to the I pin, see if you get a pulse on a complete rotation. Sometimes this is hard to see. + +If you are using SPI, use a logic analyzer and connect to the CLK, MISO, and CS pins. +Set a trigger for the CS pin and ensure that the encoder position is being sent and is increasing/decreasing as you spin the motor. +There is extremely cheap hardware that is supported by `Sigrok `_ for protocol analysis. + + +Encoder Noise +-------------------------------------------------------------------------------- + +Noise is found in all circuits, life is just about figuring out if it is preventing your system from working. +Lots of users have no problems with noise interfering with their ODrive operation, others will tell you "`I've been using the same encoder as you with no problems`". +Power to 'em, that may be true, but it doesn't mean it will work for you. +If you are concerned about noise, there are several possible sources: + +* Importantly, encoder wires may be too close to motor wires, avoid overlap as much as possible +* Long wires between encoder and ODrive +* Use of ribbon cable + +The following **might** mitigate noise problems. +Use shielded cable, or use twisted pairs, where one side of each twisted pair is tied to ground and the other side is tied to your signal. +If you are using SPI, use a 20-50 ohm resistor in series on CLK, which is more susceptible noise. + +If you are using an encoder with an index signal, another problem that has been encountered is noise on the Z input of ODrive. +Symptoms for this problem include: + +* difficulty with :code:`requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE`, where your calibration sequence may not complete +* strange behavior after performing :code:`.save_configuration()` and :code:`.reboot()` +* when performing an index_search, the motor does not return to the same position each time. + +One easy step that **might** fix the noise on the Z input is to solder a 22nF-47nF capacitor to the Z pin and the GND pin on the underside of the ODrive board. + +Hall Feedback Pinout +-------------------------------------------------------------------------------- + +If position accuracy is not a concern, you can use A/B/C hall effect encoders for position feedback. + +To use this mode, configure the corresponding encoder mode: :code:`.config.mode = ENCODER_MODE_HALL`. +Configure the corresponding GPIOs as digital inputs: + +For encoder 0: + +.. code:: iPython + + .config.gpio9_mode = GPIO_MODE_DIGITAL + .config.gpio10_mode = GPIO_MODE_DIGITAL + .config.gpio11_mode = GPIO_MODE_DIGITAL + +For encoder 1: + +.. code:: iPython + + .config.gpio12_mode = GPIO_MODE_DIGITAL + .config.gpio13_mode = GPIO_MODE_DIGITAL + .config.gpio14_mode = GPIO_MODE_DIGITAL + + +In this mode, the pinout on the encoder port is as follows: + +.. list-table:: + :widths: 25 25 + :header-rows: 1 + + * - Label on ODrive + - Hall feedback + * - A + - Hall A + * - B + - Hall B + * - Z + - Hall C + + +SPI Encoders +-------------------------------------------------------------------------------- + +Apart from (incremental) quadrature encoders, ODrive also supports absolute SPI encoders (since firmware v0.5). +These usually measure an absolute angle. +This means you don't need to repeat the encoder calibration after every ODrive reboot. +Currently, the following modes are supported: + + * **CUI protocol**: Compatible with the AMT23xx family (AMT232A, AMT232B, AMT233A, AMT233B). + * **AMS protocol**: Compatible with AS5047P and AS5048A. + +Some of these chips come with evaluation boards that can simplify mounting the chips to your motor. +For our purposes if you are using an evaluation board you should select the settings for 3.3v. + +.. note:: + The AMT23x family has a hardware bug that causes them to not properly tristate the MISO line. + To use them with ODrive, there are two workarounds. + One is to sequence power to the encoder a second or two after the ODrive recieves power. + This allows 1 encoder to be used without issue. + Another solution is to add a tristate buffer, such as the 74AHC1G125SE, on the MISO line between the ODrive and each AMT23x encoder. + Tie the enable pin on the buffer to the CS line for the respective encoder. + This allows for more than one AMT23x encoder, or one AMT23x and another SPI encoder, to be used at the same time. + +#. Connect the encoder to the ODrive's SPI interface: + + * The encoder's SCK, MISO (aka "DATA" on CUI encoders), MOSI (if present on the encoder), GND and 3.3V should connect to the ODrive pins with the same label. + If you want to save a wire with AMS encoders, you can also connect the encoder's MOSI to the encoder's VDD instead. + * The encoder's Chip Select (aka nCS/CSn) can be connected to any of the ODrive's GPIOs (caution: GPIOs 1 and 2 are usually used by UART). + +If you are having calibration problems, make sure that your magnet is centered on the axis of rotation on the motor. +Some users report that this has a significant impact on calibration. +Also make sure that your magnet height is within range of the spec sheet. + +#. In :code:`odrivetool`, run: + + .. code:: iPython + + .encoder.config.abs_spi_cs_gpio_pin = 4 # or which ever GPIO pin you choose + .encoder.config.mode = ENCODER_MODE_SPI_ABS_CUI # or ENCODER_MODE_SPI_ABS_AMS + .encoder.config.cpr = 2**14 # or 2**12 for AMT232A and AMT233A + .save_configuration() + .reboot() + +#. Run the :ref:`offset calibration ` and then save the calibration with :code:`.save_configuration()`. + The next time you reboot, the encoder should be immediately ready. + +Sometimes the encoder takes longer than the ODrive to start, in which case you need to clear the errors after every restart. + +If you are having calibration problems - make sure your magnet is centered on the axis of rotation on the motor, some users report this has a significant impact on calibration. +Also make sure your magnet height is within range of the spec sheet. + diff --git a/docs/reStructuredText/_build/html/_sources/endstops.rst.txt b/docs/reStructuredText/_build/html/_sources/endstops.rst.txt new file mode 100644 index 00000000..56fcf119 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/endstops.rst.txt @@ -0,0 +1,226 @@ +================================================================================ +Endstops and Homing +================================================================================ + +.. contents:: + :depth: 1 + :local: + +By default, the ODrive assumes that your motor encoder's zero position is the same as your machine's zero position, but in real life this is rarely the case. +In these systems it is useful to allow your motor to move until a physical or electronic device orders the system to stop. +That `endstop` can be used as a known reference point. Once the ODrive has hit that position it may then want to move to a final zero, or `home`, position. +The process of finding your machine's zero position is known as `homing`. + +ODrive supports the use of its GPIO pins to connect to phyiscal limit switches or other sensors that can serve as endstops. +Before you can home your machine, you must be able to adequately control your motor in :code:`AXIS_STATE_CLOSED_LOOP_CONTROL`. + +Endstop Configuration +-------------------------------------------------------------------------------- + +Each axis supports two endstops: :code:`min_endstop` and :code:`max_endstop`. +For each endstop, the following properties are accessible through :code:`odrivetool`: + +.. list-table:: + :widths: 25 25 25 + :header-rows: 1 + + * - Name + - Type + - Default + * - gpio_num + - int + - 0 + * - offset + - float + - 0.0 + * - debounce_ms + - float + - 50.0 + * - enabled + - boolean + - false + * - is_active_high + - boolean + - false + + +:code:`gpio_num` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The GPIO pin number, according to the silkscreen labels on ODrive. Set with these commands: + +.. code:: iPython + + ..max_endstop.config.gpio_num = <1, 2, 3, 4, 5, 6, 7, 8> + ..min_endstop.config.gpio_num = <1, 2, 3, 4, 5, 6, 7, 8> + + +:code:`enabled` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Enables/disables detection of the endstop. If disabled, homing and e-stop cannot take place. Set with: + + +.. code:: iPython + + ..max_endstop.config.enabled = + ..min_endstop.config.enabled = + + +:code:`offset` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is the position of the endstops on the relevant axis, in turns. +For example, if you want a position command of `0` to represent a position 3 turns away from the endstop, the offset would be `-3.0` (because the endstop is located at axis position `-3.0`). + +.. code:: iPython + + ..min_endstop.config.offset = + + +This setting is only used for homing. Only the offset of the :code:`min_endstop` is used. + +:code:`debounce_ms` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The debouncing time for this endstop. Most switches exhibit some sort of bounce, and this setting will help prevent the switch from triggering repeatedly. +It works for both HIGH and LOW transitions, regardless of the setting of :code:`is_active_high`. +Debouncing is a good practice for digital inputs, read up on it `here `_. :code:`debounce_ms` has units of miliseconds. + +.. code:: iPython + + ..max_endstop.config.debounce_ms = + ..min_endstop.config.debounce_ms = + + +:code:`is_active_high` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is how you configure the endstop to be either "NPN" or "PNP". +An "NPN" configuration would be :code:`is_active_high = False` whereas a PNP configuration is `is_active_high = True`. +Refer to the following table for more information: + +Typically configuration **1** or **3** is preferred when using mechanical switches as the most common failure mode leaves the switch open. + +GPIO Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The GPIOs that are used for the endstops need to be configured according to the diagram below. + +Assuming your endstop is connected to GPIO X: + +* Configuration 1, 2: :code:`.config.gpioX_mode = GPIO_MODE_DIGITAL_PULL_DOWN` +* Configuration 3, 4: :code:`.config.gpioX_mode = GPIO_MODE_DIGITAL_PULL_DOWN` + +.. figure:: figures/Endstop_configuration.png + :scale: 50 % + :alt: Endstop configuration + +Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If we want to configure a 3D printer-style (configuration 4) minimum endstop for homing on GPIO 5 and we want our motor to move away from the endstop about a quarter turn with a 8192 cpr encoder, we would set: + +.. code:: iPython + + .config.gpio5_mode = GPIO_MODE_DIGITAL + ..min_endstop.config.gpio_num = 5 + ..min_endstop.config.is_active_high = False + ..min_endstop.config.offset = -1.0*(8912/4) + ..min_endstop.config.enabled = True + .config.gpio5_mode = GPIO_MODE_DIGITAL_PULL_UP + + +Testing The Endstops +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once the endstops are configured you can test your endstops for correct functionality. +Try activating your endstops and check the states of these variables through odrivetool: + +.. code:: iPython + + ..max_endstop.endstop_state + ..min_endstop.endstop_state + + +A state of `True` means the switch is pressed. A state of `False` means the switch is NOT pressed. +As simple as that. Give it a try. Click your switches, or put a magnet on your hall switch and see if the states change. + +After testing, don't forget to save and reboot: + +.. code:: iPython + + .save_configuration() + .reboot() + +Homing +-------------------------------------------------------------------------------- + +There is one additional configuration parameter in :code:`controller.config` specifically for the homing process: + +.. list-table:: + :widths: 25 25 25 + :header-rows: 1 + + * - Name + - Type + - Default + * - homing_speed + - float + - 0.25f + + + +:code:`homing_speed` is the axis travel speed during homing, in [turns/second]. +If you are using SPI based encoders and the axis is homing in the wrong direction, you can enter a negative value for the homing speed and a negative value for the minimum endstop offset. + +Set the homing speed to 0.25 turns / sec: + +.. code:: iPython + + odrv0.axis0.controller.config.homing_speed = 0.25 + + +Performing the Homing Sequence +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Homing is possible once the ODrive has closed-loop control over the axis. +To trigger homing, we must enter:code:`AXIS_STATE_HOMING`. This starts the homing sequence, which works as follows: + +#. The axis switches to :code:`INPUT_MODE_VEL_RAMP` +#. The axis ramps up to :code:`homing_speed` in the direction of :code:`min_endstop` +#. The axis presses the :code:`min_endstop` +#. The axis switches to :code:`INPUT_MODE_TRAP_TRAJ` +#. The axis moves to the home position in a controlled manner + +It requires quite a few settings in addition to the endstop settings: + +.. code:: iPython + + ..controller.config.vel_ramp_rate + ..trap_traj.config.vel_limit + ..trap_traj.config.accel_limit + ..trap_traj.config.decel_limit + + +We realize this is a little excessive and we will work towards minimizing the setup, but this works well for smooth and reliable behaviour for now. + +Homing at Startup +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is possible to configure the odrive to enter homing immediately after startup. +To enable homing at startup, the following must be configured: + +.. code:: iPython + + ..config.startup_homing = True + + +Additional Endstop Devices +-------------------------------------------------------------------------------- + +In addition to phyiscal switches there are other options for wiring up your endstops - you will have to work out the details of connecting your device but here are some suggested approaches: + +.. figure:: figures/endstop_figure.png + :scale: 100 % + :alt: endstop figure \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/_sources/fibre_types/com_odriverobotics_ODrive.rst.txt b/docs/reStructuredText/_build/html/_sources/fibre_types/com_odriverobotics_ODrive.rst.txt new file mode 100644 index 00000000..3a47d8ca --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/fibre_types/com_odriverobotics_ODrive.rst.txt @@ -0,0 +1,11 @@ +ODrive Reference +================ + +.. fibreclass:: com.odriverobotics.ODrive + + +.. fibrenamespace:: com.odriverobotics.ODrive + :bitfields: + :enums: + :classes: + :namespaces: diff --git a/docs/reStructuredText/_build/html/_sources/getting-started.rst.txt b/docs/reStructuredText/_build/html/_sources/getting-started.rst.txt new file mode 100644 index 00000000..7efa2398 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/getting-started.rst.txt @@ -0,0 +1,553 @@ +================================================================================ +Getting Started +================================================================================ + +.. contents:: + :depth: 1 + :local: + +Hardware Requirements +-------------------------------------------------------------------------------- + +**You will need** + +* One or two `brushless motors `__. It is fine, even recommended, to start testing with just a single motor and encoder. +* One or two `encoder(s) `__. +* A power supply (12V-24V for the 24V board variant, 12V-56V for the 56V board variant). A battery is also fine. Some advice on choosing a power supply can be found `here `__. + +.. dropdown:: What voltage variant do I have? + + On all ODrives shipped July 2018 or after have a silkscreen label clearly indicating the voltage variant. + + ODrives before this may or may not have this label. If you don't have a label, then you can look at the bus capacitors (8 gray cylinder components on the underside of the board). If they read 470uF, you have a 24V version; if they read 120uF you have a 48V version. + +* A power resistor. A good starting point would be the 50W resistor included with your ODrive. + +.. dropdown:: Do I really need a power resistor? What values to choose? + + If you don't have a brake resistor, the ODrive will pump excess power back into the power supply during deceleration to achieve the desired deceleration torque. If your power supply doesn't eat that power (which it won't if it's not a battery), the bus voltage will inevitebly rise. If you're unlucky this will break the power supply. + At some point, the ODrive's overvoltage protection will trip, after which both motors will be allowed to spin freely. Depending on your machine, this may or may not be a problem. + + The power resistor values you need depends on your motor setup, and peak/average deceleration power. + + To be on the safe side, think about what speed and current limits you want to set for the motor. + + When braking at max speed and with maximum motor current, the power that is dissipated in the power resistor can be calulated as: `P_brake = V_emf * I_motor` where `V_emf = motor_rpm / motor_kv`. + + + +Wiring up the ODrive +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. warning:: Firmware, software, and documentation is intended for use with ODrive motor controllers purchased from odriverobotics.com. ODrive Robotics does not sell products through any channel other than odriverobotics.com. We do not provide support for ODrives purchased elsewhere. + +.. warning:: Make sure you have a good mechanical connection between the encoder and the motor, slip can cause disastrous oscillations or runaway. + +All non-power I/O is 3.3V output and 5V tolerant on input, on ODrive v3.3 and newer. + +Wiring up the motors +******************************************************************************** + +Connect the motor phases into the 3-phase screw terminals. It is not recommended to use a clip-on connector such as an alligator clip, as this can cause issues with the phase resistance/inductance measurements. + +Wiring up the encoders +******************************************************************************** + +Connect the encoder(s) to J4. The A,B phases are required, and the Z (index pulse) is optional. The A,B and Z lines have 3.3k pull up resistors, for use with open-drain encoder outputs. For single ended push-pull signals with weak drive current (\<4mA), you may want to desolder the pull-ups. + +.. image:: figures/ODriveBasicWiring.png + :scale: 40 % + :align: center + +Safety & Power UP +******************************************************************************** +.. warning:: Always think safety before powering up the ODrive if motors are attached. Consider what might happen if the motor spins as soon as power is applied. + +* Unlike some devices, the ODrive does not recieve power over the USB port so the 24/56 volt power input is required even just to communicate with it using USB. It is ok to power up the ODrive before or after connecting the USB cable. + +* To power up the ODrive, connect the power source to the DC terminals. Make sure to pay attention to the polarity. Try to connect the power source first and then turn it on to avoid inrush current. If this can't be avoided then a small spark is normal. This is caused by the capacitors charging up. + +* Make sure to avoid a ground loop! See the :ref:`ground loop page ` for details. + +.. _install-odrivetool: + +Downloading and Installing :code:`odriveool` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Most instructions in this guide refer to a utility called `odrivetool`, so you should install that first. + +.. tabs:: + .. tab:: Windows + + #. Install Python 3 + + We recommend the Anaconda distribution because it packs a lot of useful scientific tools, however you can also install the standalone python. + + * **Anaconda**: Download the installer from `here `__. Execute the downloaded file and follow the instructions. + * **Standalone Python**: Download the installer for 3.8.6 from `here `__. Execute the downloaded file and follow the instructions. As of Oct 2020, Matplotlib (required by odrivetool) had not been updated to work with 3.9, so please use 3.8.6. + * If you have Python 2 installed alongside Python 3, replace :code:`pip` by :code:`C:\Users\YOUR_USERNAME\AppData\Local\Programs\Python\Python36-32\Scripts\pip`. If you have trouble with this step then refer to `this walkthrough `__. + + #. Launch the command prompt. + + * **Anaconda**: In the start menu, type :code:`Anaconda Prompt` :kbd:`Enter` + * **Standalone Python** In the start menu, type :code:`cmd` :kbd:`Enter` + + #. Install odrivetool by running + + .. code-block:: Bash + + pip install --upgrade odrive + + .. tab:: OSX + + We are going to run the following commands for installation in Terminal. + + #. If you don't already have it, install homebrew: + + .. code-block:: Bash + + /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + + + #. Install python: + + .. code-block:: Bash + + brew install python + + .. note:: + If you get an error saying `python 2.7.* is already installed`, then upgrade to Python 3 by running: + + .. code-block:: Bash + + brew upgrade python + + #. Install libusb: + + libusb is required for odrivetool to communicate with the ODrive: + + .. code-block:: Bash + + brew install libusb + + .. _installodrivetool: + + #. Install odrivetool: + + Now that you have Python 3 and all the package managers, run: + + .. code-block:: Bash + + pip3 install --upgrade odrive + + **Troubleshooting** + + #. Permission Errors: + + Just run the previous command in sudo + + .. code-block:: Bash + + sudo pip3 install --upgrade odrive + + + #. Dependency Errors: + + If the installer doesn't complete and you get a dependency error (Ex. :code:`No module...`` or :code:`module_name not found`), run the command + + .. code-block:: Bash + + sudo pip3 install module_name + + and try to :ref:`install odrivetool ` again. + + #. Other Install Errors: + + If the installer fails at installing dependencies, try + + .. code-block:: Bash + + sudo pip3 install odrive --no-deps + + .. warning:: If you do this, brace yourself for runtime errors when you run :code:`odrivetool` (the basic functionality should work though). + + + .. tab:: Linux + + #. `Install Python 3 `__. + + For example, on Ubuntu, run + + .. code-block:: Bash + + sudo apt install python3 python3-pip + + + #. Install odrivetool by opening a terminal and typing + + .. code-block:: Bash + + sudo pip3 install --upgrade odrive + + .. note:: This should automatically add the udev rules. If this fails for some reason you can add them manually: + + .. code-block:: Bash + + echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="0d[0-9][0-9]", MODE="0666"' | sudo tee /etc/udev/rules.d/91-odrive.rules + sudo udevadm control --reload-rules + sudo udevadm trigger + + .. admonition:: **Ubuntu**, **Raspbian** + + If you can't invoke :code:`odrivetool` at this point, try adding :code:`~/.local/bin` to your :code:`$PATH`, `see related bug `__. + This is done for example by running :code:`nano ~/.bashrc`, scrolling to the bottom, pasting :code:`export PATH=$PATH:~/.local/bin`, and then saving and closing, then close and reopen the terminal window. + +Firmware +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To simplify the start up procedure we suggest using the latest firmware verion. Please follow the instructions :ref:`here ` to make sure your firmware is up to date. + +.. admonition:: **ODrive v3.5 and later** + + Your board should come preflashed with the latest firmware. If you run into problems, check your firmware version and upgrade if possible. + +.. admonition:: **ODrive v3.4 and earlier** + + Your board does **not** come preflashed with any firmware. Follow the instructions :ref:`here ` on the ST Link procedure before you continue. + + +.. _odrivetool-startup: + +Start :code:`odrivetool` +-------------------------------------------------------------------------------- + +* To launch the main interactive ODrive tool, type :code:`odrivetool` and :kbd:`Enter`. +* Connect your ODrive and wait for the tool to find it. If it dos not connect after a few seconds refer to the :ref:`troubleshooting guide `. +* Now you can, for instance type :code:`odrv0.vbus_voltage` :kbd:`Enter` to inspect the boards main supply voltage. + +It should look something like this: + +.. code:: iPython + + ODrive control utility v0.5.4 + Please connect your ODrive. + Type help() for help. + + Connected to ODrive 306A396A3235 as odrv0 + In [1]: odrv0.vbus_voltage + Out[1]: 11.97055721282959 + +The tool you're looking at is a fully capable Python command prompt, so you can type any valid python code. +You can read more about :code:`odrivetool` :ref:`here `. + +.. _motor-config: + +Motor Configuration +-------------------------------------------------------------------------------- + +.. warning:: Read this section carefully, else you risk breaking something. + +Configuration of a motor is done within the :code:`odrivetool` console, type :code:`odrivetool` and :kbd:`Enter` to launch. Make sure you have completed :ref:` Start odrivetool ` before continuing. + +This section assumes that the motor being configured is connected to the ODrive as M0 (:code:`axis0`). To configure M1, simply replace all instances of :code:`axis0` with :code:`axis1`. + +If any of the following steps fail, print the errors by running :code:`dump_errors(odrv0)` and refer to the :ref:`Error codes ` section for debugging. +Once the error(s) have been identified and corrected, run :code:`odrv0.clear_errors()` to clear them before moving forward. + +.. admonition:: Working on a hoverboard? + + There is a :ref:`separate guide ` specifically for hoverboard motors + + +Setting the Limits +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +With :code:`odrivetool` open you can assign variables directly by name. +For instance, to set the current limit of M0 to 10A you would type + +.. code:: iPython + + odrv0.axis0.motor.config.current_lim = 10 + +Current limit +********************************************************************************* + + The default current limit, for safety reasons, is set to 10A. This is quite weak, but good for making sure the drive is stable. + To change the current limit, + + .. code:: iPython + + odrv0.axis0.motor.config.current_lim = val + + Once you have tuned the ODrive, you can increase this to 60A to increase performance. + + .. dropdown:: Need more than 60A? + + For current limits above 60A, you must change the current amplifier gains. + You do this by requesting a different current range. i.e. for 90A on M0 + + .. code:: iPython + + odrv0.axis0.motor.config.requested_current_range = 90 + + + then :ref:`save the configuration and reboot ` as the gains are written out to the DRV (MOSFET driver) only during startup. + + .. Note:: The motor current and the current drawn from the power supply is not the same in general. You should not look at the power supply current to see what is going on with the motor current.* + + .. dropdown:: Ok, so tell me how it actually works then... + + The current in the motor is only connected to the current in the power supply `sometimes` and other times it just cycles out of one phase and back in the other. + This is what the modulation magnitude is (sometimes people call this duty cycle, but that's a bit confusing because we use `SVM `__ not straight PWM). + When the modulation magnitude is 0, the average voltage seen across the motor phases is 0, and the motor current is never connected to the power supply. + When the magnitude is 100%, it is always connected, and at 50% it's connected half the time, and cycled in just the motor half the time. + + The largest effect on modulation magnitude is speed. There are other smaller factors, but in general: if the motor is still it's not unreasonable to have 50A in the motor from 5A on the power supply. + When the motor is spinning close to top speed, the power supply current and the motor current will be somewhat close to each other. + +Velocity limit +********************************************************************************* + +The motor will be limited to this speed in [turn/s]. Again the default value is quite slow. + +.. code:: iPython + + odrv0.axis0.controller.config.vel_limit = 2 + + +Calibration Current +********************************************************************************* + +You can change :code:`odrv0.axis0.motor.config.calibration_current` [A] to the largest value you feel comfortable leaving running through the motor continuously when the motor is stationary. +If you are using a small motor (i.e. 15A current rated) you may need to reduce :code:`calibration_current` to a value smaller than the default. + + +Setting Other Hardware Parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Enable Brake Resistor +********************************************************************************* + +Set this to `True` if using a brake resistor. You need to :ref:`save the configuration and reboot ` the ODrive for this to take effect. + +.. code:: iPython + + odrv0.config.enable_brake_resistor + + +Brake Resistor Value +********************************************************************************* + +This is the resistance [Ohms] of the brake resistor. You can leave this at the default setting if you are not using a brake resistor. + +.. code:: iPython + + odrv0.config.brake_resistance + + +.. note:: There may be some extra resistance in your wiring and in the screw terminals, so if you are getting issues while braking you may want to increase this parameter by around 0.05 ohm. + + +Negative Current +********************************************************************************* + +This is the amount of current [Amps] allowed to flow back into the power supply. The convention is that it is negative. By default, it is set to a conservative value of 10mA. If you are using a brake resistor and getting :code:`DC_BUS_OVER_REGEN_CURRENT` errors, raise it slightly. +If you are not using a brake resistor and you intend to send braking current back to the power supply, set this to a safe level for your power source. Note that in that case, it should be higher than your motor current limit + current limit margin. + +.. code:: iPython + + odrv0.config.dc_max_negative_current + + +Pole Pairs +********************************************************************************* + +This is the number of **magnet poles** in the rotor, **divided by two**. To find this, you can simply count the number of permanent magnets in the rotor, if you can see them. + +.. code:: iPython + + odrv0.axis0.motor.config.pole_pairs + + +.. Note:: This is **not** the same as the number of coils in the stator. + +A good way to find the number of pole pairs is with a current limited power supply. Connect any two of the three phases to a power supply outputting around 2A, spin the motor by hand, and count the number of detents. +This will be the number of pole pairs. If you can't distinguish the detents from the normal cogging present when the motor is disconnected, increase the current. +Another way is sliding a loose magnet in your hand around the rotor, and counting how many times it stops. This will be the number of `pole pairs`. If you use a ferrous piece of metal instead of a magnet, you will get the number of `magnet poles`. + +Torque Constant +********************************************************************************* + +This is the ratio of torque produced by the motor per Amp of current delivered to the motor. This should be set to **8.27 / (motor KV)**. + +.. code:: iPython + + odrv0.axis0.motor.config.torque_constant + + +If you decide that you would rather command torque in units of Amps, you could simply set the torque constant to 1. + +Motor Type +********************************************************************************* + +This is the type of motor being used. Currently two types of motors are supported: High-current motors (`MOTOR_TYPE_HIGH_CURRENT, [0]`) and gimbal motors (`MOTOR_TYPE_GIMBAL, [2]`). + +.. code:: iPython + + odrv0.axis0.motor.config.motor_type + + +.. dropdown:: Which :code:`motor_type` to choose? + + If you're using a regular hobby brushless motor like `this `__ one, you should set :code:`motor_mode` to :code:`MOTOR_TYPE_HIGH_CURRENT`. + For low-current gimbal motors like `this `__ one, you should choose :code:`MOTOR_TYPE_GIMBAL`. Do not use :code:`MOTOR_TYPE_GIMBAL` on a motor that is not a gimbal motor, as it may overheat the motor or the ODrive. + + **Further detail:** + If 100's of mA of current noise is "small" for you, you can choose :code:`MOTOR_TYPE_HIGH_CURRENT`. + If 100's of mA of current noise is "large" for you, and you do not intend to spin the motor very fast (Ω * L << R), and the motor is fairly large resistance (1 ohm or larger), you can chose :code:`MOTOR_TYPE_GIMBAL`. + If 100's of mA current noise is "large" for you, `and` you intend to spin the motor fast, then you need to replace the shunt resistors on the ODrive. + + +.. note:: + When using gimbal motors, :code:`current_lim` `and` :code:`calibration_current` actually mean `voltage limit` and `calibration voltage` respectively, since we don't use current feedback. + This means that if you set it to 10, it means 10V, despite the name of the parameter. + + +Motor Thermistor +********************************************************************************* + +If using motor thermistor please see the :ref:`thermistors ` page for setup. + + + +Encoder Configuration +-------------------------------------------------------------------------------- + +With Encoder +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Set the encoder count per revolution [CPR] value + +.. code:: iPython + + odrv0.axis0.encoder.config.cpr + +This is 4x the Pulse Per Revolution (PPR) value. Usually this is indicated in the datasheet of your encoder. +If you purchased an encoder from our shop, you can find the parameters `here `__. + + +Without Encoder +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* If you wish to run in sensorless mode, please see :ref:`Setting up sensorless. ` +* If you are using hall sensor feedback, please see the :ref:`hoverboard motor example `. + +.. _save-config: + +Save Configuration +-------------------------------------------------------------------------------- + +You can save all :code:`.config` parameters to persistent memory so the ODrive remembers them between power cycles. This will reboot the board. + +.. code:: iPython + + odrv0.save_configuration() + + +Position control of M0 +-------------------------------------------------------------------------------- +Let's get motor 0 up and running. The procedure for motor 1 is exactly the same, so feel free to substitute :code:`axis1` wherever it says :code:`axis0`. + + #. Start the calibratrion sequence by entering + + .. code:: iPython + + odrv0.axis0.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE + + and press :kbd:`Enter`. + + After about 2 seconds you should hear a beep. Then the motor will turn slowly in one direction for a few seconds, then back in the other direction. + + .. dropdown:: What's the point of this? + + This procedure first measures your motor's electrical properties (namely phase resistance and phase inductance) and then the offset between the motor's electrical phase and the encoder position. + + The startup procedure is demonstrated `here `__. + + .. note:: + The rotor must be allowed to rotate without any biased load during startup. + That means mass and weak friction loads are fine, but gravity or spring loads are not okay. + Also note that in the video, the motors spin after initialization, but in the current software the default behaviour is not like that. + + .. dropdown:: Help, something isn't working! + + Check the encoder wiring and that the encoder is firmly connected to the motor. Check the value of :code:`dump_errors(odrv0)` and then refer to the :ref:`error code documentation ` for details. + Once you understand the error and have fixed its cause, you may clear the error state with (:code:`odrv0.clear_errors()` :kbd:`Enter`) and retry. + + + #. Start closed loop control by typing + + .. code:: iPython + + odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL + + and press :kbd:`Enter`. From now on the ODrive will try to hold the motor's position. If you try to turn it by hand, it will fight you gently. That is unless you bump up :code:`odrv0.axis0.motor.config.current_lim`, in which case it will fight you more fiercely. + If the motor begins to vibrate either immediately or after being disturbed you will need to :ref:`lower the controller gains `. + + + #. Send the motor a new position setpoint with + + .. code:: iPython + + odrv0.axis0.controller.input_pos = 1 + + The units are in turns. + + #. At this point you will probably want to :ref:`Properly tune ` the motor controller in order to maximize system performance. + + +Other Control Modes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default control mode is unfiltered position control in the absolute encoder reference frame. +The ODrive can be used with a variety of control modes, to learn more, check out the :ref:`control modes ` documentation page. + +Watchdog Timer +-------------------------------------------------------------------------------- + +Each axis has a configurable watchdog timer that can stop the motors if the +control connection to the ODrive is interrupted. + +Each axis has a configurable watchdog timeout: :code:`axis.config.watchdog_timeout`, +measured in seconds. Set + +.. code:: iPython + + axis.config.enable_watchdog = True + +to turn on this feature. + +The watchdog is fed using the :code:`axis.watchdog_feed()` method of each axis. +Some :ref:`ascii commands ` feed the watchdog automatically. + +What's next? +-------------------------------------------------------------------------------- + +You can now: +* :ref:`Properly tune ` the motor controller to unlock the full potential of the ODrive. +* See what other :ref:`commands and parameters ` are available, in order to better control the ODrive. +* Control the ODrive from your own program or hook it up to an existing system through one of it's :ref:`interfaces `. +* See how you can improve the behavior during the startup procedure, like :ref:`bypassing encoder calibration `. +* The CAN communication is the most reliable way of talking to ODrive in a real application. Check out the :ref:`CAN Guide ` and :ref:`CAN Protocol ` + +If you have any issues or any questions please get in touch. The `ODrive Community `__ warmly welcomes you. + + +Upgrading from 0.4.12 +-------------------------------------------------------------------------------- + +A new version (0.5.4) of ODrive firmware has released, complete with a new odrivetool. +Follow the installation instructions, making sure to add the :code:`--upgrade` flag to pip commands, and check out the `Changelog `_ for changes! + +The odrivetool will stage and restore your configuration. +This probably isn't wise for the 0.4.12 -> 0.5.1 upgrade, so we suggest using :code:`odrv0.erase_configuration()` immediately after connecting the first time. diff --git a/docs/reStructuredText/_build/html/_sources/ground-loops.rst.txt b/docs/reStructuredText/_build/html/_sources/ground-loops.rst.txt new file mode 100644 index 00000000..7ee93566 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/ground-loops.rst.txt @@ -0,0 +1,78 @@ + +.. _ground-loops: + +================================================================================ +Ground Loops +================================================================================ + +.. contents:: + :depth: 1 + :local: + +For electrical devices to communicate, most of the time they require a common ground connection. +Best practice is to connect the grounds back to a single point, called a "star ground". +If there are multiple paths to ground, a "ground loop" is formed. +Ground loops and wire inductance can cause issues for high current electronics like ODrive. +As an example of what can go wrong, look at the diagram below. + +The Problem +-------------------------------------------------------------------------------- + +.. image:: figures/ground_loop_bad.png + :scale: 80 % + :align: center + :alt: Ground Loop with inductance + +The issue is the inductance of the power wires between the ODrive and power supply. +The inductance and the high current drawn by the ODrive causes V_1 to not be the same as V_2. +If the voltage caused by the wire inductance and current is high enough, the 0-5V gpio signals can swing much higher or lower than the normal 0-5V range. +This causes a current to flow through the ODrive GPIO pins. + +Solutions +-------------------------------------------------------------------------------- + +Reduce Power Wire +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +All wires have some amount of inductance. The inductance is proportional to the length of the wires and the area of the loop formed by the positive and negative power wires. +It is beneficial to keep those wires as short as possible and as close together as possible. This reduces the effect of the problem but does not eliminate it! + +Isolation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To fix this, the ground loop must be broken. This can be achieved by isolating the power supplies (no common V-) and connecting a signal ground between the RPi and ODrive. +An example of this is a single ODrive connected to a battery and a device like a RPi connected to a mains power supply or different battery. +If more than one ODrive is in use and they share a power supply, you have a ground loop again. + +The best way to fix this is to isolate the data connection between the RPi and ODrive(s). The diagram below illustrates where the isolator should go. + + +.. image:: figures/ground_loop_fix.png + :scale: 80 % + :align: center + :alt: Ground Loop fixed by isolator + +By isolating the data connection (whether it is GPIO, USB, or UART), the ground loop is broken. +Isolation can be achieved by using a USB isolator or a signal isolator for GPIO connections. + +Here are some examples of USB isolators: + +* `Isolator 1 `_ +* `Isolator 2 `_ + +These are generic devices. If Aliexpress is not an option for you, you can probably find them available in your area from a different vendor. +In the US, these types of isolators are available from Amazon and Ebay. + +For GPIO connections, like UART, Step/dir, PWM, etc, you can use signal isolators like the ISO7762F from Texas Instruments. +Keep in mind that isolators have a speed limit. Devices like optocouplers might be too slow for UART connections or Step/direction. +Check the datasheet! + +Current Limiting +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If isolators are not an option, you can use series resistors to limit the injection current to a safe level. Place a resistor on the recieving side of all connections to or from the ODrive GPIO pins. +4.7kOhms is a good value, but anything from 3.3kOhms to 10kOhms should work. Series resistors offer some protection for the ODrive, but the ground loop problem can still cause the GPIOs to be pulled high or low for short periods of time. +The ODrive and your other device will most likely be safe but communications might be interrupted. + +As an example, for UART, you would place a resistor close to the RX pin of the ODrive and another one close to the RX pin of the other device (like an Arduino). +This allows the driving side, the TX pins, to adequately drive the bus capacitance. diff --git a/docs/reStructuredText/_build/html/_sources/hoverboard.rst.txt b/docs/reStructuredText/_build/html/_sources/hoverboard.rst.txt new file mode 100644 index 00000000..0ef39a3e --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/hoverboard.rst.txt @@ -0,0 +1,299 @@ + +.. _hoverboard-doc: + +================================================================================ +Hoverboard motor and remote control setup guide +================================================================================ + +.. contents:: + :depth: 1 + :local: + +By popular request here follows a step-by-step guide on how to setup the ODrive to drive hoverboard motors using RC PWM input. +Each step is accompanied by some explanation so hopefully you can carry over some of the steps to other setups and configurations. + +.. raw:: html + + + + + +.. [![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/ponx_U4xhoM/0.jpg)](https://www.youtube.com/watch?v=ponx_U4xhoM)
Click above to play video. + +Hoverboard Motor Wiring +------------------------------------------------------------------------------- + +Hoverboard motors come with three motor phases (usually colored yellow, blue, green) which are thicker, and a set of 5 thinner wires for the hall sensor feedback (usually colored red, yellow, blue, green, black). + +You may wire the motor phases in any order into a motor connector on the ODrive, as we will calibrate the phase alignment later anyway. Wire the hall feedback into the ODrive J4 connector (make sure that the motor channel number matches) as follows: + +.. list-table:: + :widths: 25 25 + :header-rows: 1 + + * - Hall wire + - J4 signal + * - Red + - 5V + * - Yellow + - A + * - Blue + - B + * - Green + - Z + * - Black + - GND + + +.. note:: + In order to be compatible with encoder inputs, the ODrive doesn't have any filtering capacitors on the pins where the hall sensors connect. + Therefore to get a reliable hall signal, it is recommended that you add some filter capacitors to these pins. + We recommend about 22nF between each signal pin and GND. + You can see instructions `here `__. + + +Hoverboard Motor Configuration +------------------------------------------------------------------------------- + +Standard 6.5 inch hoverboard hub motors have 30 permanent magnet poles, and thus 15 pole pairs. + If you have a different motor you need to count the magnets or have a reliable datasheet for this information. + +.. code:: Bash + + odrv0.axis0.motor.config.pole_pairs = 15 + + + +Hoverboard hub motors are quite high resistance compared to the hobby aircraft motors, so we want to use a bit higher voltage for the motor calibration, and set up the current sense gain to be more sensitive. +The motors are also fairly high inductance, so we need to reduce the bandwidth of the current controller from the default to keep it stable. +The KV rating of the motor also should be known. It can be measured using the "drill test", detailed `here `__. +If you can't perform this test, a typical value is 16. + +.. code:: Bash + + odrv0.axis0.motor.config.resistance_calib_max_voltage = 4 + odrv0.axis0.motor.config.requested_current_range = 25 #Requires config save and reboot + odrv0.axis0.motor.config.current_control_bandwidth = 100 + odrv0.axis0.motor.config.torque_constant = 8.27 / + + +If you set the encoder to hall mode (instead of incremental). +See the :ref:`pinout ` for instructions on how to plug in the hall feedback. +The hall feedback has 6 states for every pole pair in the motor. +Since we have 15 pole pairs, we set the cpr to `15*6 = 90`. +Since hall sensors are low resolution feedback, we also bump up the offset calibration displacement to get better calibration accuracy. + +.. code:: Bash + + odrv0.axis0.encoder.config.mode = ENCODER_MODE_HALL + odrv0.axis0.encoder.config.cpr = 90 + odrv0.axis0.encoder.config.calib_scan_distance = 150 + odrv0.config.gpio9_mode = GPIO_MODE_DIGITAL + odrv0.config.gpio10_mode = GPIO_MODE_DIGITAL + odrv0.config.gpio11_mode = GPIO_MODE_DIGITAL + + +Since the hall feedback only has 90 counts per revolution, we want to reduce the velocity tracking bandwidth to get smoother velocity estimates. +We can also set these fairly modest gains that will be a bit sloppy but shouldn't shake your rig apart if it's built poorly. +Make sure to tune the gains up when you have everything else working to a stiffness that is applicable to your application. +Lets also start in velocity control mode since that is probably what you want for a wheeled robot. Note that in velocity mode :code:`pos_gain` isn't used but I have given you a recommended value anyway in case you wanted to run position control mode. + + .. note:: + The gains used here are dependent on the :code:`torque_constant` and :code:`cpr` config settings. The values for hoverboard motors are **very different** from the stock settings. Do not skip the above steps and go straight to these settings! + + +.. code:: Bash + + odrv0.axis0.encoder.config.bandwidth = 100 + odrv0.axis0.controller.config.pos_gain = 1 + odrv0.axis0.controller.config.vel_gain = 0.02 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr + odrv0.axis0.controller.config.vel_integrator_gain = 0.1 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr + odrv0.axis0.controller.config.vel_limit = 10 + odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL + + +In the next step we are going to start powering the motor and so we want to make sure that some of the above settings that require a reboot are applied first. + +.. code:: Bash + + odrv0.save_configuration() + odrv0.reboot() + + +Make sure the motor is free to move, then activate the motor calibration. + +.. code:: Bash + + odrv0.axis0.requested_state = AXIS_STATE_MOTOR_CALIBRATION + + +You can read out all the data pertaining to the motor: + +.. code:: Bash + + odrv0.axis0.motor + +Check to see that there is no error and that the phase resistance and inductance are reasonable. Here are the results I got: + +.. code:: Bash + + error = 0x0000 (int) + phase_inductance = 0.00033594953129068017 (float) + phase_resistance = 0.1793474406003952 (float) + + +If all looks good then you can tell the ODrive that saving this calibration to persistent memory is OK: + +.. code:: Bash + + odrv0.axis0.motor.config.pre_calibrated = True + + +Next step is to check the alignment between the motor and the hall sensor. +Because of this step you are allowed to plug the motor phases in random order and also the hall signals can be random. +Just don't change it after calibration. + +Make sure the motor is free to move and run: + +.. code:: Bash + + odrv0.axis0.requested_state = AXIS_STATE_ENCODER_HALL_POLARITY_CALIBRATION + + +Check the status of the encoder object: + +.. code:: Bash + + odrv0.axis0.encoder + + +Check that there are no errors. + +.. code:: Bash + + error = 0x0000 (int) + + +If the hall encoder polarity calibration was successful, run the encoder offset calibration. + +.. code:: Bash + + odrv0.axis0.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION + + +Check the status of the encoder object: + +.. code:: Bash + + odrv0.axis0.encoder + + +Check that there are no errors. +If your hall sensors has a standard timing angle then :code:`phase_offset_float` should be close to 0.5 mod 1. Meaning values close to -1.5, -0.5, 0.5, or 1.5, etc are all good. + + +.. code:: Bash + + error = 0x0000 (int) + config: + phase_offset_float = 0.5126956701278687 (float) + + +If all looks good then you can tell the ODrive that saving this calibration to presistent memory is OK: + + +.. code:: Bash + + odrv0.axis0.encoder.config.pre_calibrated = True + + +OK, we are now done with the motor configuration! Time to save, reboot, and then test it. +The ODrive starts in idle (we will look at changing this later) so we can enable closed loop control. + + +.. code:: Bash + + odrv0.save_configuration() + odrv0.reboot() + odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL + odrv0.axis0.controller.input_vel = 2 + # Your motor should spin here + odrv0.axis0.controller.input_vel = 0 + odrv0.axis0.requested_state = AXIS_STATE_IDLE + + +Hopefully you got your motor to spin! Feel free to repeat all of the above for the other axis if appropriate. + +PWM Input +------------------------------------------------------------------------------- + +If you want to drive your hoverboard wheels around with an RC remote control you can use the :ref: `RC PWM input `. There is more information in that link. +Lets use GPIO 3/4 for the velocity inputs so that we don't have to disable UART. +Then let's map the full stick range of these inputs to some suitable velocity setpoint range. +We also have to reboot to activate the PWM input. + +.. code:: Bash + + odrv0.config.gpio3_pwm_mapping.min = -2 + odrv0.config.gpio3_pwm_mapping.max = 2 + odrv0.config.gpio3_pwm_mapping.endpoint = odrv0.axis0.controller._input_vel_property + + odrv0.config.gpio4_pwm_mapping.min = -2 + odrv0.config.gpio4_pwm_mapping.max = 2 + odrv0.config.gpio4_pwm_mapping.endpoint = odrv0.axis1.controller._input_vel_property + + +.. code:: Bash + + odrv0.save_configuration() + odrv0.reboot() + + +Now we can check that the sticks are writing to the velocity setpoint. +Move the stick, print :code:`input_vel`, move to a different position, check again. + +.. code:: Bash + + In [1]: odrv0.axis1.controller.input_vel + Out[1]: 0.01904754638671875 + + In [2]: odrv0.axis1.controller.input_vel + Out[2]: 0.01904754638671875 + + In [3]: odrv0.axis1.controller.input_vel + Out[3]: 1.152389526367188 + + In [4]: odrv0.axis1.controller.input_vel + Out[4]: 1.81905517578125 + + In [5]: odrv0.axis1.controller.input_vel + Out[5]: -0.990474700927734 + +Ok, now we should be able to turn on the drive and control the wheels! + +.. code:: Bash + + odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL + odrv0.axis1.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL + + +Safety +------------------------------------------------------------------------------- + +Be sure to setup the Failsafe feature on your RC Receiver so that if connection is lost between the remote and the receiver, the receiver outputs 0 and 0 for the velocity setpoint of both axes (or whatever is safest for your configuration). Also note that if the receiver turns off (loss of power, etc) or if the signal from the receiver to the ODrive is lost (wire comes unplugged, etc), the ODrive will continue the last commanded velocity setpoint. There is currently no timeout function in the ODrive for PWM inputs. + +Automatic Startup +------------------------------------------------------------------------------- + +Try to reboot and then activate :code:`AXIS_STATE_CLOSED_LOOP_CONTROL` on both axis. +Check that everything is operational and works as expected. +If so, you can now make the ODrive turn on the motor power automatically after booting. +This is useful if you are going to be running the ODrive without a PC or other logic board. + +.. code:: Bash + + odrv0.axis0.config.startup_closed_loop_control = True + odrv0.axis1.config.startup_closed_loop_control = True + odrv0.save_configuration() + odrv0.reboot() + diff --git a/docs/reStructuredText/_build/html/_sources/index.rst.txt b/docs/reStructuredText/_build/html/_sources/index.rst.txt new file mode 100644 index 00000000..85ceee11 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/index.rst.txt @@ -0,0 +1,65 @@ +.. ODrive Documentation documentation master file, created by + sphinx-quickstart on Wed Nov 3 20:01:31 2021. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +.. include:: getting-started.rst + + +.. toctree:: + :hidden: + :maxdepth: 1 + :caption: General + + getting-started + odrivetool + control-modes + commands + encoders + control + troubleshooting + specifications + ground-loops + +.. toctree:: + :hidden: + :maxdepth: 1 + :caption: Tutorials + + Hoverboard Guide + migration + CAN Guide + + +.. toctree:: + :hidden: + :maxdepth: 1 + :caption: Interfaces and Protocols + + protocol + Pinout + usb + uart + native-protocol + ascii-protocol + can-protocol + Step & Direction + rc-pwm + analog-input + endstops + thermistors + + +.. fibreautosummary:: com.odriverobotics.ODrive + :caption: ODrive Device API + + +.. toctree:: + :hidden: + :maxdepth: 1 + :caption: For ODrive Developers + + developer-guide + configuring-vscode + configuring-eclipse + diff --git a/docs/reStructuredText/_build/html/_sources/migration.rst.txt b/docs/reStructuredText/_build/html/_sources/migration.rst.txt new file mode 100644 index 00000000..7bd6cb7d --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/migration.rst.txt @@ -0,0 +1,126 @@ +================================================================================ +Migration Guide +================================================================================ + +.. contents:: + :depth: 1 + :local: + +v0.5.1 -> v0.5.2 +-------------------------------------------------------------------------------- + +The change from v0.5.1 to v0.5.2 had fewer breaking changes than the v0.4.12 to v0.5.1 change. + +GPIO Modes +-------------------------------------------------------------------------------- + +The GPIO configuration is now more explicit. For example, to use :code:`gpio1` for for step signals (as part of a step/dir interface), it must be set to + +.. code:: iPython + + odrv0.config.gpio1_mode = GPIO_MODE_DIGITAL + + +Braking Behavior +-------------------------------------------------------------------------------- + +Before using the brake resistor, it must be explicitly enabled as follows: + +.. code:: iPython + + odrv0.config.enable_brake_resistor = True + +and then save the configuration and reboot for the setting to take effect. + +Step/Direction Settings +-------------------------------------------------------------------------------- + +Previously, steps were added incrementally to :code:`input_pos`. +This caused issues with accumulated floating point rounding error. +Now, an absolute step count is used. +This change requires that the circular setpoints mode is used :code:`odrv0.axis0.controller.config.circular_setpoints = True` when step/dir signals are used. + +In addition, :code:`odrv0.axis0.config.turns_per_step` has been removed and :code:`odrv0.axis0.controller.config.steps_per_circular_range` is used. + +For example: + +previously: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: iPython + + odrv0.axis0.config.turns_per_step = 1.0/1024.0 + +v0.5.2 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: iPython + + odrv0.axis0.controller.config.circular_setpoints = True + odrv0.axis0.controller.config.circular_setpoint_range = 1.0 + odrv0.axis0.controller.config.steps_per_circular_range = 1024 + +For best results, set both the circular range and steps per circular range to powers of 2. + +API changes +-------------------------------------------------------------------------------- + +For other API changes, see the Changelog file on github. + +v0.4.12 -> v0.5.1 +-------------------------------------------------------------------------------- + +Certain changes occurred between firmware versions v0.4.12 and v0.5.1 that will break existing configurations. +This document is a guide for how to take a working v0.4.12 ODrive config and change it to work with firmware v0.5.1. + +Unit Changes +-------------------------------------------------------------------------------- + +ODrive now uses units of [turns], [turns/s], and [turns/s^2] instead of [counts], [counts/s], and [counts/s^2]. +In addition, the motor controller class now has an input command of torque in [Nm] instead of current in [Amps]. +In general, every user-facing parameter that has to do with position or velocity is affected by the unit change. + +.. note:: + For the torque to be in correct in [Nm] you need to configure the :code:`motor.config.torque_constant`. + See the updated :ref:`getting started ` for more details. + +Control Parameter Names +-------------------------------------------------------------------------------- + +ODrive now uses :code:`input_pos`, :code:`input_vel`, and :code:`input_torque` as commands instead of :code:`pos_setpoint`, :code:`vel_setpoint`, and :code:`current_setpoint`. + +Guide +-------------------------------------------------------------------------------- + +For a working v0.4.12 ODrive configuration, use the following equations to convert parameters as required. + +* :code:`pos_gain` is unaffected ( [counts/s / count] `*`> [turns/s / turns] ) +* :code:`vel_gain` is :code:`vel_gain_old * torque_constant * encoder cpr` +* :code:`vel_integrator_gain` is :code:`vel_integrator_gain_old * torque_constant * encoder cpr` + +For other values, [turns] = [counts] / [encoder cpr]. Converting [counts/s] and [counts/s^2] is similar. + +Affected Variables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* :code:`axis.controller.input_pos` +* :code:`axis.controller.input_vel` +* :code:`axis.controller.input_torque` +* :code:`axis.controller.config.vel_limit` +* :code:`axis.controller.config.vel_ramp_rate` +* :code:`axis.controller.config.current_ramp_rate` is now :code:`axis.controller.config.torque_ramp_rate` +* :code:`axis.controller.config.circular_setpoint_range` +* :code:`axis.controller.config.inertia` +* :code:`axis.controller.config.homing_speed` +* :code:`axis.controller.pos_setpoint` +* :code:`axis.controller.vel_setpoint` +* :code:`axis.controller.torque_setpoint` instead of :code:`axis.controller.current_setpoint` +* :code:`axis.trap_traj.config.vel_limit` +* :code:`axis.trap_traj.config.accel_limit` +* :code:`axis.trap_traj.config.decel_limit` +* :code:`axis.encoder.pos_estimate` +* :code:`axis.encoder.pos_estimate_circular` +* :code:`axis.encoder.vel_estimate` +* :code:`axis.config.counts_per_step` is now :code:`turns_per_step` for the step/direction interface +* :code:`axis.sensorless_estimator.vel_estimate` is in mechanical [turns/s] instead of electrical [radians/s] + diff --git a/docs/reStructuredText/_build/html/_sources/native-protocol.rst.txt b/docs/reStructuredText/_build/html/_sources/native-protocol.rst.txt new file mode 100644 index 00000000..122eef27 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/native-protocol.rst.txt @@ -0,0 +1,32 @@ +.. _native-protocol: + +================================================================================ +Native Protocol +================================================================================ + +This protocol is what the :code:`odrivetool` uses to talk to the ODrive. +If you have a choice, this is the recommended protocol for all applications. +The native protocol runs on :ref:`USB ` and on :ref:`UART `. + +Python +-------------------------------------------------------------------------------- + +The :code:`odrivetool` you installed as part of the :ref:`Getting Started guide ` comes with a library that you can use to easily control the ODrive from Python. + +Assuming you already installed the odrive library (:code:`pip install odrive`), the simplest program to control the ODrive is this: + +.. code:: Python + + import odrive + odrv0 = odrive.find_any() + print(str(odrv0.vbus_voltage)) + + +For a more comprehensive example, see `tools/odrive_demo.py `_. + +Other Languages +-------------------------------------------------------------------------------- + +We don't have an official library for you just yet. Check the community, there might be someone working on it. +If you want to write a library yourself, refer to the :ref:`native protocol specification `. +You are of course welcome to contribute it back. \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/_sources/odrivetool.rst.txt b/docs/reStructuredText/_build/html/_sources/odrivetool.rst.txt new file mode 100644 index 00000000..f2d170e6 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/odrivetool.rst.txt @@ -0,0 +1,395 @@ +.. _odrivetool-doc: + +================================================================================ +:code:`odrivetool` +================================================================================ + +.. contents:: + :depth: 1 + :local: + +The :code:`odrivetool` is the accompanying PC program for the ODrive. It's main purpose is to provide an interactive shell to control the device manually, as well as some supporting functions like firmware update. + +Installation +------------------------------------------------------------------------------- + +Refer to the :ref:`Getting Started guide `. + +Type :code:`odrivetool --help` to see what features are available. + +Multiple ODrives +------------------------------------------------------------------------------- + +By default, :code:`odrivetool` will connect to any ODrive it finds. +If this is not what you want, you can select a specific ODrive. + +To find the serial number of your ODrive, run :code:`odrivetool`, connect exactly one ODrive and power it up. +You should see this: + +.. code:: iPython + + Connected to ODrive 306A396A3235 as odrv0 + In [1]: + +:code:`306A396A3235` is the serial number of this particular ODrive. +If you want :code:`odrivetool` to ignore all other devices you would close it and then run: + +.. code:: Bash + + odrivetool --serial-number 306A396A3235 + +.. dropdown:: My ODrive is stuck in DFU mode, can I still find the serial number? + + Yes, the serial number is part of the USB descriptors. + In Linux you can find it by running: + + .. code:: Bash + + (sudo lsusb -d 1209:0d32 -v; sudo lsusb -d 0483:df11 -v) 2>/dev/null | grep iSerial + + This should output something like: + + .. code:: Bash + + iSerial 3 385F324D3037 + iSerial 3 306A396A3235 + + Here, two ODrives are connected. + +Configuration Backup +------------------------------------------------------------------------------- + +You can use :code:`odrivetool` to back up and restore device configurations or transfer the configuration of one ODrive to another one. + + * To save the configuration to a file on the PC, run + + .. code:: iPython + + odrivetool backup-config my_config.json + + * To restore the configuration form such a file, run + + .. code:: iPython + + odrivetool restore-config my_config.json + +.. note:: + + The encoder offset calibration is not restored because this would be dangerous if you transfer the calibration values of one axis to another axis. + +.. _firmware-update: + +Device Firmware Update +------------------------------------------------------------------------------- + +.. attention:: + + DFU is not supported on ODrive v3.4 or earlier. You need to :ref:`flash with the external programmer ` instead. + +To update the ODrive to the newest firmware release, simply open up a terminal and run the following command: + +.. code:: Bash + + odrivetool dfu + +You should then see + +.. code:: iPython + + ODrive control utility v0.3.7.dev + Waiting for ODrive... + Found ODrive 308039673235 (v3.5-24V) with firmware v0.3.7-dev + Checking online for newest firmware... found v0.3.7 + Downloading firmware... + Putting device 308039673235 into DFU mode... + Erasing... done + Flashing... done + Verifying... done + + +.. note:: This command will connect to GitHub servers to retrieve the latest firmware. + +If you have a non-default configuration saved on the device, :code:`odrivetool` will try to carry over the configuration across the firmware update. +If any of the settings are removed or renamed, you will get warning messages. + +Flashing Custom Firmware +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to flash a specific firmware file instead of automatically downloading one, you can run + +.. code:: Bash + + odrivetool dfu path/to/firmware/file.hex + +You can download one of the officially released firmware files from `here `__. +.. You will need one of the :code:`.hex` files (not the :code:`.elf`file). +On Windows you will need one of the :code:`.hex` files, and for Linux and Mac you will want the :code:`.elf` file. +Make sure you select the file that matches your board version. + +To compile firmware from source, refer to the :ref:`developer guide `. + +Troubleshooting +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. tabs:: + .. tab:: **Windows** + + During the update, a new device called "STM32 BOOTLOADER" will appear. + Open the `Zadig utility `_ and set the driver for "STM32 BOOTLOADER" to libusb-win32. + After that the firmware update will continue. + + .. tab:: **Linux** + + Try running :code:`sudo odrivetool dfu` instead of :code:`odrivetool dfu`. + +On some machines you will need to unplug and plug back in the USB cable to make the PC understand that we switched from regular mode to bootloader mode. + +.. _force-dfu: + +**Forcing DFU Mode** + +If the DFU script can't find the device, try forcing it into DFU mode: + +.. tabs:: + .. tab:: ODrive v3.5 and newer + + Flick the DIP switch that says "DFU, RUN" to "DFU" and power cycle the board. + If that alone doesn't work, also connect the pin "GPIO6" to "GND". + After you're done upgrading firmware, don't forget to put the switch back into the "RUN" position and power cycle the board again. + + .. tab:: ODrive v3.1, v3.2 + + Connect the pin "BOOT0" to "3.3V" and power cycle the board. + If that alone doesn't work, also connect the pin "GPIO1" to "GND". + After you're done, remove the wires and power cycle the board again. + + +.. _st-link-flash: + +Alternative DFU Tool +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some people have had issues using the python dfu tool, so below is a guide on how to manually use different tools. + +Before starting the below steps, you need to get firmware binary. +You can download one of the officially released firmware files from `here `__. +Make sure you select the file that matches your board version. +On Windows you will need one of the :code:`.hex` files, and for Linux and Mac you will want the :code:`.elf` file. + +To compile firmware from source, refer to the :ref:`developer guide `. + +**Multi-platform** + +ST has a tool called STM32CubeProgrammer. + +#. Download the tool `here `__. + You will need to make an account with ST to download the tool. +#. Install the tool. On Windows, make sure to let it make a desktop shortcut. +#. Force the ODrive into :ref:`DFU mode. ` +#. Launch STM32CubeProgrammer. +#. Under "Memory & File edition", there are two tabs called "Device memory" and "Open file". + Click "Open file" and choose the ODrive firmware hex file that you downloaded or compiled. +#. In the top right, there is a dropdown menu containing the different methods to connect to an STM32 device. Choose "USB". +#. Under "USB configuration", a USB port should be automatically selected and the ODrive serial number should be present next to "Serial number." +#. Click "Connect" above "USB configuration". +#. Click the tab with the name of your firmware file (example: :code:`ODriveFirmware_v3.6-56V.hex`) if it is not already selected. +#. Click "Download" to flash your ODrive with the firmware. Your ODrive is now flashed! +#. Close STM32CubeProgrammer. +#. Turn off the power to the ODrive and set the DIP swtich back to RUN mode. + +.. tabs:: + .. tab:: **Windows** + + You can use the DfuSe app from ST. + + #. Download the tool `here `__. + Unfortunately they make you create a login to download. Sorry about that. + #. After installing the tool, launch :code:`DfuFileMgr.exe` which probably got added to the start menu as "Dfu file manager". + #. Select "I want to GENERATE a DFU file from S19, HEX or BIN files", press :kbd:`OK`. + #. Click the button that says "S19 or Hex...", find the :code:`ODriveFirmware.hex` file you built or downloaded. + #. Leave all the other settings as default and click the "Generate..." button. + #. Save the output file as :code:`ODriveFirmware.dfu`. Note that the success message has a warning sign for some reason... + #. Launch :code:`DfuSeDemo.exe` which probably got added to the start menu as "DfuSeDemo". + #. Force the ODrive into DFU mode, as per the instructions above "How to force DFU mode". + #. In the top left it should now be connected to "STM Device in DFU Mode". + #. If it doesn't appear, it may be because the driver is set to libusb by Zadig. We need to set it back to the original driver. + Follow `these instructions `_. + #. If, after doing the above step, the ODrive still installs itself as a libusb device in Device Manager, you can try to delete the libusb driver (this is OK, since we can use Zadig to install it again). + You can simply delete the file :code:`C:\Windows\System32\drivers\libusb0.sys`. + #. In the bottom right section called "Upgrade or Verify Action" click the button "Choose...". + #. Locate the :code:`ODriveFirmware.dfu` we made before. + #. Click button "Upgrade". + #. If you get a warning that it's not possible to check that it's the correct device type: click yes to continue. + #. Congratulations your ODrive should now be flashed; you can now quit DfuSeDemo. + #. Turn off the power to the ODrive and set the DIP switch back to RUN mode. + + .. tab:: **Linux** + + Install :code:`dfu-util` + + .. code:: Bash + + sudo apt install dfu-util + + :ref:`Force DFU mode. ` + + In the Firmware directory, after finishing building the firmware run: + + .. code:: Bash + + sudo dfu-util -a 0 -s 0x08000000 -D build/ODriveFirmware.bin + + .. tab:: **macOS** + + First, you need to install the arm development tools to copy the binary into the appropriate format: + + .. code:: Bash + + brew install --cask gcc-arm-embedded + + Then convert the binary to .bin format: + + .. code:: Bash + + arm-none-eabi-objcopy -O binary ODriveFirmware_v3.5-48V.elf ODriveFirmware_v3.5-48V.bin + + Install :code:`dfu-util` + + .. code:: Bash + + brew install dfu-util + + .. note:: If Using MacPorts + + Instead run: + + .. code:: Bash + + sudo port install dfu-util + + Put the ODrive into DFU mode using the DIP switch, then turn it on and plug in the USB. + Find the correct device serial number using: + + .. code:: Bash + + dfu-util --list + + This should return something like: + + .. code:: Bash + + Found DFU: [0483:df11] ver=2200, devnum=5, cfg=1, intf=0, path="20-2", alt=0, + name="@Internal Flash /0x08000000/04*016Kg,01*064Kg,07*128Kg", serial="388237123123" + + Finally, flash the firmware using the found serial number: + + .. code:: Bash + + sudo dfu-util -S 388237123123 -a 0 -s 0x08000000 -D ODriveFirmware_v3.5-48V.bin + +Flashing with an STLink +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This procedure is only necessary for ODrive v3.4 or earlier. You will need an STLink/v2 or compatible programmer. You should have received one with your ODrive. + +#. Install OpenOCD + * **Windows:** `instructions `_ (also follow the instructions on the ST-LINK/V2 drivers) + * **Linux:** :code:`sudo apt-get install openocd` + * **macOS:** :code:`brew install openocd` +#. Download the latest firmware release form `here `__. You will need the :code:`.elf` file. + Make sure you select the file that matches your board version. +#. Wire up the ODrive and STLink/v2 programmer as shown in this picture + + .. figure:: figures/stlink-wiring.jpg + :scale: 18 % + + Stlink Wiring Diagram + + and power up the ODrive. + +#. Open up a terminal and navigate to the directory where the firmware is. +#. Run the following command (replace :code:`ODriveFirmware_v3.4-24V.elf` with the name of your firmware file): + + .. code:: Bash + + openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c init -c "reset halt" -c "flash write_image erase ODriveFirmware_v3.4-24V.elf" -c "reset run" -c exit + + If everything worked correctly, you should see something similar to this towards the end of the printout: + + .. code:: Bash + + wrote 262144 bytes from file ODriveFirmware_v3.4-24V.elf in 10.194110s (25.113 KiB/s) + + If something doesn't work, make sure :code:`openocd` is in your :code:`PATH` variable, check that the wires are connected properly and try with elevated privileges. + +Liveplotter +------------------------------------------------------------------------------- + +Liveplotter is used for the graphical plotting of odrive parameters (i.e. position) in real time. +To start liveplotter, close any other instances of liveplotter and run + +.. code:: Bash + + odrivetool liveplotter + +from a new anaconda prompt window. By default two parameters are plotted on startup; the encoder position of axis 1 and axis 2. +In the below example the motors are running in :code:`closed_loop_control` while they are being forced off position by hand. + +.. figure:: figures/liveplotter-pos-estimate.png + :scale: 100 % + +To change what parameters are plotted open odrivetool (located in :code:`Anaconda3\Scripts` or :code:`ODrive-master\tools`) with a text editor and modify the liveplotter function: + +.. code:: iPython + + # If you want to plot different values, change them here. + # You can plot any number of values concurrently. + cancellation_token = start_liveplotter(lambda: [ + odrv0.axis0.encoder.pos_estimate, + odrv0.axis1.encoder.pos_estimate, + ]) + +For example, to plot the approximate motor torque [Nm] and the velocity [RPM] of axis0, you would modify the function to read: + +.. code:: iPython + + # If you want to plot different values, change them here. + # You can plot any number of values concurrently. + cancellation_token = start_liveplotter(lambda: [ + ((odrv0.axis0.encoder.vel_estimate*60), # turns/s to rpm + ((odrv0.axis0.motor.current_control.Iq_setpoint * my_odrive.axis0.motor.config.torque_constant), # Torque [Nm] + ]) + +In the example below the motor is forced off axis by hand and held there. +In response the motor controller increases the torque (orange line) to counteract this disturbance up to a peak of 500 N.cm at which point the motor current limit is reached. +When the motor is released it returns back to its commanded position very quickly as can be seen by the spike in the motor velocity (blue line). + +.. figure:: figures/liveplotter-iq-omega.png + :scale: 100 % + + Liveplotter Torque Velocity Plot + +To change the scale and sample rate of the plot modify the following parameters located at the beginning of utils.py (located in :code:`Anaconda3\Lib\site-packages\odrive`): + +.. code:: python + + data_rate = 100 + plot_rate = 10 + num_samples = 1000 + + +For more examples on how to interact with the plotting functionality refer to these `Matplotlib examples. `_ + +Liveplotter from Interactive :code:`odrivetool` Instance +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also run :code:`start_liveplotter(...)` directly from the interactive odrivetool prompt. +This is useful if you want to issue commands or otherwise keep interacting with the odrive while plotting. + +For example you can type the following directly into the interactive prompt: + +.. code:: iPython + + start_liveplotter(lambda: [odrv0.axis0.encoder.pos_estimate]) + +Just like the examples above, you can list several parameters to plot separated by comma in the square brackets. +In general, you can plot any variable that you are able to read like normal in odrivetool. + diff --git a/docs/reStructuredText/_build/html/_sources/pinout.rst.txt b/docs/reStructuredText/_build/html/_sources/pinout.rst.txt new file mode 100644 index 00000000..5ab79ec5 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/pinout.rst.txt @@ -0,0 +1,39 @@ +.. _pinout-chart: + +================================================================================ +ODrive v3.x Pinout +================================================================================ + +.. contents:: + :depth: 1 + :local: + +.. ODrive v4.1 +.. -------------------------------------------------------------------------------- + +.. **TODO** + +.. ODrive v3.x +.. -------------------------------------------------------------------------------- + +.. csv-table:: Table Title + :file: figures/pinout.csv + :header-rows: 1 + +**key:** + +* **(*)** ODrive v3.5 and later. + +* **(+)** On ODrive v3.5 and later these pins have noise suppression filters. This is useful for step/dir input. + +Notes +-------------------------------------------------------------------------------- + +* Changes to the pin configuration only take effect after :code:`odrv0.save_configuration()` and :code:`odrv0.reboot()` +* Bold font marks the default configuration. +* If a GPIO is set to an unsupported mode it will be left uninitialized. +* When setting a GPIO to a special purpose mode (e.g. :code:`GPIO_MODE_UART_A`) you must also enable the corresponding feature (e.g. :code:`.config.enable_uart_a`). +* Digital mode is a general purpose mode that can be used for these functions: step, dir, enable, encoder index, hall effect encoder, SPI encoder nCS. +* You must also connect GND between ODrive and your other board. +* ODrive v3.3 and onward have 5V tolerant GPIO pins. +* Simultaneous operation of UART_A and UART_B is currently not supported. diff --git a/docs/reStructuredText/_build/html/_sources/protocol.rst.txt b/docs/reStructuredText/_build/html/_sources/protocol.rst.txt new file mode 100644 index 00000000..a0fda573 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/protocol.rst.txt @@ -0,0 +1,110 @@ +.. _protocol-doc: + +================================================================================ +ODrive Communication Protocol +================================================================================ + +.. contents:: + :depth: 1 + :local: + +Communicating with an ODrive consists of a series of endpoint operations. +An endpoint can theoretically be any kind data serialized in any way. +There is a default serialization implementation for POD types; for custom types +you must (de)serialize yourself. In the future we may provide a default serializer +for structs. +The available endpoints can be enumerated by reading the JSON from endpoint 0 +and can theoretically be different for each communication interface (they are not in practice). + +Each endpoint operation can send bytes to one endpoint (referenced by its ID) +and at the same time receive bytes from the same endpoint. The semantics of +these payloads are specific to each endpoint's type, the name of which is +indicated in the JSON. + +For instance an int32 endpoint's input and output is a 4 byte little endian +representation. In general the convention for combined read/write requests is +`exchange`, i.e. the returned value is the old value. Custom endpoint handlers +may be non-compliant. + +There is a packet based version and a stream based variant of the protocol. Each +variant is employed as appropriate. For instance USB runs the packet based variant +by default while UART runs the stream based variant. + + +Packet Format +-------------------------------------------------------------------------------- + +We will call the ODrive "server" and the PC "client". A request is a message +from the PC to the ODrive and a response is a message from the ODrive to the +PC. + +Each request-response transaction corresponds to a single endpoint operation. + +**Request** +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + * **Bytes 0, 1** Sequence number, MSB = 0 + * Currently the server does not care about ordering and does not filter resent messages. + + * **Bytes 2, 3** Endpoint ID + * The IDs of all endpoints can be obtained from the JSON definition. The JSON definition can be obtained by reading from endpoint 0. + If (and only if) the MSB is set to 1 the client expects a response for this request. + + * **Bytes 4, 5** Expected response size + * The number of bytes that should be returned to the client. If the client doesn't need any response data, it can set this value to 0. The operation will still be acknowledged if the + MSB in EndpointID is set. + + * **Bytes 6 to N-3** Payload + * The length of the payload is determined by the total packet size. The format of the payload depends on the endpoint type. The endpoint type can be obtained from the JSON definition. + + * **Bytes N-2, N-1** + * For endpoint 0: Protocol version (currently 1). A server shall ignore packets with other values. + * For all other endpoints: The CRC16 calculated over the JSON definition using the algorithm described below, except that the initial value is set to the protocol version (currently 1). A server shall ignore packets that set this field incorrectly. + +**Response** +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + * **Bytes 0, 1** Sequence number, MSB = 1 + * The sequence number of the request to which this is the response. + + * **Bytes 2, 3** Payload + * The length of the payload tends to be equal to the number of expected bytes as indicated + in the request. The server must not expect the client to accept more bytes than it requested. + +Stream Format +-------------------------------------------------------------------------------- + +The stream based format is just a wrapper for the packet format. + + * **Byte 0** Sync byte `0xAA` + * **Byte 1** Packet length + * Currently both parties shall only emit and accept values of 0 through 127. + + * **Byte 2** CRC8 of bytes 0 and 1 (see below for details) + * **Bytes 3 to N-3** Packet + * **Bytes N-2, N-1** CRC16 (see below for details) + +CRC Algorithms +-------------------------------------------------------------------------------- + +**CRC8** +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + * Polynomial: `0x37` + * Initial value: `0x42` + * No input reflection, no result reflection, no final XOR operation + * Examples: + * `0x01, 0x02, 0x03, 0x04` => `0x61` + * `0x05, 0x04, 0x03, 0x02, 0x01` => `0x64` + +**CRC16** +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + * Polynomial: `0x3d65` + * Initial value: `0x1337` (or `0x0001` for the JSON CRC) + * No input reflection, no result reflection, no final XOR operation + * Examples: + * `0x01, 0x02, 0x03, 0x04` => `0x672E` + * `0x05, 0x04, 0x03, 0x02, 0x01` => `0xE251` + +You can use this `online calculator `__ to verify your implementation. diff --git a/docs/reStructuredText/_build/html/_sources/rc-pwm.rst.txt b/docs/reStructuredText/_build/html/_sources/rc-pwm.rst.txt new file mode 100644 index 00000000..2ba29f49 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/rc-pwm.rst.txt @@ -0,0 +1,47 @@ +.. _rc-pwm-doc: + +================================================================================ +RC PWM input +================================================================================ + +.. contents:: + :depth: 1 + :local: + +You can control the ODrive directly from a hobby RC receiver. + +Any of the numerical parameters that are writable from the ODrive Tool can be hooked up to a PWM input. +The :ref:`Pinout ` tells you which pins are PWM input capable. As an example, we'll configure GPIO4 to control the angle of axis 0. +We want the axis to move within a range of -2 to 2 turns. + +#. Make sure you're able control the axis 0 angle by writing to :code:`odrv0.axis0.controller.input_pos`. + If you need help with this follow the :ref:`getting started guide `. +#. If you want to control your ODrive with the PWM input without using anything else to activate the ODrive, you can configure the ODrive such that axis 0 automatically goes operational at startup. + See :ref:`here ` for more information. +#. In ODrive Tool, configure the PWM input mapping + + .. code:: iPython + + odrv0.config.gpio4_mode = GPIO_MODE_PWM + odrv0.config.gpio4_pwm_mapping.min = -2 + odrv0.config.gpio4_pwm_mapping.max = 2 + odrv0.config.gpio4_pwm_mapping.endpoint = odrv0.axis0.controller._input_pos_property + + .. note:: + + you can disable the input by setting :code:`odrv0.config.gpio4_pwm_mapping.endpoint = None` + +#. Save the configuration and reboot + + .. code:: iPython + + odrv0.save_configuration() + odrv0.reboot() + +#. With the ODrive powered off, connect the RC receiver ground to the ODrive's GND and one of the RC receiver signals to GPIO4. + You may try to power the receiver from the ODrive's 5V supply if it doesn't draw too much power. Power up the the RC transmitter. + You should now be able to control axis 0 from one of the RC sticks. + +Be sure to setup the Failsafe feature on your RC Receiver so that if connection is lost between the remote and the receiver, the receiver outputs 0 for the velocity setpoint of both axes (or whatever is safest for your configuration). +Also note that if the receiver turns off (loss of power, etc) or if the signal from the receiver to the ODrive is lost (wire comes unplugged, etc), the ODrive will continue the last commanded velocity setpoint. +There is currently no timeout function in the ODrive for PWM inputs. \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/_sources/specifications.rst.txt b/docs/reStructuredText/_build/html/_sources/specifications.rst.txt new file mode 100644 index 00000000..83fc40d9 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/specifications.rst.txt @@ -0,0 +1,50 @@ +================================================================================ +Specifications +================================================================================ + +.. contents:: + :depth: 1 + :local: + +Electrical Specifications +-------------------------------------------------------------------------------- + +Besides the input voltage range, (12V to 24V for ODrive v3.6 24V, 12V to 56V for ODrive v3.6 56V), the electrical specifications of both version of ODrive v3.6 are the same. + +.. note:: ODrive versions after v3.5 are closed-source with respect to board files and schematics. + +ODrive v3.6 24V and 56V +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Peak current per motor: 120 Amps +* Max continuous current depends on cooling. See `this `__ for more details. + * Heatsink in still air: 40A per channel + * Heatsink with basic fan cooling: 75A per channel + * Heatsink with overkill fan cooling: 90A per channel +* Max motor RPM: This depends on your power supply voltage, motor, and encoder. It is the lesser of: + * motor RPM limit + * encoder RPM limit + * motor KV * 0.7 * Supply voltage + * 35000 eRPM / # of motor pole pairs + * (840M counts/minute) / encoder counts per revolution (for incremental encoders - 4 x pulses per revolution). + +Schematic +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The electrical schematic for ODrive v3.5 is available `here `__ in PDF format. + +Mechanical Specifications +-------------------------------------------------------------------------------- + +STEP File +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A step file for ODrive v3.5 is available `here `__. + +Board Outline and Mounting Hole Dimensions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. image:: figures/mech_dimensions.png + :scale: 30 % + :align: center + :alt: board dimensions diff --git a/docs/reStructuredText/_build/html/_sources/step-direction.rst.txt b/docs/reStructuredText/_build/html/_sources/step-direction.rst.txt new file mode 100644 index 00000000..de446fcb --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/step-direction.rst.txt @@ -0,0 +1,67 @@ +================================================================================ +Step/Direction +================================================================================ + +.. contents:: + :depth: 1 + :local: + +This is the simplest possible way of controlling the ODrive. +It is also the most primitive and fragile one. So don't use it unless you must interoperate with other hardware that you don't control. + +Pinout +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Step/dir signals: Any GPIOs can be used. Also see :ref:`Pinout ` for more info. +* GND: you must connect the grounds of the devices together. Use any GND pin on J3 of the ODrive. + +How to Configure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + + #. Choose any two of the unused GPIOs for step/dir input. + Let's say you chose GPIO7 for the step signal and GPIO8 for the dir signal. + #. Configure the GPIO modes: + + .. code:: iPython + + .config.gpio7_mode = GPIO_MODE_DIGITAL_PULL_DOWN + .config.gpio8_mode = GPIO_MODE_DIGITAL + + #. Configure the axis: + + .. code:: iPython + + .config.step_gpio_pin = 7 + .config.dir_gpio_pin = 8 + .config.enable_step_dir = True + + #. Enable circular setpoints + + .. code:: iPython + + .controller.config.circular_setpoints = True + +After this, step and direction will be enabled when you put the axis into closed loop control. +Note that to change out of step/dir, you need to set :code:`.config.enable_step_dir = False`, go to :code:`AXIS_STATE_IDLE`, and then back into closed loop control. + +Circular setpoints are used to keep floating point error at a manageable error for systems where the motor can rotate large amounts. +If the motor is commanded out of the circular range, the position setpoint automatically wraps around to stay in the range. +Two parameters are used to control this behavior: :code:`..controller.config.circular_setpoint_range` and :code:`..controller.config.steps_per_circular_range`. +The circular setpoint range sets the operating range of input_pos, starting at 0.0. The :code:`steps per circular range` setting controls how many steps are needed to traverse the entire range. + +For example, to use 1024 steps per 1 full motor turn, set + +.. code:: iPython + + ..controller.config.circular_setpoint_range = 1.0 #[turns] + ..controller.config.steps_per_circular_range = 1024 #[steps] + + +The circular range is a floating point value and the steps per circular range parameter is an integer. +For best results, set both parameters to powers of 2. + +The maximum step rate is pending tests, but 250kHz step rates with both axes in closed loop has been achieved. + +Please be aware that there is no enable line right now, and the step/direction interface is enabled by default, and remains active as long as the ODrive is in position control mode. +To get the ODrive to go into position control mode at bootup, see how to configure the :ref:`startup procedure `. diff --git a/docs/reStructuredText/_build/html/_sources/testing.rst.txt b/docs/reStructuredText/_build/html/_sources/testing.rst.txt new file mode 100644 index 00000000..1ab39827 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/testing.rst.txt @@ -0,0 +1,211 @@ + +Automated Testing +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. contents:: + :depth: 1 + :local: + +This section describes how to use the automated testing facilities. +You don't have to do this as an end user. + +The testing facility consists of the following components: + + * **Test rig:** In the simplest case this can be a single ODrive optionally with a single motor and encoder pair. Can also be multiple ODrives with multiple axes, some of which may be mechanically coupled. + * **Test host:** The PC on which the test script runs. All ODrives must be connected to the test host via USB. + * **test-rig.yaml:** Describes your test rig. Make sure all values are correct. Incorrect values may physically break or fry your test setup. + * **test_runner.py:** This is the main script that runs all the tests. + * **..._test.py** The actual tests + +The Tests +******************************************************************************** + + * :code:`analog_input_test.py`: Analog Input + * :code:`calibration_test.py`: Motor calibration, encoder offset calibration, encoder direction find, encoder index search + * :code:`can_test.py`: Partial coverage of the commands described in :ref:`CAN Protocol. ` + * :code:`closed_loop_test.py`: Velocity control, position control (TODO: sensorless control), brake regen current hard limit, current control with velocity limiting + * :code:`encoder_test.py`: Incremental encoder, hall effect encoder, sin/cos encoder, SPI encoders (AMS, CUI) + * :code:`fibre_test.py`: General USB protocol tests + * :code:`nvm_test.py`: Configuration storage + * :code:`pwm_input_test.py`: PWM input + * :code:`step_dir_test.py`: Step/dir input + * :code:`uart_ascii_test.py`: Partial coverage of the commands described in :ref:`ASCII Protocol ` + +All tests in a file can be run with e.g.: + +.. code:: Bash + + python3 uart_ascii_test.py --test-rig-yaml ../../test-rig-rpi.yaml + +See the following sections for a more detailed test flow description. + +Our Test Rig +************************************************************************** + +Our test rig essentially consists of the following components: + + * an ODrive as the test subject + * a Teensy 4.0 to emulate external hardware such as encoders + * a Motor + Encoder pair for closed loop control tests + * a Raspberry Pi 4.0 as test host + * a CAN hat for the Raspberry Pi for CAN tests + +This document is therefore centered around this test rig layout. +If your test rig differs, you may be able to run some but not all of the tests. + +How to set up a Raspberry Pi as testing host +************************************************************************** + +#. Install Raspbian Lite on a Raspberry Pi 4.0. This is easiest if you have a keyboard, mouse and screen (micro-HDMI!). +I used the `NOOBS Lite installer `_ for this. Paste the ZIP-file's contents onto a FAT32 formatted SD card (fs type `0b` in `fdisk`) and boot it. Then follow the on-screen instructions. + +#. Prepare the installation: + + .. code:: Bash + + sudo systemctl enable ssh + sudo systemctl start ssh + # Transfer your public key for passwordless SSH. All subsequent steps can be done via SSH. + sudo apt-get update + sudo apt-get upgrade + # Change /etc/hostname to something meaningful + +#. Add the following lines to :code:`/boot/config.txt`: + + * :code:`enable_uart=1` + * :code:`dtparam=spi=on` + * :code:`dtoverlay=spi-bcm2835-overlay` + * :code:`dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25` + .. note:: These oscillator and interrupt GPIO settings here are for the "RS485 CAN HAT" I have. There appear to be multiple versions, so they may be different from yours. Check the marking on the oscillator and the schematics. + +#. Remove the following arguments from :code:`/boot/cmdline.txt`: + * :code:`console=serial0,115200` + +#. Append :code:`ODRIVE_TEST_RIG_NAME=[test-rig-name]` to :code:`/etc/environment`. The HWIL tests use this to look up the the file :code:`[test-rig-name].yaml` which is supposed to describe your test rig. + +#. Reboot. + +#. Install the prerequisites: + + .. code:: Bash + + sudo apt-get install ipython3 python3-appdirs python3-yaml python3-jinja2 python3-usb python3-serial python3-can python3-scipy python3-matplotlib python3-ipdb git openocd + # Optionally, to be able to compile the firmware: + sudo apt-get install gcc-arm-none-eabi + +#. Install Teensyduino and teensy-loader-cli: + + .. code:: Bash + + sudo apt-get install libfontconfig libxft2 libusb-dev + + .. code:: Bash + + wget https://downloads.arduino.cc/arduino-1.8.13-linuxarm.tar.xz + + .. code:: Bash + + tar -xf arduino-1.8.13-linuxarm.tar.xz + + .. code:: Bash + + wget https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linuxarm + + .. code:: Bash + + chmod +x TeensyduinoInstall.linuxarm + + .. code:: Bash + + ./TeensyduinoInstall.linuxarm --dir=arduino-1.8.13 + + .. code:: Bash + + sudo cp -R arduino-1.8.13 /usr/share/arduino + + .. code:: Bash + + sudo ln -s /usr/share/arduino/arduino /usr/bin/arduino + + + .. code:: Bash + + git clone https://github.com/PaulStoffregen/teensy_loader_cli + + .. code:: Bash + + pushd teensy_loader_cli + + .. code:: Bash + + make + + .. code:: Bash + + sudo cp teensy_loader_cli /usr/bin/ + + .. code:: Bash + + sudo ln -s /usr/bin/teensy_loader_cli /usr/bin/teensy-loader-cli + + .. code:: Bash + + popd + + .. code:: Bash + + curl https://www.pjrc.com/teensy/49-teensy.rules | sudo tee /etc/udev/rules.d/49-teensy.rules + +#. Add the following lines to :code:`/etc/udev/rules.d/49-stlinkv2.rules`: + + .. code:: Bash + + SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", MODE:="0666" + SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", MODE:="0666" + +#. :code:`sudo mkdir /opt/odrivetest && sudo chown $USER /opt/odrivetest` + +#. At this point you need the ODrive repository. See next section to sync it from your main PC. We assume now that you navigated to `tools/odrive/tests/`. + +#. :code:`sudo ../../odrivetool udev-setup` + +#. `sudo udevadm trigger` + +#. Run once after every reboot: :code:`sudo -E ipython3 --pdb test_runner.py -- --setup-host` + +SSH Testing Flow +************************************************************************** + +Here's one possible workflow for developing on the local host and testing on a remote SSH host. + +We assume that the ODrive repo is at :code:`/path/to/ODriveFirmware` and your testing host is configured under the SSH name :code:`odrv`. + +To flash and start remote debugging: + +#. Start OpenOCD remotely, along with a tunnel to localhost: + + .. code:: Bash + + ssh -t odrv -L3333:localhost:3333 bash -c "\"openocd '-f' 'interface/stlink-v2.cfg' '-f' 'target/stm32f4x_stlink.cfg'\"" + + + You can keep this open for multiple debug sessions. Press :kbd:`Ctrl` **+** :kbd:`C` to quit. + +#. Compile the firmware. +#. In VSCode, select the run configuration "Debug ODrive v3.x/v4.x - Remote" and press Run. This will flash the new firmware before dropping you into the debugger. + +To run a test: + + .. code:: Bash + + rsync -avh -e ssh /path/to/ODriveFirmware/ odrv:/opt/odrivetest --exclude="Firmware/build" --exclude="Firmware/.tup" --exclude=".git" --exclude="GUI" --delete + + .. code:: Bash + + ssh odrv + + .. code:: Bash + + cd /opt/odrivetest/tools/odrive/tests/ + ipython3 --pdb uart_ascii_test.py + diff --git a/docs/reStructuredText/_build/html/_sources/thermistors.rst.txt b/docs/reStructuredText/_build/html/_sources/thermistors.rst.txt new file mode 100644 index 00000000..8ffd5df6 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/thermistors.rst.txt @@ -0,0 +1,71 @@ +.. _thermistor-doc: + +================================================================================ +Thermistors +================================================================================ + +.. contents:: + :depth: 1 + :local: + +Introduction +-------------------------------------------------------------------------------- + +Thermistors are elements that change their resistance based on the temperature. +They can be used to electrically measure temperature. +The ODrive itself has thermistors on board near the FETs to ensure that they don't burn themselves out. +In addition to this it's possible to connect your own thermistor to measure the temperature of the connected motors. +There are two types of thermistors, Negative Temperature Coefficient (NTC) and Positive Temperature Coefficient (PTC). +This indicates whether the resistance goes up or down when the temperature goes up or down. +The ODrive only supports the NTC type thermistor. + +FET Thermistor +-------------------------------------------------------------------------------- + +The temperature of the onboard FET thermistors can be read out by using the :code:`odrivetool` under :code:`.motor.fet_thermistor.temperature`. +The odrive will automatically start current limiting the motor when the :code:`.motor.fet_thermistor.config.temp_limit_lower` threshold is exceeded and once :code:`.motor.fet_thermistor.config.temp_limit_upper` is exceeded the ODrive will stop controlling the motor and set an error. +The lower and upper threshold can be changed, but this is not recommended. + +Connecting Motor Thermistors +-------------------------------------------------------------------------------- + +To use your own thermistors with the ODrive a few things have to be clarified first. +The use of your own thermistor requires one analog input pin. Under :code:`.motor.motor_thermistor.config` the configuration of your own thermistor is available with the following fields: + +* :code:`gpio_pin`: The GPIO input in used for this thermistor. +* :code:`poly_coefficient_0` to :code:`poly_coefficient_3`: Coefficient that needs to be set for your specific setup more on that in :ref:`Thermistor coefficients `. +* :code:`temp_limit_lower` and :code:`temp_limit_upper`: Same principle as the FET temperature limits. +* :code:`enabled`: Whether this thermistor is enabled or not. + +Voltage Divider Circuit +-------------------------------------------------------------------------------- + +To measure a temperature with a thermistor a voltage divider circuit is used in addition with an ADC. +The screenshot below is taken directly from the ODrive schematic. + +.. figure:: figures/thermistor-voltage-divider.png + :scale: 100 % + :alt: Thermistor voltage divider + +The way this works is that the thermistor is connected in series with a known resistance value. +By connecting an ADC directly after the thermistor the resistance value can be determined. +For further information see `Voltage divider `_. +While not strictly necessary, it is a good idea to add a capacitor as shown as well. This will help reduce the effect of electrical noise. +A value between 470nF and 4.7uF is recommended, and any voltage rating 4V or higher. Put the capacitor physically close to the ODrive. +To use a thermistor with the ODrive a voltage divider circuit has to be made that uses `VCCA` as the power source with `GNDA` as the ground. +The voltage divider output can be connected to a GPIO pin that supports analog input. + +.. _thermistor-coefficients: + +Thermistor Coefficients +-------------------------------------------------------------------------------- + +Every thermistor and voltage divider circuit is different and thus it's necessary to let the ODrive know how to relate a voltage it measures at the GPIO pin to a temperature. +The :code:`poly_coefficient_0` to :code:`poly_coefficient_3` under :code:`.motor.motor_thermistor.config` are used for this. +The :code:`odrivetool` has a convenience function :code:`set_motor_thermistor_coeffs(axis, Rload, R_25, Beta, Tmin, Tmax)` which can be used to calculate and set these coefficients. + +* :code:`axis`: Which axis do set the motor thermistor coefficients for (:code:`odrv0.axis0` or :code:`odrv0.axis1`). +* :code:`Rload`: The Ohm value of the resistor used in the voltage divider circuit. +* :code:`R_25`: The resistance of the thermistor when the temperature is 25 degrees celsius. Can usually be found in the datasheet of your thermistor. Can also be measured manually with a multimeter. +* :code:`Beta`: A constant specific to your thermistor. Can be found in the datasheet of your thermistor. +* :code:`Tmin` and :code:`Tmax`: The temperature range that is used to create the coefficients. Make sure to set this range to be wider than what is expected during operation. A good example may be -10 to 150. diff --git a/docs/reStructuredText/_build/html/_sources/troubleshooting.rst.txt b/docs/reStructuredText/_build/html/_sources/troubleshooting.rst.txt new file mode 100644 index 00000000..c5ffd938 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/troubleshooting.rst.txt @@ -0,0 +1,159 @@ +.. _troubleshooting: + +================================================================================ +Troubleshooting +================================================================================ + +.. contents:: + :depth: 1 + :local: + +.. _error-codes: + +Error Codes +-------------------------------------------------------------------------------- + +If your ODrive is not working as expected, run :code:`odrivetool` and type :code:`dump_errors(odrv0)` :kbd:`Enter`. +This will dump a list of all the errors that are present. To clear all the errors, you can run :code:`odrv0.clear_errors()`. + +With this information you can look up the API documentation for your error(s): + +* System error flags documented :attr:`here `. +* Axis error flags documented :attr:`here `. +* Motor error flags documented :attr:`here `. +* Encoder error flags documented :attr:`here `. +* Controller error flags documented :attr:`here `. +* Sensorless estimator error flags documented :attr:`here `. + +What if :code:`dump_errors()` gives me python errors? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you get output like this: + +.. dropdown:: AttributeError: 'RemoteObject' object has no attribute + + .. code:: iPython + + In [1]: dump_errors(odrv0) + axis0 + --------------------------------------------------------------------------- + AttributeError Traceback (most recent call last) + ~/.local/lib/python3.6/site-packages/fibre/shell.py in + ----> 1 dump_errors(odrv0) + + ~/.local/lib/python3.6/site-packages/odrive/utils.py in dump_errors(odrv, clear) + 78 ('axis', axis, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("AXIS_ERROR_")}), + 79 ('motor', axis.motor, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("MOTOR_ERROR_")}), + ---> 80 ('fet_thermistor', axis.fet_thermistor, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("THERMISTOR_CURRENT_LIMITER_ERROR")}), + 81 ('motor_thermistor', axis.motor_thermistor, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("THERMISTOR_CURRENT_LIMITER_ERROR")}), + 82 ('encoder', axis.encoder, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("ENCODER_ERROR_")}), + + ~/.local/lib/python3.6/site-packages/fibre/remote_object.py in __getattribute__(self, name) + 243 return attr + 244 else: + --> 245 return object.__getattribute__(self, name) + 246 #raise AttributeError("Attribute {} not found".format(name)) + 247 + + AttributeError: 'RemoteObject' object has no attribute 'fet_thermistor' + + +when you call :code:`dump_errors()`, you have a version mismatch between odrivetool and the firmware on your ODrive. +To get the newest version of odrivetool, you can run :code:`pip install odrive --upgrade`. +To get the newest ODrive firmware, run :code:`odrivetool dfu`. +See the :ref:`odrivetool ` page for more details. + +USB Connectivity Issues +-------------------------------------------------------------------------------- + + * Try turning it off and on again (the ODrive, the script, the PC) + * Make sure you're using the latest firmware and python tools release + * **Linux**: Type :code:`lsusb` to list all USB devices. Verify that your ODrive is listed. + * **Linux**: Make sure you :ref:`set up your udev rules ` correctly. + * **Windows**: Right-click on the start menu and open "Device Manager". Verify that your ODrive is listed. + * **Windows**: Use the `Zadig utility `_ to verify the driver is set to :code:`WinUSB` or :code:`libusb-win32`. + + .. note:: There are two options listed in Zadig for ODrive: :code:`ODrive 3.x Native Interface (Interface 2)` and :code:`ODrive 3.x CDC Interface (Interface 0)`. + + Only the driver setting of the native interface is important to `odrivetool`. + * Ensure that no other ODrive program is running + * Run :code:`odrivetool` with the `--verbose` option. + * Run :code:`PYUSB_DEBUG=debug odrivetool` to get even more log output. + * If you're a developer you can use Wireshark to capture USB traffic. + * Try a different USB cable + * Try routing your USB cable so that it is far away from the motor and PSU cables to reduce EMI + +Firmware Issues +-------------------------------------------------------------------------------- + +Failure to Build the Firmware when Running :code:`make` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Clear out temporary files from previous compiles by first running `make clean` to prevent conflicts. +* **Windows users**: Confirm that tup has been correctly added to path by running :code:`env|grep PATH` in Git Bash. + If you see no mention of tup then you must `add its location to your PATH environment variable. `_. + Note that you may need to restart for the added path to take effect. + +Failure to Flash the Firmware When Running :code:`make flash` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* If using an ST-link, confirm that the ST-link is connected the correct pins and that you have power supplied to the board. + This can be by the 5V pin on the ST link or the main DC power jack. No power is supplied over the USB connection. + +Other Issues that `may not` produce an error code +-------------------------------------------------------------------------------- + +Motor cuts off or spins uncontrollably at high speeds (ie: > 5000 RPM) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* You may be approaching the limit of your encoder. + The 2400 count/rotation encoders that were initially included with odrive are realistically limited to around 5000 RPM. + Exceeding this speed causes the odrive to lose track of position. + This can only be fixed by using an alternative encoder or gearing down the output of your motor onto your encoder so that it still sees < 5000RPM at full speed. + If using the gearing options be sure to change your counts/rotation accordingly. + +Motor vibrates when stationary or makes constant noise +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Likely due to incorrect gains, specifically :code:`vel_gain` may be set too high. + Try following the :ref:`tuning procedure `. +* Check encoder shaft connection. Grub screws may vibrate lose with time. + If using a CUI shaft encoder try remounting the plastic retaining ring and confirm that it is not coming into contact with the encoder housing. + Also confirm that the encoder is securely mounted. +* If you are using a high resolution encoder (>4000 counts/rotation) then increasing encoder_pll_bandwidth may help reduce vibration. +* If you connect your motor to an object with a large moment of inertia (such as a flywheel) this will help reduce vibrations at high gians. + However, make sure that all connections are ridged. Cheap shaft couplers or belts under low tension can introduce enough flex into a system that the motor may still vibrate independently. + +Motor overshoots target position or oscillates back and forth +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Likely due to incorrect gains for a given motor current limit. + Specifically :code:`pos_gain` is set too high. + Try following the :ref:`tuning procedure `. +* Increase the current limit of your motor for more torque. + +Motor slowly starts to increase in speed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Encoder has likely slipped. This may occur when your motor makes a hard stop or violently vibrates causing something to come lose. + Power the board off and on again so that it undertakes a new calibration. + If you are using an index search on startup then you will need to repeat the index calibration process. + +Motor feels like it has less torque than it should and/or gets hot sitting still while under no load +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Encoder has likely slipped causing the motor controller to commutate the wrong windings slightly which reduces output torque and produces excess heat as the motor 'fights itself'. +* This can also be caused if the rotor bell slips on the motor shaft. + On some motors the rotor bell is secured against the shaft with a grub screw. + Confirm that this screw is tight enough. + For further details on how to resolve this issue see `this forum post `_. + +False steps or direction changes when using step/dir +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Prior to Odrive board V3.5 no filtering is present on the GPIO pins used for step/dir interface and so inductively coupled noise may causes false steps to be detected. + ODrive V3.5 and has onboard filtering to resolve this issue. +* If you experience this issue use a twisted pair cable between your microcontroller that’s generating the step/dir signals and your odrive board. + A section cut from cat-5 cable works well as does just twisting some normal insulated wire together. +* Ensure that the step/dir signal cables are not draped over the odrive board, are not running in parallel to the motor or power supply cables. +* If the above does not resolve your issue on V3.4 boards and lower try adding a ~22 Ohm resistor in series with the step and direction pins along with a ~ 4.7 nF capacitor between the ground pin and the step and dir pins such as shown `here `_. diff --git a/docs/reStructuredText/_build/html/_sources/uart.rst.txt b/docs/reStructuredText/_build/html/_sources/uart.rst.txt new file mode 100644 index 00000000..c11c5ef3 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/uart.rst.txt @@ -0,0 +1,34 @@ +.. _uart-doc: + +================================================================================ +UART Interface +================================================================================ + +The ODrive's :code:`UART_A` interface is enabled by default with a baudrate of 115200 on the pins as shown in :ref:`Pinout `. + +To use UART connect it like this: + +* Tx of the ODrive <=> Rx of other device +* Rx of the ODrive <=> Tx of other device +* GND of the ODrive (use any GND pin on J3 of the ODrive) <=> GND of the other device + +The logic level of the ODrive is 3.3V. The GPIOs are 5V tolerant. + +You can use :code:`odrv0.config.uart_a_baudrate` to change the baudrate and :code:`odrv0.config.enable_uart_a` to disable/reenable :code:`UART_A`. +The :code:`UART_A` port can run the :ref:`Native Protocol ` or the :ref:`ASCII Protocol `, but not both at the same time. +You can configure this by setting :code:`odrv0.config.uart0_protocol` to either :code:`STREAM_PROTOCOL_TYPE_ASCII_AND_STDOUT` for the ASCII protocol or :code:`STREAM_PROTOCOL_TYPE_FIBRE` for the native protocol. + +How to use UART on GPIO3/4 +-------------------------------------------------------------------------------- + +If you need GPIO1/2 for some function other than UART you can disable :code:`UART_A` and instead use :code:`UART_B` on GPIO3/4. Here's how you do it: + +.. code:: iPython + + odrv0.config.enable_uart_a = False + odrv0.config.gpio1_mode = GPIO_MODE_DIGITAL + odrv0.config.gpio2_mode = GPIO_MODE_DIGITAL + odrv0.config.enable_uart_b = True + odrv0.config.gpio3_mode = GPIO_MODE_UART_B + odrv0.config.gpio4_mode = GPIO_MODE_UART_B + odrv0.reboot() diff --git a/docs/reStructuredText/_build/html/_sources/usb.rst.txt b/docs/reStructuredText/_build/html/_sources/usb.rst.txt new file mode 100644 index 00000000..b072ef01 --- /dev/null +++ b/docs/reStructuredText/_build/html/_sources/usb.rst.txt @@ -0,0 +1,38 @@ +.. _usb-doc: + +================================================================================ +USB +================================================================================ + +This page documents the low level USB configuration. +If you're looking for a higher level protocol documentation see :ref:`Native Protocol ` and :ref:`ASCII Protocol `. + +This page assumes that you are familiar with the general USB architecture, in particular with terms like "configuration", "interface" and "endpoint". + +On USB the ODrive provides a single configuration which is a composite device consisting of a CDC device (virtual COM port) and a vendor specific device. + +.. dropdown:: What is a composite device? + + A composite device is a device where interfaces are grouped by interface association descriptors. + For such devices, the host OS loads an intermediate driver, so that each of the interface groups can be treated like a separate device and have its own host-side driver attached. + + +The following interface groups are present: + + * Interface Association: Communication Device Class (CDC) + * Interface 0: + * Endpoint `0x82`: CDC commands + + * Interface 1: + * Endpoint `0x01`: CDC data OUT + * Endpoint `0x81`: CDC data IN + + * Interface Association: Vendor Specific Device Class + * Interface 2: + * Endpoint `0x03`: data OUT + * Endpoint `0x83`: data IN + +The CDC interface (endpoint pair `0x01, 0x81`) runs the :ref:`ASCII Protocol ` by default (see :code:`odrv0.config.enable_ascii_protocol_on_usb`). +The vendor specific interface (endpoint pair `0x03, 0x83`) runs the :ref:`Native Protocol ` (the packet based variant). + +The two interfaces can not (yet) be used simultaneously. diff --git a/docs/reStructuredText/_build/html/_static/basic.css b/docs/reStructuredText/_build/html/_static/basic.css new file mode 100644 index 00000000..b3bdc004 --- /dev/null +++ b/docs/reStructuredText/_build/html/_static/basic.css @@ -0,0 +1,861 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 450px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +dl.footnote > dt, +dl.citation > dt { + float: left; + margin-right: 0.5em; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0.5em; + content: ":"; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +code.descclassname { + background-color: transparent; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/_static/check-solid.svg b/docs/reStructuredText/_build/html/_static/check-solid.svg new file mode 100644 index 00000000..92fad4b5 --- /dev/null +++ b/docs/reStructuredText/_build/html/_static/check-solid.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/reStructuredText/_build/html/_static/clipboard.min.js b/docs/reStructuredText/_build/html/_static/clipboard.min.js new file mode 100644 index 00000000..54b3c463 --- /dev/null +++ b/docs/reStructuredText/_build/html/_static/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.8 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1 + + + + diff --git a/docs/reStructuredText/_build/html/_static/copybutton.css b/docs/reStructuredText/_build/html/_static/copybutton.css new file mode 100644 index 00000000..5d291490 --- /dev/null +++ b/docs/reStructuredText/_build/html/_static/copybutton.css @@ -0,0 +1,81 @@ +/* Copy buttons */ +button.copybtn { + position: absolute; + display: flex; + top: .3em; + right: .5em; + width: 1.7em; + height: 1.7em; + opacity: 0; + transition: opacity 0.3s, border .3s, background-color .3s; + user-select: none; + padding: 0; + border: none; + outline: none; + border-radius: 0.4em; + border: #e1e1e1 1px solid; + background-color: rgb(245, 245, 245); +} + +button.copybtn.success { + border-color: #22863a; +} + +button.copybtn img { + width: 100%; + padding: .2em; +} + +div.highlight { + position: relative; +} + +.highlight:hover button.copybtn { + opacity: 1; +} + +.highlight button.copybtn:hover { + background-color: rgb(235, 235, 235); +} + +.highlight button.copybtn:active { + background-color: rgb(187, 187, 187); +} + +/** + * A minimal CSS-only tooltip copied from: + * https://codepen.io/mildrenben/pen/rVBrpK + * + * To use, write HTML like the following: + * + *

Short

+ */ + .o-tooltip--left { + position: relative; + } + + .o-tooltip--left:after { + opacity: 0; + visibility: hidden; + position: absolute; + content: attr(data-tooltip); + padding: .2em; + font-size: .8em; + left: -.2em; + background: grey; + color: white; + white-space: nowrap; + z-index: 2; + border-radius: 2px; + transform: translateX(-102%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); +} + +.o-tooltip--left:hover:after { + display: block; + opacity: 1; + visibility: visible; + transform: translateX(-100%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); + transition-delay: .5s; +} diff --git a/docs/reStructuredText/_build/html/_static/copybutton.js b/docs/reStructuredText/_build/html/_static/copybutton.js new file mode 100644 index 00000000..482bda03 --- /dev/null +++ b/docs/reStructuredText/_build/html/_static/copybutton.js @@ -0,0 +1,197 @@ +// Localization support +const messages = { + 'en': { + 'copy': 'Copy', + 'copy_to_clipboard': 'Copy to clipboard', + 'copy_success': 'Copied!', + 'copy_failure': 'Failed to copy', + }, + 'es' : { + 'copy': 'Copiar', + 'copy_to_clipboard': 'Copiar al portapapeles', + 'copy_success': '¡Copiado!', + 'copy_failure': 'Error al copiar', + }, + 'de' : { + 'copy': 'Kopieren', + 'copy_to_clipboard': 'In die Zwischenablage kopieren', + 'copy_success': 'Kopiert!', + 'copy_failure': 'Fehler beim Kopieren', + }, + 'fr' : { + 'copy': 'Copier', + 'copy_to_clipboard': 'Copié dans le presse-papier', + 'copy_success': 'Copié !', + 'copy_failure': 'Échec de la copie', + }, + 'ru': { + 'copy': 'Скопировать', + 'copy_to_clipboard': 'Скопировать в буфер', + 'copy_success': 'Скопировано!', + 'copy_failure': 'Не удалось скопировать', + }, + 'zh-CN': { + 'copy': '复制', + 'copy_to_clipboard': '复制到剪贴板', + 'copy_success': '复制成功!', + 'copy_failure': '复制失败', + } +} + +let locale = 'en' +if( document.documentElement.lang !== undefined + && messages[document.documentElement.lang] !== undefined ) { + locale = document.documentElement.lang +} + +let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT; +if (doc_url_root == '#') { + doc_url_root = ''; +} + +const path_static = `${doc_url_root}_static/`; + +/** + * Set up copy/paste for code blocks + */ + +const runWhenDOMLoaded = cb => { + if (document.readyState != 'loading') { + cb() + } else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', cb) + } else { + document.attachEvent('onreadystatechange', function() { + if (document.readyState == 'complete') cb() + }) + } +} + +const codeCellId = index => `codecell${index}` + +// Clears selected text since ClipboardJS will select the text when copying +const clearSelection = () => { + if (window.getSelection) { + window.getSelection().removeAllRanges() + } else if (document.selection) { + document.selection.empty() + } +} + +// Changes tooltip text for two seconds, then changes it back +const temporarilyChangeTooltip = (el, oldText, newText) => { + el.setAttribute('data-tooltip', newText) + el.classList.add('success') + setTimeout(() => el.setAttribute('data-tooltip', oldText), 2000) + setTimeout(() => el.classList.remove('success'), 2000) +} + +// Changes the copy button icon for two seconds, then changes it back +const temporarilyChangeIcon = (el) => { + img = el.querySelector("img"); + img.setAttribute('src', `${path_static}check-solid.svg`) + setTimeout(() => img.setAttribute('src', `${path_static}copy-button.svg`), 2000) +} + +const addCopyButtonToCodeCells = () => { + // If ClipboardJS hasn't loaded, wait a bit and try again. This + // happens because we load ClipboardJS asynchronously. + if (window.ClipboardJS === undefined) { + setTimeout(addCopyButtonToCodeCells, 250) + return + } + + // Add copybuttons to all of our code cells + const codeCells = document.querySelectorAll('div.highlight pre') + codeCells.forEach((codeCell, index) => { + const id = codeCellId(index) + codeCell.setAttribute('id', id) + + const clipboardButton = id => + `` + codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) + }) + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} + + +var copyTargetText = (trigger) => { + var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); + return formatCopyText(target.innerText, '', false, true, true, true, '', '') +} + + // Initialize with a callback so we can modify the text before copy + const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText}) + + // Update UI with error/success messages + clipboard.on('success', event => { + clearSelection() + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success']) + temporarilyChangeIcon(event.trigger) + }) + + clipboard.on('error', event => { + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure']) + }) +} + +runWhenDOMLoaded(addCopyButtonToCodeCells) \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/_static/copybutton_funcs.js b/docs/reStructuredText/_build/html/_static/copybutton_funcs.js new file mode 100644 index 00000000..b9168c55 --- /dev/null +++ b/docs/reStructuredText/_build/html/_static/copybutton_funcs.js @@ -0,0 +1,58 @@ +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} diff --git a/docs/reStructuredText/_build/html/_static/css/badge_only.css b/docs/reStructuredText/_build/html/_static/css/badge_only.css new file mode 100644 index 00000000..e380325b --- /dev/null +++ b/docs/reStructuredText/_build/html/_static/css/badge_only.css @@ -0,0 +1 @@ +.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/reStructuredText/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/reStructuredText/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/reStructuredText/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/reStructuredText/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/fontawesome-webfont.eot b/docs/reStructuredText/_build/html/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/fontawesome-webfont.svg b/docs/reStructuredText/_build/html/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000..855c845e --- /dev/null +++ b/docs/reStructuredText/_build/html/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/fontawesome-webfont.ttf b/docs/reStructuredText/_build/html/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/fontawesome-webfont.woff b/docs/reStructuredText/_build/html/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/fontawesome-webfont.woff2 b/docs/reStructuredText/_build/html/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/lato-bold-italic.woff b/docs/reStructuredText/_build/html/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/lato-bold-italic.woff differ diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/lato-bold-italic.woff2 b/docs/reStructuredText/_build/html/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/lato-bold.woff b/docs/reStructuredText/_build/html/_static/css/fonts/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/lato-bold.woff differ diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/lato-bold.woff2 b/docs/reStructuredText/_build/html/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/lato-bold.woff2 differ diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/lato-normal-italic.woff b/docs/reStructuredText/_build/html/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/lato-normal-italic.woff differ diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/lato-normal-italic.woff2 b/docs/reStructuredText/_build/html/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/lato-normal.woff b/docs/reStructuredText/_build/html/_static/css/fonts/lato-normal.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/lato-normal.woff differ diff --git a/docs/reStructuredText/_build/html/_static/css/fonts/lato-normal.woff2 b/docs/reStructuredText/_build/html/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/css/fonts/lato-normal.woff2 differ diff --git a/docs/reStructuredText/_build/html/_static/css/theme.css b/docs/reStructuredText/_build/html/_static/css/theme.css new file mode 100644 index 00000000..0d9ae7e1 --- /dev/null +++ b/docs/reStructuredText/_build/html/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,.wy-nav-top a,.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.rst-content .wy-breadcrumbs li tt,.wy-breadcrumbs li .rst-content tt,.wy-breadcrumbs li code{padding:5px;border:none;background:none}.rst-content .wy-breadcrumbs li tt.literal,.wy-breadcrumbs li .rst-content tt.literal,.wy-breadcrumbs li code.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.field-list>dt:after,html.writer-html5 .rst-content dl.footnote>dt:after{content:":"}html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.footnote>dt>span.brackets{margin-right:.5rem}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{font-style:italic}html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.footnote>dd p,html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{font-size:inherit;line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/_static/doctools.js b/docs/reStructuredText/_build/html/_static/doctools.js new file mode 100644 index 00000000..61ac9d26 --- /dev/null +++ b/docs/reStructuredText/_build/html/_static/doctools.js @@ -0,0 +1,321 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated === 'undefined') + return string; + return (typeof translated === 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated === 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('
\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keydown(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box, textarea, dropdown or button + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' + && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey + && !event.shiftKey) { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/docs/reStructuredText/_build/html/_static/documentation_options.js b/docs/reStructuredText/_build/html/_static/documentation_options.js new file mode 100644 index 00000000..9c28f34f --- /dev/null +++ b/docs/reStructuredText/_build/html/_static/documentation_options.js @@ -0,0 +1,12 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '0.0', + LANGUAGE: 'None', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/_static/file.png b/docs/reStructuredText/_build/html/_static/file.png new file mode 100644 index 00000000..a858a410 Binary files /dev/null and b/docs/reStructuredText/_build/html/_static/file.png differ diff --git a/docs/reStructuredText/_build/html/_static/jquery-3.5.1.js b/docs/reStructuredText/_build/html/_static/jquery-3.5.1.js new file mode 100644 index 00000000..50937333 --- /dev/null +++ b/docs/reStructuredText/_build/html/_static/jquery-3.5.1.js @@ -0,0 +1,10872 @@ +/*! + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2020-05-04T22:49Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.5.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2020-03-14 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Analog Input

+

Analog inputs can be used to measure voltages between 0 and 3.3V. +ODrive uses a 12 bit ADC (4096 steps) and so has a maximum resolution of 0.8 mV. +A GPIO must be configured with <odrv>.config.gpioX_mode = GPIO_MODE_ANALOG_IN before it can be used as an analog input. +To read the voltage on GPIO1 in odrivetool the following would be entered: odrv0.get_adc_voltage(1).

+

Similar to RC PWM input, analog inputs can also be used to feed any of the numerical properties that are visible in odrivetool. +This is done by configuring odrv0.config.gpio3_analog_mapping and odrv0.config.gpio4_analog_mapping. +Refer to RC PWM for instructions on how to configure the mappings.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/ascii-protocol.html b/docs/reStructuredText/_build/html/ascii-protocol.html new file mode 100644 index 00000000..55011c8e --- /dev/null +++ b/docs/reStructuredText/_build/html/ascii-protocol.html @@ -0,0 +1,348 @@ + + + + + + ASCII Protocol — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

ASCII Protocol

+ +
+

Sending Commands

+
+
    +
  • +
    Via USB:
      +
    • Windows: Use PuTTY to manually send commands or open the COM port using your favorite programming language

    • +
    • Linux/macOS: Run /dev/tty* to list all serial ports. The ODrive will show up as /dev/ttyACM0 (or similar) on Linux and /dev/tty.usbmodem[...] on macOS. +Once you know the name, you can use screen /dev/ttyACM0 (with the correct name) to send commands manually or open the device using your favorite programming language. +Serial ports on Unix can be opened, written to and read from like a normal file.

    • +
    +
    +
    +
  • +
  • +
    Via UART: Connect the ODrive’s TX (GPIO1) to your host’s RX. Connect your ODrive’s RX (GPIO2) to your host’s TX. See UART for more info.
      +
    • Arduino: You can use the ODrive Arduino library to talk to the ODrive.

    • +
    • Windows/Linux/macOS: You can use an FTDI USB-UART cable to connect to the ODrive.

    • +
    +
    +
    +
  • +
+
+

The ODrive does not echo commands. That means that when you type commands into a program like screen, the characters you type won’t show up in the console.

+
+

Arduino

+

There is an Arduino library that gives some examples on how to use the ASCII protocol to communicate with the ODrive. +Check it out here <../Arduino/ODriveArduino>.

+
+
+
+

Command Format

+

The ASCII protocol is human-readable and line-oriented, with each line having the following format:

+

Format: command *42 ; comment [new line character]

+
+
    +
  • *42 stands for a GCode compatible checksum and can be omitted. If and only if a checksum is provided, the device will also include a checksum in the response, if any. +If the checksum is provided but is not valid, the line is ignored. The checksum is calculated as the bitwise xor of all characters before the asterisk (*). +Example of a valid checksum: r vbus_voltage *93.

  • +
  • comments are supported for GCode compatibility

  • +
  • the command is interpreted once the new-line character is encountered

  • +
+
+
+
+

Command Reference

+
+

Motor Trajectory

+

Format: t motor destination

+
    +
  • t for trajectory.

  • +
  • motor is the motor number, 0 or 1.

  • +
  • destination is the goal position, in [turns].

  • +
+

Example:

+
t 0 -2
+
+
+

For general moving around of the axis, this is the recommended command.

+

This command updates the watchdog timer for the motor.

+
+
+

Motor Position

+

For basic use where you send one setpoint at at a time, use the q command.

+

Format: q motor position velocity_lim torque_lim

+
    +
  • q for position.

  • +
  • motor is the motor number, 0 or 1.

  • +
  • position is the desired position, in [turns].

  • +
  • velocity_lim is the velocity limit, in [turns/s] (optional).

  • +
  • torque_lim is the torque limit, in [Nm] (optional).

  • +
+

Example:

+
q 0 -2 1 0.1
+
+
+

If you have a realtime controller that is streaming setpoints and tracking a trajectory, use the p command.

+

Format: p motor position velocity_ff torque_ff

+
    +
  • p for position

  • +
  • motor is the motor number, 0 or 1.

  • +
  • position is the desired position, in [turns].

  • +
  • velocity_ff is the velocity feed-forward term, in [turns/s] (optional).

  • +
  • torque_ff is the torque feed-forward term, in [Nm] (optional).

  • +
+

Example:

+
p 0 -2 0 0
+
+
+

This command updates the watchdog timer for the motor.

+
+

Note

+

If you don’t know what feed-forward is or what it’s used for, simply omit it.

+
+
+
+

Motor Velocity

+

Format: v motor velocity torque_ff

+
    +
  • v for velocity

  • +
  • motor is the motor number, 0 or 1.

  • +
  • velocity is the desired velocity in [turns/s].

  • +
  • torque_ff is the torque feed-forward term, in [Nm] (optional).

  • +
+

Example:

+
v 0 1 0
+
+
+

This command updates the watchdog timer for the motor.

+
+
+

Motor Current

+

Format: c motor torque

+
    +
  • c for torque

  • +
  • motor is the motor number, 0 or 1.

  • +
  • torque is the desired torque in [Nm].

  • +
+

This command updates the watchdog timer for the motor.

+
+
+

Request Feedback

+

imput format: f motor

+

response format: pos vel

+
    +
  • f for feedback.

  • +
  • pos is the encoder position in [turns] (float).

  • +
  • vel is the encoder velocity in [turns/s] (float).

  • +
+
+
+

Update Motor Watchdog

+

Format: u motor

+
    +
  • u for /u/pdate.

  • +
  • motor is the motor number, 0 or 1.

  • +
+

This command updates the watchdog timer for the motor, without changing any +setpoints.

+
+
+
+

Parameter Reading/Writing

+

Not all parameters can be accessed via the ASCII protocol but at least all parameters with float and integer type are supported.

+
+
    +
  • Reading format: r [property]

    +
      +
    • property name of the property, as seen in ODrive Tool

    • +
    • response: text representation of the requested value

    • +
    • Example: r vbus_voltage => response: 24.087744

    • +
    +
  • +
  • Writing format: w [property] [value]

    +
      +
    • property name of the property, as seen in ODrive Tool

    • +
    • value text representation of the value to be written

    • +
    • Example:

      +
      w axis0.controller.input_pos -123.456
      +
      +
      +
    • +
    +
  • +
+
+
+
+

System Commands

+
    +
  • ss - Save config

  • +
  • se - Erase config

  • +
  • sr - Reboot

  • +
  • sc - Clear errors

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/can-guide.html b/docs/reStructuredText/_build/html/can-guide.html new file mode 100644 index 00000000..8470e9ce --- /dev/null +++ b/docs/reStructuredText/_build/html/can-guide.html @@ -0,0 +1,341 @@ + + + + + + CAN Bus Guide for ODrive — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

CAN Bus Guide for ODrive

+ +

ODrive v3 supports CAN 2.0b. We’ve built a simple protocol (named CANSimple) so that most ODrive functions can be controlled without a full CAN Open or similar stack. +This guide is intended for beginners to set up CAN on the ODrive and on their host device. +We will be focusing on Raspberry Pi and Arduino-compatible devices using the MCP2515 CAN Controller.

+
+

What is CAN bus?

+

Borrowing from Wikipeda:

+

> A Controller Area Network (CAN bus) is a robust vehicle bus standard designed to allow microcontrollers and devices to communicate with each other’s applications without a host computer. +It is a message-based protocol, designed originally for multiplex electrical wiring within automobiles to save on copper, but it can also be used in many other contexts. +For each device, the data in a frame is transmitted sequentially but in such a way that if more than one device transmits at the same time, the highest priority device can continue while the others back off. +Frames are received by all devices, including by the transmitting device.

+

In simple terms, CAN is a way of communicating between many devices over a single twisted pair of wires. +The signal is transmitted as the difference in voltage between the two wires (differential signalling), which makes it very robust against noise. +Instead of using a unique address (like I2C) or a select pin (like SPI), CAN messages have a unique ID that also acts as the priority. +At the beginning of a message frame, all devices talk and read at the same time. As the message ID is transmitted, the lowest value “wins” and that message will be transmitted (ID 0 has the highest priority). +All other devices will wait for the next chance to send. If two devices send the same message ID at the same time, they will conflict and a bus failure may occur. +Make sure your devices can never send the same message ID at the same time!

+

See also this great article from Danfoss that quickly describes how to put together the wiring for a CAN bus

+CAN picture +
+
+

Why use CAN?

+

CAN is convenient for its simple and robust Physical Layer (PHY) that requires only a twisted pair of wires and a 120ohm termination resistor at each end. +It has low jitter and low latency, because there is no host computer. It is relatively fast (CAN 2.0b supports 1 Mbps). Messages are easy to configure and load with data. +Transceivers and controllers are inexpensive and widely available, thanks to its use in automotive.

+
+
+

Hardware Setup

+

ODrive assumes the CAN PHY is a standard differential twisted pair in a linear bus configuration with 120 ohm termination resistance at each end. +ODrive versions less than V3.5 include a soldered 120 ohm termination resistor, but ODrive versions V3.5 and greater implement a dip switch to toggle the termination. +ODrive uses 3.3v as the high output, but conforms to the CAN PHY requirement of achieving a differential voltage > 1.5V to represent a “0”. +As such, it is compatible with standard 5V bus architectures.

+
+
+

Setting up CAN on ODrive

+

CANSimple breaks the CAN Message ID into two parts: An axis ID and a command ID. By default, CAN is enabled on the ODrive, where Axis 0 has ID 0, and Axis 1 has ID 1. +The ID of each axis should be unique; each should be set via odrivetool before connecting to the bus with the command:

+
<odrv>.<axis>.config.can.node_id = <number>
+
+
+

By default, ODrive supports a value up to 63 (0x3F). See can-protocol for more information.

+

You should also set the CAN bus speed on ODrive with the command

+
<odrv>.can.config.baud_rate = <number>
+
+
+ ++++ + + + + + + + + + + + + + + + + + + + +

Speed

value

125 kbps

125000

250 kbps

250000

500 kbps

500000

1000 kbps

1000000

+

That’s it! You’re ready to set up your host device.

+
+

Example

+
odrv0.axis0.config.can.node_id = 0
+odrv0.axis1.config.can.node_id = 1
+odrv0.can.config.baud_rate = 250000
+
+
+
+
+
+

Setting up a Raspberry Pi for CAN Communications

+

First, you will need a CAN Hat for your Raspberry Pi. +We are using this CAN hat.

+

Setting up the Raspberry Pi essentially involves the following: +#. Enable SPI communications to the MCP2515 +#. Install can-utils with apt-get install can-utils +#. Creating a connection between your application and the can0 socket

+

There are many tutorials for this process. +This one is pretty good, and this recent forum post also works. +However, be careful. You have to set the correct parameters for the particular CAN hat you’re using!

+
    +
  1. Set the correct oscillator value

  2. +
+

We configure the MCP2515 in section 2.2 of the tutorial, but the hat we recommend uses a 12MHz crystal instead of a 16 MHz crystal. +If you’re not sure what value to use, the top of the oscillator will have the value printed on it in MHz.

+

My Settings:

+
dtparam=spi-on
+dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25
+dtoverlay=spi0-hw-cs
+
+
+
    +
  1. Use the correct CAN baud rate

  2. +
+

By default, ODrive uses 250 kbps (250000) but the tutorial is using 500 kbps. Make sure you use the value set earlier on the ODrive.

+
sudo ip link set can0 up type can bitrate 250000
+
+
+
+

Wiring ODrive to CAN

+

The CANH and CANL pins on J2 are used for CAN communication. Connect CANH to CANH on all other devices, and CANL to CANL.

+

If your ODrive is the “last” (furthest) device on the bus, you can use the on-board 120 Ohm termination resistor by switching the DIP switch to “CAN 120R”. +Otherwise, add an external resistor.

+
+
+

Verifying Communcation

+

By default, each ODrive axis will send a heartbeat message at 10Hz. We can confirm our ODrive communication is working by starting the can0 interface, and then reading from it:

+
sudo ip link set can0 up type can bitrate 250000
+candump can0 -xct z -n 10
+
+
+

This will read the first 10 messages from the ODrive and stop. If you’d like to see all messages, remove the -n 10 part (hit CTRL+C to exit). +The other flags (x, c, t) are adding extra information, colouring, and a timestamp, respectively.

+
candump can0 -xct z -n 10
+(000.000000)  can0  RX - -  001   [8]  00 00 00 00 01 00 00 00
+(000.001995)  can0  RX - -  021   [8]  00 00 00 00 08 00 00 00
+(000.099978)  can0  RX - -  001   [8]  00 00 00 00 01 00 00 00
+(000.101963)  can0  RX - -  021   [8]  00 00 00 00 08 00 00 00
+(000.199988)  can0  RX - -  001   [8]  00 00 00 00 01 00 00 00
+(000.201980)  can0  RX - -  021   [8]  00 00 00 00 08 00 00 00
+(000.299986)  can0  RX - -  001   [8]  00 00 00 00 01 00 00 00
+(000.301976)  can0  RX - -  021   [8]  00 00 00 00 08 00 00 00
+(000.399986)  can0  RX - -  001   [8]  00 00 00 00 01 00 00 00
+(000.401972)  can0  RX - -  021   [8]  00 00 00 00 08 00 00 00
+
+
+

Alternatively, if you have python can installed (pip3 install python-can), you can use the can.viewer script:

+

python3 -m can.viewer -c "can0" -i "socketcan" which will give you a nice readout. +See the python-can docs for an example.

+
+
+
+

Commanding the ODrive

+

Now that we’ve verified the communication is working, we can try commanding the ODrive. +Make sure your ODrive is configured and working properly over USB with:code:odrivetool before continuing. +See the Getting Started Guide for help with first-time configuration.

+

To move the ODrive, we use the command Set Input Pos, or cmd ID 0x00C. First we create a message with this ID, and then “OR” in the axis ID. +Then we create an 8-byte array of data with input position that we want, with a float value turned into bytes… this can be a pain though.

+
+
+

DBC Files

+

A DBC file (.dbc) is a database of all the messages and signals in a CAN protocol. +This file can be used with Python cantools to serialize and deserialize messages without having to handle the bitshifting etc yourself. +We have generated a .dbc for CANSimple for you!

+ +

Instead of manually writing values into the data, we can create a dictionary of signal:value pairs and serialize the data according to the database definition.

+
    +
  1. Load the database into memory

  2. +
  3. Use encode_message() to get a byte array representation of data for sending

  4. +
  5. Use decode_message() to get a dictionary representation of data for receiving

  6. +
+

The CAN DBC Example script shows you how this can be used. This is the recommended method of serializing and deserializing.

+

If you’re using C++, then you can use the CANHelpers single-header library to do this instead, although the DBC file isn’t used.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/can-protocol.html b/docs/reStructuredText/_build/html/can-protocol.html new file mode 100644 index 00000000..6e8d21f5 --- /dev/null +++ b/docs/reStructuredText/_build/html/can-protocol.html @@ -0,0 +1,229 @@ + + + + + + CAN Protocol — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

CAN Protocol

+ +

This document describes the CAN Protocol. For examples of usage, check out our CAN Guide!

+
+

Configuring ODrive for CAN

+

Configuration of the CAN parameters should be done via USB before putting the device on the bus.

+

To set the desired baud rate, use <odrv>.can.config.baud_rate = <value>.

+

Each axis looks like a separate node on the bus. +Thus, they both have the two properties can_node_id and can_node_id_extended. +The node ID can be from 0 to 63 (0x3F) inclusive, or, if extended CAN IDs are used, from 0 to 16777215 (0xFFFFFF). +If you want to connect more than one ODrive on a CAN bus, you must set different node IDs for the second ODrive or they will conflict and crash the bus.

+
+

Example Configuration

+
odrv0.axis0.config.can_node_id = 3
+odrv0.axis1.config.can_node_id = 1
+odrv0.can.config.baud_rate = 500000
+odrv0.save_configuration()
+odrv0.reboot()
+
+
+
+
+
+

Transport Protocol

+

We’ve implemented a very basic CAN protocol that we call “CAN Simple” to get users going with ODrive. +This protocol is sufficiently abstracted that it is straightforward to add other protocols such as CANOpen, J1939, or Fibre over ISO-TP in the future. +Unfortunately, implementing those protocols is a lot of work, and we wanted to give users a way to control ODrive’s basic functions via CAN sooner rather than later.

+
+

CAN Frame

+

At its most basic, the CAN Simple frame looks like this:

+
    +
  • Upper 6 bits - Node ID - max 0x3F (or 0xFFFFFF when using extended CAN IDs)

  • +
  • Lower 5 bits - Command ID - max 0x1F

  • +
+

To understand how the Node ID and Command ID interact, let’s look at an example

+

The 11-bit Arbitration ID is setup as follows:

+

can_id = axis_id << 5 | cmd_id

+

For example, an Axis ID of 0x01 with a command of 0x0C would be result in 0x2C:

+

0x01 << 5 | 0x0C = 0x2C

+
+

All multibyte values are little endian (aka Intel format, aka least significant byte first).

+
+

Note

+
    +
  • These messages are call & response. The Master node sends a message with the RTR bit set, and the axis responds with the same ID and specified payload.

  • +
  • These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple.

  • +
  • These messages can be sent to either address on a given ODrive board.

  • +
+
+
+
+

Interoperability with CANopen

+

You can deconflict with CANopen like this:

+
    +
  • odrv0.axis0.config.can.node_id = 0x010 - Reserves messages 0x200 through 0x21F

  • +
  • odrv0.axis1.config.can.node_id = 0x018 - Reserves messages 0x300 through 0x31F

  • +
+

It may not be obvious, but this allows for some compatibility with CANOpen. +Although the address space 0x200 and 0x300 correspond to receive PDO base addresses, we can guarantee they will not conflict if all CANopen node IDs are >= 32. E.g.:

+
    +
  • CANopen nodeID = 35 = 0x23

  • +
  • Receive PDO 0x200 + nodeID = 0x223, which does not conflict with the range [0x200 : 0x21F]

  • +
+

Be careful that you don’t assign too many nodeIDs per PDO group. Four CAN Simple nodes (32*4) is all of the available address space of a single PDO. +If the bus is strictly ODrive CAN Simple nodes, a simple sequential Node ID assignment will work fine.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/commands.html b/docs/reStructuredText/_build/html/commands.html new file mode 100644 index 00000000..7f2f351e --- /dev/null +++ b/docs/reStructuredText/_build/html/commands.html @@ -0,0 +1,298 @@ + + + + + + Parameters & Commands — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Parameters & Commands

+

We will use the <odrv> as a placeholder for any ODrive object. Every ODrive controller is an ODrive object. In odrivetool this is usually odrv0. Furthermore we use <axis> as a placeholder for any axis, which is an attribute of an ODrive object (for example odrv0.axis0). An axis represents where the motors are connected. (axis0 for M0 or axis1 for M1)

+ +
+

Per-Axis Commands

+

For the most part, both axes on the ODrive can be controlled independently.

+
+

State Machine

+

The current state of an axis is indicated by <axis>.current_state. +The user can request a new state by assigning a new value to <axis>.requested_state. +The default state after startup is AXIS_STATE_IDLE. A description of all states can be found here.

+
+
+

Startup Procedure

+

By default the ODrive takes no action at startup and goes to idle immediately. +In order to change what startup procedures are used, set the startup procedures you want to True. +The ODrive will sequence all enabled startup actions selected in the order shown below.

+
    +
  • <axis>.config.startup_motor_calibration

  • +
  • <axis>.config.startup_encoder_index_search

  • +
  • <axis>.config.startup_encoder_offset_calibration

  • +
  • <axis>.config.startup_closed_loop_control

  • +
+

See here for a description of each state.

+
+
+

Control Mode

+

The default control mode is position control. +If you want a different mode, you can change <axis>.controller.config.control_mode. +Possible values are listed here.

+
+
+

Input Mode

+

As of version v0.5.0, ODrive now intercepts the incoming commands and can apply filters to them. +The old protocol values pos_setpoint, vel_setpoint, and current_setpoint are still used internally by the closed-loop cascade control, but the user cannot write to them directly. +This allows us to condense the number of ways the ODrive accepts motion commands.

+
+

Control Commands

+
    +
  • <axis>.controller.input_pos = <turn>

  • +
  • <axis>.controller.input_vel = <turn/s>

  • +
  • <axis>.controller.input_torque = <torque in Nm>

  • +
+

Modes can be selected by changing <axis>.controller.config.input_mode. +The default input mode is INPUT_MODE_PASSTHROUGH. +Possible values are listed here.

+
+
+
+
+

System Monitoring Commands

+
+

Encoder Position and Velocity

+
    +
  • View encoder position with <axis>.encoder.pos_estimate [turns] or <axis>.encoder.pos_est_counts [counts]

  • +
  • View rotational velocity with <axis>.encoder.vel_estimate [turn/s] or <axis>.encoder.vel_est_counts [count/s]

  • +
+
+
+

Motor Current and Torque Estimation

+
    +
  • View the commanded motor current with <axis>.motor.current_control.Iq_setpoint [A]

  • +
  • View the measured motor current with <axis>.motor.current_control.Iq_measured [A]. +If you find that this returns noisy data then use the command motor current instead. +The two values should be close so long as you are not approaching the maximum achievable rotational velocity of your motor for a given supply voltage, in which case the commanded current may become larger than the measured current.

  • +
+

Using the motor current and the known KV of your motor you can estimate the motors torque using the following relationship: Torque [N.m] = 8.27 * Current [A] / KV.

+
+
+
+

General System Commands

+
+

Saving the Configuration

+

All variables that are part of a [...].config object can be saved to non-volatile memory on the ODrive so they persist after you remove power. +The relevant commands are:

+
+
    +
  • <odrv>.save_configuration(): Stores the configuration to persistent memory on the ODrive.

  • +
  • <odrv>.erase_configuration(): Resets the configuration variables to their factory defaults. This also reboots the device.

  • +
+
+
+
+

Diagnostics

+
+
    +
  • <odrv>.serial_number: A number that uniquely identifies your device. When printed in upper case hexadecimal (hex(<odrv>.serial_number).upper()), this is identical to the serial number indicated by the USB descriptor.

  • +
  • <odrv>.fw_version_major, <odrv>.fw_version_minor, <odrv>.fw_version_revision: The firmware version that is currently running.

  • +
  • <odrv>.hw_version_major, <odrv>.hw_version_minor, <odrv>.hw_version_revision: The hardware version of your ODrive.

  • +
+
+
+
+
+

Setting up Sensorless

+

The ODrive can run without encoder/hall feedback, but there is a minimum speed, usually around a few hundred RPM. +In other words, sensorless mode does not support stopping or changing direction!

+

Sensorless mode starts by ramping up the motor speed in open loop control and then switches to closed loop control automatically. +The sensorless speed ramping parameters are in axis.config.sensorless_ramp. +The vel and accel (in [radians/s] and [radians/s^2]) parameters control the speed that the ramp tries to reach and how quickly it gets there. +When the ramp reaches sensorless_ramp.vel, controller.input_vel is automatically set to the same velocity, in [turns/s], and the state switches to closed loop control.

+

If your motor comes to a stop after the ramp, try incrementally raising the vel parameter. +The goal is to be above the minimum speed necessary for sensorless position and speed feedback to converge - this is not well-parameterized per motor. +The parameters suggested below work for the D5065 motor, with 270KV and 7 pole pairs. +If your motor grinds and skips during the ramp, lower the accel parameter until it is tolerable.

+

Below are some suggested starting parameters that you can use for the ODrive D5065 motor. +Note that you must set the pm_flux_linkage correctly for sensorless mode to work. +Motor calibration and setup must also be completed before sensorless mode will work.

+
odrv0.axis0.controller.config.vel_gain = 0.01
+odrv0.axis0.controller.config.vel_integrator_gain = 0.05
+odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL
+odrv0.axis0.controller.config.vel_limit = <a value greater than axis.config.sensorless_ramp.vel / (2pi * <pole_pairs>)>
+odrv0.axis0.motor.config.current_lim = 2 * odrv0.axis0.config.sensorless_ramp.current
+odrv0.axis0.sensorless_estimator.config.pm_flux_linkage = 5.51328895422 / (<pole pairs> * <motor kv>)
+odrv0.axis0.config.enable_sensorless_mode = True
+
+
+

To start the motor:

+
odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/configuring-eclipse.html b/docs/reStructuredText/_build/html/configuring-eclipse.html new file mode 100644 index 00000000..0285900e --- /dev/null +++ b/docs/reStructuredText/_build/html/configuring-eclipse.html @@ -0,0 +1,215 @@ + + + + + + Setting up Eclipse development environment — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Setting up Eclipse development environment

+ + +
+

Import Project

+
    +
  • File -> Import -> C/C++ -> Existing Code as Makefile Project

  • +
  • Browse for existing code location, find the OdriveFirmware root.

  • +
  • In the Toolchain options, select Cross GCC

  • +
  • Hit Finish

  • +
  • Build the project (press ctrl-B)

  • +
+
+Toolchain options +
+
+
+

Load the Launch Configuration

+
    +
  • File -> Import -> Run/Debug -> Launch Configurations -> Next

  • +
  • Highlight (don’t tick) the OdriveFirmare folder in the left column

  • +
  • Tick OdriveFirmware.launch in the right column

  • +
  • Hit Finish

  • +
+
+Launch Configurations +
+
+
+

Launch!

+
    +
  • Make sure the programmer is connected to the board as per Flashing the firmware.

  • +
  • +
    Press the down-arrow of the debug symbol in the toolbar, and hit Debug Configurations
      +
    • You can also hit Run -> Debug Configurations

    • +
    +
    +
    +
  • +
  • Highlight the debug configuration you imported, called OdriveFirmware. +If you do not see the imported launch configuration rename your project to ODriveFirmware or edit the launch configuration to match your project name by unfiltering unavailable projects:

  • +
+
+Launch Configurations Filters +
+
    +
  • Hit Debug

  • +
  • Eclipse should flash the board for you and the program should start halted on the first instruction in Main

  • +
  • Set beakpoints, step, hit Resume, etc.

  • +
  • Make some cool features! ;D

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/configuring-vscode.html b/docs/reStructuredText/_build/html/configuring-vscode.html new file mode 100644 index 00000000..724498f6 --- /dev/null +++ b/docs/reStructuredText/_build/html/configuring-vscode.html @@ -0,0 +1,246 @@ + + + + + + Configuring Visual Studio Code — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Configuring Visual Studio Code

+ +

VSCode is the recommended IDE for working with the ODrive codebase. +It is a light-weight text editor with Git integration and GDB debugging functionality.

+

Before doing the VSCode setup, make sure you’ve installed all of your prerequisites.

+
+

Setup Procedure

+
    +
  1. Clone the ODrive repository

  2. +
  3. Download VSCode

  4. +
  5. Open VSCode

  6. +
  7. Install extensions. This can be done directly from VSCode (Ctrl + Shift + X)

    +
    +
      +
    • +
      Required extensions:
        +
      • C/C++ ext install ms-vscode.cpptools

      • +
      • Cortex-Debug ext install marus25.cortex-debug

      • +
      • Cortex-Debug: Device Support Pack - STM32F4 ext install marus25.cortex-debug-dp-stm32f4

      • +
      +
      +
      +
    • +
    • +
      Recommended Extensions:
        +
      • Include Autocomplete

      • +
      • Path Autocomplete

      • +
      • Auto Comment Blocks

      • +
      +
      +
      +
    • +
    +
    +
  8. +
  9. Create an environment variable named ARM_GCC_ROOT whose value is the location of the GNU Arm Embedded Toolchain (.e.g C:\Program Files (x86)\GNU Tools Arm Embedded\7 2018-q2-update) that you installed in the prerequisites section of the developer’s guide. +This is not strictly needed for Linux or Mac, and you can alternatively use the Cortex-debug: Arm Toolchain Path setting in VSCode extension settings.

  10. +
  11. Relaunch VSCode

  12. +
  13. Open the VSCode Workspace file, which is located in the root of the ODrive repository. It is called ODrive_Workspace.code-workspace. +The first time you open it, VSCode will install some dependencies. If it fails, you may need to change your proxy settings.

  14. +
+

You should now be ready to compile and test the ODrive project.

+
+
+

Building the Firmware

+
    +
  • Terminal -> Run Build Task (Ctrl1 + Shift + B)

  • +
+

A terminal window will open with your native shell. VSCode is configured to run the command make -j4 in this terminal.

+
+
+

Flashing the Firmware

+
    +
  • Terminal -> Run Task -> flash

  • +
+

A terminal window will open with your native shell. VSCode is configured to run the command make flash in this terminal.

+

If the flashing worked, you can connect to the board using the odrivetool.

+
+
+

Debugging

+

An extension called Cortex-Debug has recently been released which is designed specifically for debugging ARM Cortex projects. +You can read more on Cortex-Debug here.

+

Note: If developing on Windows, you should have arm-none-eabi-gdb and openOCD on your PATH.

+
+
    +
  • Make sure you have the Firmware folder as your active folder

  • +
  • Set CONFIG_DEBUG=true in the tup.config file

  • +
  • Flash the board with the newest code (starting debug session doesn’t do this)

  • +
  • In the Run tab (Ctrl + Shift + D), select “Debug ODrive (Firmware)”

  • +
  • Press Start Debugging (or press F5)

  • +
  • The processor will reset and halt.

  • +
  • Set your breakpoints. Note: you can only set breakpoints when the processor is halted, if you set them during run mode, they won’t get applied.

  • +
  • Continue (press F5)

  • +
  • Stepping over/in/out, restarting, and changing breakpoints can be done by first pressing the “pause” (F6) button at the top the screen.

  • +
  • When done debugging, simply stop (Shift + F5) the debugger. It will kill your openOCD process too.

  • +
+
+
+
+

Cleaning the Build

+

This sometimes needs to be done if you change branches.

+
    +
  • Open a terminal (View -> Integrated Terminal) and enter make clean

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/control-modes.html b/docs/reStructuredText/_build/html/control-modes.html new file mode 100644 index 00000000..702ec2f4 --- /dev/null +++ b/docs/reStructuredText/_build/html/control-modes.html @@ -0,0 +1,325 @@ + + + + + + Control Modes — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Control Modes

+

The default control mode is unfiltered position control in the absolute encoder reference frame. +You may wish to use a controlled trajectory instead. +Or you may wish to control position in a circular frame to allow continuous rotation forever without growing the numeric value of the setpoint too large.

+

You may also wish to control velocity (directly or with a ramping filter). +You can also directly control the current of the motor, which is proportional to torque.

+ +
+

Filtered Position Control

+

Asking the ODrive controller to go as hard as it can to raw setpoints may result in jerky movement. +Even if you are using a planned trajectory generated from an external source, if that is sent at a modest frequency, the ODrive may chase each stair in the incoming staircase in a jerky way. +In this case, a good starting point for tuning the filter bandwidth is to set it to one half of your setpoint command rate.

+

You can use the second order position filter in these cases. +Set the filter bandwidth [Hz]:

+
axis.controller.config.input_filter_bandwidth = 2.0
+
+
+

Activate the setpoint filter:

+
axis.controller.config.input_mode = INPUT_MODE_POS_FILTER
+
+
+

You can now control the position with

+
axis.controller.input_pos = 1
+
+
+
+secondOrderResponse +

Step response of a 1000 to 0 position input with a filter bandwidth of 1.0 Hz

+
+
+
+

Trajectory Control

+

See the Usage section for usage details.

+

This mode lets you smoothly accelerate, coast, and decelerate the axis from one position to another. With raw position control, the controller simply tries to go to the setpoint as quickly as possible. Using a trajectory lets you tune the feedback gains more aggressively to reject disturbance, while keeping smooth motion.

+
+TrapTrajPosVel +

Position (blue) and velocity (orange) vs. time using trajectory control.

+
+
+

Parameters

+
odrv0.axis0.trap_traj.config.vel_limit = <Float>
+
+
+
odrv0.axis0.trap_traj.config.accel_limit = <Float>
+
+
+
odrv0.axis0.trap_traj.config.decel_limit = <Float>
+
+
+
odrv0.axis0.controller.config.inertia = <Float>
+
+
+
    +
  • vel_limit is the maximum planned trajectory speed. This sets your coasting speed.

  • +
  • accel_limit is the maximum acceleration in turns / sec^2

  • +
  • decel_limit is the maximum deceleration in turns / sec^2

  • +
  • controller.config.inertia is a value which correlates acceleration (in turns / sec^2) and motor torque. It is 0 by default. It is optional, but can improve response of your system if correctly tuned. Keep in mind this will need to change with the load / mass of your system.

  • +
+
+

Note

+

All values should be strictly positive (>= 0).

+
+

Keep in mind that you must still set your safety limits as before. It is recommended you set these a little higher ( > 10%) than the planner values, to give the controller enough control authority.

+
odrv0.axis0.motor.config.current_lim = <Float>
+
+
+
odrv0.axis0.controller.config.vel_limit = <Float>
+
+
+
+
+

Usage

+

Make sure you are in position control mode. To activate the trajectory module, set the input mode to trajectory:

+
odrv0.axis0.controller.config.input_mode = INPUT_MODE_TRAP_TRAJ
+
+
+

Simply send a position command to execute the move:

+
odrv0.axis0.controller.input_pos = <Float>
+
+
+

Use the move_incremental function to move to a relative position.

+
odrv0.axis0.controller.move_incremental(pos_increment, from_goal_point)
+
+
+

To set the goal relative to the current actual position, use from_goal_point = False +To set the goal relative to the previous destination, use from_goal_point = True

+

You can also execute a move with the appropriate ascii command.

+
+
+
+

Circular Position Control

+

To enable Circular position control, set

+
odrv0.axis0.controller.config.circular_setpoints = True
+
+
+

This mode is useful for continuous incremental position movement. +For example a robot rolling indefinitely, or an extruder motor or conveyor belt moving with controlled increments indefinitely. +In the regular position mode, the input_pos would grow to a very large value and would lose precision due to floating point rounding.

+

In this mode, the controller will try to track the position within only one turn of the motor. Specifically, input_pos is expected in the range [0, 1). +If the input_pos is incremented to outside this range (say via step/dir input), it is automatically wrapped around into the correct value. +Note that in this mode encoder.pos_circular is used for feedback instead of encoder.pos_estimate.

+

If you try to increment the axis with a large step in one go that exceeds 1 turn, the motor will go to the same angle around the wrong way. +This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a larger circular range. +Set

+
odrv0.axis0.controller.config.circular_setpoints_range = <N>
+
+
+

Choose N to give you an appropriate circular space for your application.

+
+
+

Velocity Control

+

Set the control mode

+
odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL
+
+
+

You can now control the velocity [turn/s] with

+
odrv0.axis0.controller.input_vel = 1
+
+
+
+
+

Ramped Velocity Control

+

Set the control mode

+
odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL
+
+
+

Set the velocity ramp rate (acceleration in turn/s^2):

+
odrv0.axis0.controller.config.vel_ramp_rate = 0.5
+
+
+

Activate the ramped velocity mode:

+
odrv0.axis0.controller.config.input_mode = INPUT_MODE_VEL_RAMP
+
+
+

You can now control the velocity (turn/s) with

+
odrv0.axis0.controller.input_vel = 1
+
+
+
+
+

Torque Control

+

Set the control mode

+
odrv0.axis0.controller.config.control_mode = CONTROL_MODE_TORQUE_CONTROL
+
+
+

You can now control the torque (Nm) with

+
odrv0.axis0.controller.input_torque = 0.1
+
+
+
+

Note

+

If you exceed vel_limit in torque control mode, the current is reduced. +To disable this, set

+
odrv0.axis0.controller.enable_torque_mode_vel_limit = False
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/control.html b/docs/reStructuredText/_build/html/control.html new file mode 100644 index 00000000..e29549cd --- /dev/null +++ b/docs/reStructuredText/_build/html/control.html @@ -0,0 +1,281 @@ + + + + + + Control Structure and Tuning — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Control Structure and Tuning

+ +

The motor controller is a cascaded style position, velocity and current control loop, as per the diagram below. +When the control mode is set to position control, the whole loop runs. +When running in velocity control mode, the position control part is removed and the velocity command is fed directly in to the second stage input. +In torque control mode, only the current controller is used.

+
+controller_with_ff +

Cascaded position and velocity I loops

+
+

Each stage of the control loop is a variation on a PID controller. +A PID controller is a mathematical model that can be adapted to control a wide variety of systems. +This flexibility is essential as it allows the ODrive to be used to control all kinds of mechanical systems.

+
+

Note

+

The controller has been updated to use torque in Newton-meters instead of current at the “system” level. +There is a torque_constant parameter which converts between torque and current, after which the rest of this explanation still holds.

+
+
+

Position Control Loop

+

The position controller is a P loop with a single proportional gain,

+
+
+\[ \begin{align}\begin{aligned}\text{pos_error} &= \text{pos_setpoint} - \text{pos_feedback},\\\text{vel_cmd} &= \text{pos_error} * \text{pos_gain} + \text{vel_feedforward}.\end{aligned}\end{align} \]
+
+
+
+

Velocity Control Loop

+

The velocity controller is a PI loop where

+
+
+\[ \begin{align}\begin{aligned}\text{vel_error} &= \text{vel_cmd} - \text{vel_feedback},\\\text{current_integral} &+= \text{vel_error} * \text{vel_integrator gain},\\\text{current_cmd} &= \text{vel_error} * \text{vel_gain} + \text{current_integral} + \text{current_feedforward}.\end{aligned}\end{align} \]
+
+
+
+

Current Control Loop

+

The current controller is also a PI loop,

+
+
+\[ \begin{align}\begin{aligned}\text{current_error} &= \text{current_cmd} - \text{current_feedback},\\\text{voltage_integral} &+= \text{current_error} * \text{current_integrator gain},\\\text{voltage_cmd} &= \text{current_error} * \text{current_gain} + \text{voltage_integral} + ...\\& ... + \text{voltage_feedforward} \textbf{ (when we have motor model)}.\end{aligned}\end{align} \]
+
+
+

Note

+

current_gain and current_integrator_gain are automatically set according to motor.config.current_control_bandwidth

+
+

For more detail refer to controller.cpp.

+
+
+

Controller Details

+

The ultimate output of the controller is the voltage applied to the gate of each FET to deliver current through each coil of the motor. +The current through the motor linearly relates to the torque output of the motor. +This means that the inputs to the cascaded controller are theoretically the position (angle), velocity (angle/time), and acceleration (angle/time/time) of the motor. +Note that when thinking about the controller from the perpective of the physics of the motor you would expect to see the time in the Velocity and Current loops, but it is absent because the time difference between iterations is always 125 microseconds (8kHz). +Because the time difference between controller loops is a constant and can simply be wrapped into the controller gains.

+

The output of each stage of the controller is clamped before being fed into the next stage. +So after the vel_cmd is calculated from the position controller, the vel_cmd is clamped to the velocity limit. +The torque_cmd output of the velocity controller is then clamped and fed to the current controller. +Oddly enough the controller class does not contain the current controller, but instead the current controller is housed in the motor class due to the complexity of the motor driver schema.

+

The feedforward terms available when using the position or velocity control mode are meant to enable better performance when the dynamics of a system are known and the host controller can predict the motion based on the load. +A perfect example of this is the use of the trajectory controller that sets the position, velocity, and torque based on the desired position, velocity, and acceleration. +If you take a trapezoidal velocity profile for example, you can imagine on the ramp upward the velocity will be increasing over time, while the torque is a non-zero constant. +At the flat portion of the profile the velocity will be a non-zero constant, but the acceleration will be zero. +This trajectory controller use case uses the cascaded controller with multiple inputs to achieve the desired motion with the best performance.

+
+
+

Tuning

+

Tuning the motor controller is an essential step to unlock the full potential of the ODrive. +Tuning allows for the controller to quickly respond to disturbances or changes in the system (such as an external force being applied or a change in the setpoint) without becoming unstable. +Correctly setting the three tuning parameters (called gains) ensures that ODrive can control your motors in the most effective way possible. +The three (starting) values are:

+
    +
  • pos_gain [(turn/s) / turn]:

    +
    +
    odrv0.axis0.controller.config.pos_gain = 20.0
    +
    +
    +
    +
  • +
  • vel_gain [Nm/(turn/s)]:

    +
    +
    odrv0.axis0..controller.config.vel_gain = 0.16
    +
    +
    +
    +
  • +
  • vel_integrator_gain [Nm/((turn/s) * s)]:

    +
    +
    odrv0.axis0.controller.config.vel_integrator_gain = 0.32
    +
    +
    +
    +
  • +
+
+
An upcoming feature will enable automatic tuning. Until then, here is a rough tuning procedure:
    +
  1. Set vel_integrator_gain gain to 0

  2. +
  3. Make sure you have a stable system. If it is not, decrease all gains until you have one.

  4. +
  5. Increase vel_gain by around 30% per iteration until the motor exhibits some vibration.

  6. +
  7. Back down vel_gain to 50% of the vibrating value.

  8. +
  9. Increase pos_gain by around 30% per iteration until you see some overshoot.

  10. +
  11. Back down pos_gain until you do not have overshoot anymore.

  12. +
  13. The integrator can be set to 0.5 * bandwidth * vel_gain, where bandwidth is the overall resulting tracking bandwidth of your system. +Say your tuning made it track commands with a settling time of 100ms (the time from when the setpoint changes to when the system arrives at the new setpoint); this means the bandwidth was \(\frac{1}{100ms} = \frac{1}{0.1s} = 10Hz\). +In this case you should set

    +
    +
    vel_integrator_gain = 0.5 * 10 * <vel_gain>
    +
    +
    +
    +
  14. +
+
+
+

The liveplotter tool can be immensely helpful in dialing in these values. +To display a graph that plots the position setpoint vs the measured position value run the following in the ODrive tool:

+
start_liveplotter(lambda:[odrv0.axis0.encoder.pos_estimate, odrv0.axis0.controller.pos_setpoint])
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/developer-guide.html b/docs/reStructuredText/_build/html/developer-guide.html new file mode 100644 index 00000000..fa35037d --- /dev/null +++ b/docs/reStructuredText/_build/html/developer-guide.html @@ -0,0 +1,815 @@ + + + + + + ODrive Firmware Developer Guide — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

ODrive Firmware Developer Guide

+ +

This guide is intended for developers who wish to modify the firmware of the ODrive. +As such it assumes that you know things like how to use Git, what a compiler is, etc. If that sounds scary, turn around now.

+

The official releases are maintained on the master branch. However since you are a developer, you are encouraged to use the devel branch, as it contains the latest features.

+

The project is under active development, so make sure to check the Changelog to keep track of updates.

+
+

Prerequisites

+

The recommended tools for ODrive development are:

+
+
    +
  • make: Used to invoke tup

  • +
  • Tup: The build system used to invoke the compile commands

  • +
  • ARM GNU Compiler: For cross-compiling code

  • +
  • ARM GDB: For debugging the code and stepping through on the device

  • +
  • OpenOCD: For flashing the ODrive with the STLink/v2 programmer

  • +
  • Python 3, along with the packages PyYAML, Jinja2 and jsonschema: For running the Python tools (odrivetool). Also required for compiling firmware.

  • +
+
+

See below for specific installation instructions for your OS.

+

Depending on what you’re gonna do, you may not need all of the components.

+

Once you have everything, you can verify the correct installation by running:

+
arm-none-eabi-gcc --version
+arm-none-eabi-gdb --version
+openocd --version             # should be 0.10.0 or later
+tup --version                 # should be 0.7.5 or later
+python --version              # should be 3.7 or later
+
+
+

Installing Prerequisites

+
+
sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa
+
+
+
sudo apt-get update
+
+
+
sudo apt-get install gcc-arm-embedded
+
+
+
sudo apt-get install openocd
+
+
+
sudo apt-get install git-lfs
+
+
+
sudo add-apt-repository ppa:jonathonf/tup && sudo apt-get update && sudo apt-get install tup
+
+
+
sudo apt-get install python3 python3-yaml python3-jinja2 python3-jsonschema
+
+
+
+
+
+

Configuring the Build

+

To customize the compile time parameters, copy or rename the file Firmware/tup.config.default to Firmware/tup.config and edit the following parameters in that file:

+
    +
  • CONFIG_BOARD_VERSION The board version you’re using. Can be v3.1, v3.2, v3.3, v3.4-24V, v3.4-48V, v3.5-24V, v3.5-48V, etc. Check for a label on the upper side of the ODrive to find out which version you have. +Some ODrive versions don’t specify the voltage: in that case you can read the value of the main capacitors: 120uF are 48V ODrives, 470uF are 24V ODrives.

  • +
  • CONFIG_DEBUG Defines whether debugging will be enabled when compiling the firmware; specifically the -g -gdwarf-2 flags. +Note that printf debugging will only function if your tup.config specifies the USB_PROTOCOL or UART_PROTOCOL as stdout and DEBUG_PRINT is defined. +See the IDE specific documentation for more information.

  • +
+

You can also modify the compile-time defaults for all .config parameters. +You will find them if you search for AxisConfig, MotorConfig, etc.

+
+
+

Building and Flashing the Firmware

+
    +
  1. Run make in the Firmware directory.

  2. +
  3. Connect the ODrive via USB and power it up.

  4. +
  5. Flash the firmware using odrivetool dfu.

  6. +
+ +
+
+

Testing

+
+

Automated Testing

+ +

This section describes how to use the automated testing facilities. +You don’t have to do this as an end user.

+

The testing facility consists of the following components:

+
+
    +
  • Test rig: In the simplest case this can be a single ODrive optionally with a single motor and encoder pair. Can also be multiple ODrives with multiple axes, some of which may be mechanically coupled.

  • +
  • Test host: The PC on which the test script runs. All ODrives must be connected to the test host via USB.

  • +
  • test-rig.yaml: Describes your test rig. Make sure all values are correct. Incorrect values may physically break or fry your test setup.

  • +
  • test_runner.py: This is the main script that runs all the tests.

  • +
  • …_test.py The actual tests

  • +
+
+
+

The Tests

+
+
    +
  • analog_input_test.py: Analog Input

  • +
  • calibration_test.py: Motor calibration, encoder offset calibration, encoder direction find, encoder index search

  • +
  • can_test.py: Partial coverage of the commands described in CAN Protocol.

  • +
  • closed_loop_test.py: Velocity control, position control (TODO: sensorless control), brake regen current hard limit, current control with velocity limiting

  • +
  • encoder_test.py: Incremental encoder, hall effect encoder, sin/cos encoder, SPI encoders (AMS, CUI)

  • +
  • fibre_test.py: General USB protocol tests

  • +
  • nvm_test.py: Configuration storage

  • +
  • pwm_input_test.py: PWM input

  • +
  • step_dir_test.py: Step/dir input

  • +
  • uart_ascii_test.py: Partial coverage of the commands described in ASCII Protocol

  • +
+
+

All tests in a file can be run with e.g.:

+
python3 uart_ascii_test.py --test-rig-yaml ../../test-rig-rpi.yaml
+
+
+

See the following sections for a more detailed test flow description.

+
+
+

Our Test Rig

+

Our test rig essentially consists of the following components:

+
+
    +
  • an ODrive as the test subject

  • +
  • a Teensy 4.0 to emulate external hardware such as encoders

  • +
  • a Motor + Encoder pair for closed loop control tests

  • +
  • a Raspberry Pi 4.0 as test host

  • +
  • a CAN hat for the Raspberry Pi for CAN tests

  • +
+
+

This document is therefore centered around this test rig layout. +If your test rig differs, you may be able to run some but not all of the tests.

+
+
+

How to set up a Raspberry Pi as testing host

+

#. Install Raspbian Lite on a Raspberry Pi 4.0. This is easiest if you have a keyboard, mouse and screen (micro-HDMI!). +I used the NOOBS Lite installer for this. Paste the ZIP-file’s contents onto a FAT32 formatted SD card (fs type 0b in fdisk) and boot it. Then follow the on-screen instructions.

+
    +
  1. Prepare the installation:

    +
    +
    sudo systemctl enable ssh
    +sudo systemctl start ssh
    +# Transfer your public key for passwordless SSH. All subsequent steps can be done via SSH.
    +sudo apt-get update
    +sudo apt-get upgrade
    +# Change /etc/hostname to something meaningful
    +
    +
    +
    +
  2. +
  3. Add the following lines to /boot/config.txt:

    +
    +
      +
    • enable_uart=1

    • +
    • dtparam=spi=on

    • +
    • dtoverlay=spi-bcm2835-overlay

    • +
    • +
      dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25
      +

      Note

      +

      These oscillator and interrupt GPIO settings here are for the “RS485 CAN HAT” I have. There appear to be multiple versions, so they may be different from yours. Check the marking on the oscillator and the schematics.

      +
      +
      +
      +
    • +
    +
    +
  4. +
  5. +
    Remove the following arguments from /boot/cmdline.txt:
      +
    • console=serial0,115200

    • +
    +
    +
    +
  6. +
  7. Append ODRIVE_TEST_RIG_NAME=[test-rig-name] to /etc/environment. The HWIL tests use this to look up the the file [test-rig-name].yaml which is supposed to describe your test rig.

  8. +
  9. Reboot.

  10. +
  11. Install the prerequisites:

    +
    +
    sudo apt-get install ipython3 python3-appdirs python3-yaml python3-jinja2 python3-usb python3-serial python3-can python3-scipy python3-matplotlib python3-ipdb git openocd
    +# Optionally, to be able to compile the firmware:
    +sudo apt-get install gcc-arm-none-eabi
    +
    +
    +
    +
  12. +
  13. Install Teensyduino and teensy-loader-cli:

    +
    +
    sudo apt-get install libfontconfig libxft2 libusb-dev
    +
    +
    +
    wget https://downloads.arduino.cc/arduino-1.8.13-linuxarm.tar.xz
    +
    +
    +
    tar -xf arduino-1.8.13-linuxarm.tar.xz
    +
    +
    +
    wget https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linuxarm
    +
    +
    +
    chmod +x TeensyduinoInstall.linuxarm
    +
    +
    +
    ./TeensyduinoInstall.linuxarm --dir=arduino-1.8.13
    +
    +
    +
    sudo cp -R arduino-1.8.13 /usr/share/arduino
    +
    +
    +
    sudo ln -s /usr/share/arduino/arduino /usr/bin/arduino
    +
    +
    +
    git clone https://github.com/PaulStoffregen/teensy_loader_cli
    +
    +
    +
    pushd teensy_loader_cli
    +
    +
    +
    make
    +
    +
    +
    sudo cp teensy_loader_cli /usr/bin/
    +
    +
    +
    sudo ln -s /usr/bin/teensy_loader_cli /usr/bin/teensy-loader-cli
    +
    +
    +
    popd
    +
    +
    +
    curl https://www.pjrc.com/teensy/49-teensy.rules | sudo tee /etc/udev/rules.d/49-teensy.rules
    +
    +
    +
    +
  14. +
  15. Add the following lines to /etc/udev/rules.d/49-stlinkv2.rules:

    +
    +
    SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", MODE:="0666"
    +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", MODE:="0666"
    +
    +
    +
    +
  16. +
  17. sudo mkdir /opt/odrivetest && sudo chown $USER /opt/odrivetest

  18. +
  19. At this point you need the ODrive repository. See next section to sync it from your main PC. We assume now that you navigated to tools/odrive/tests/.

  20. +
  21. sudo ../../odrivetool udev-setup

  22. +
  23. sudo udevadm trigger

  24. +
  25. Run once after every reboot: sudo -E ipython3 --pdb test_runner.py -- --setup-host

  26. +
+
+
+

SSH Testing Flow

+

Here’s one possible workflow for developing on the local host and testing on a remote SSH host.

+

We assume that the ODrive repo is at /path/to/ODriveFirmware and your testing host is configured under the SSH name odrv.

+

To flash and start remote debugging:

+
    +
  1. Start OpenOCD remotely, along with a tunnel to localhost:

    +
    +
    ssh -t odrv -L3333:localhost:3333 bash -c "\"openocd '-f' 'interface/stlink-v2.cfg' '-f' 'target/stm32f4x_stlink.cfg'\""
    +
    +
    +

    You can keep this open for multiple debug sessions. Press Ctrl + C to quit.

    +
    +
  2. +
  3. Compile the firmware.

  4. +
  5. In VSCode, select the run configuration “Debug ODrive v3.x/v4.x - Remote” and press Run. This will flash the new firmware before dropping you into the debugger.

  6. +
+

To run a test:

+
+
rsync -avh -e ssh /path/to/ODriveFirmware/ odrv:/opt/odrivetest --exclude="Firmware/build" --exclude="Firmware/.tup" --exclude=".git" --exclude="GUI" --delete
+
+
+
ssh odrv
+
+
+
cd /opt/odrivetest/tools/odrive/tests/
+ipython3 --pdb uart_ascii_test.py
+
+
+
+
+
+
+
+

Debugging

+

If you’re using VSCode, make sure you have the Cortex Debug extension, OpenOCD, and the STLink. +You can verify that OpenOCD and STLink are working by ensuring you can flash code. +Open the ODrive_Workspace.code-workspace file, and start a debugging session (F5). +VSCode will pick up the correct settings from the workspace and automatically connect. +Breakpoints can be added graphically in VSCode.

+
    +
  • Run make gdb. This will reset and halt at program start. Now you can set breakpoints and run the program. If you know how to use gdb, you are good to go.

  • +
+
+
+

Setting up an IDE

+

For working with the ODrive code you don’t need an IDE, but the open-source IDE VSCode is recommended. +It is also possible to use Eclipse. If you’d like to go that route, please see the respective configuration document:

+ +
+
+

STM32CubeMX

+

This project uses the STM32CubeMX tool to generate startup code and to ease the configuration of the peripherals. +You can download it from here <http://www2.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-configurators-and-code-generators/stm32cubemx.html?icmp=stm32cubemx_pron_pr-stm32cubef2_apr2014&sc=stm32cube-pr2>`___. +All CubeMX related files are in :code:`Firmware/Board/v3.

+

You will likely want the pinout for this process. It is available here.

+
+

Maintaining Modified Generated Code

+

When generating the code, STM32CubeMX will nuke everything except some special sections that they provide. +These sections are marked like USER CODE BEGIN…`USER CODE END`. +We used to try to make sure all edits we made to the generated code would only go in these sections, so some code structrure may reflect that. +However over time we realized this will not be tenable, so instead we use git to rebase all changes of the generated code whenever we need to regenerate it. +We use two special branches that will help us to do this, they are STM32CubeMX-start and STM32CubeMX-end. +How to use these is shown in the following example.

+
+

Note

+

Due to how this rebasing is done, all development that changes the generated code should be done directly on STM32CubeMX-end, and not based on devel, then follow step 4 below to carry them over to your feature branch. If you did some changes to the generated code based from devel, you need to cherry pick just those changes over to STM32CubeMX-end.

+
+
    +
  1. Ensuring a clean slate

  2. +
+
+
    +
  • We do all changes to the STM32CubeMX config and regenerate the code on top of STM32CubeMX-start. +* git checkout STM32CubeMX-start

  • +
  • Run stm32cubeMX and load the Firmware/Board/v3/Odrive.ioc project file. +* If the tool asks if you wish to migrate to a new version, choose to download the old firmware package (unless you want to use the latest libraries)

  • +
  • Without changing any settings, press Project -> Generate code.

  • +
  • You may need to let it download some drivers and such.

  • +
  • STM32CubeMX may now have a newer version of some of the libraries, so there may be changes to the generated code even though we didn’t change any settings. We need to check that everything is still working, and hence check in the changes:

  • +
  • git config --local core.autocrlf input - This will tell git that all files should be checked in with LF endings (CubeMX generates CRLF endings).

  • +
  • git diff - Ignore the pile of line ending warnings.

  • +
  • If you feel qualified: you can now ispect if CubeMX introduced something stupid. If there were any changes, and they look acceptable, we should commit them:

    +
      +
    • git commit -am "Run STM32CubeMX v1.21" - Replace with actual version of CubeMX

    • +
    +
  • +
+
+
    +
  1. Making Changes to the STM32CubeMX Config

  2. +
+
+
    +
  • After completing the above steps, make sure the working directory is clean: +* git status should include “nothing to commit, working tree clean”

  • +
  • Make your changes in STM32CubeMX, save the project and generate the code. (Project -> Generate code)

  • +
  • git diff - Check that the introduced changes are as expected

  • +
  • If everything looks ok, you can commit your changes.

  • +
+
+
    +
  1. Rebasing the Modifications to the Generated Code

  2. +
+
+
    +
  • git checkout STM32CubeMX-end

  • +
  • git rebase STM32CubeMX-start

  • +
  • Make sure the rebase finishes, fixing any conflicts that may arise

  • +
+
+
    +
  1. Merge New STM32CubeMX Code to your Feature Branch

  2. +
+
+

Simply merge the new state at:code: STM32CubeMX-end into your feature branch. +* git checkout your-feature +* git merge STM32CubeMX-end

+
+
    +
  1. Pushing back Upstream

  2. +
+
+
    +
  • Generate a PR like normal for your feature.

  • +
  • Make sure youhave pushed to the STM32CubeMX-start and STM32CubeMX-end branches on your fork.

  • +
  • Make a note in your PR to the maintainer that they need to update the STM32CubeMX branches when they merge the PR.

  • +
+
+
+
+
+

Troubleshooting

+ +
+

Cannot identify target as a STM32 family when flashing using openocd

+

Problem: When I try to flash ODrive v4.1 with make flash then I get:

+
[...]
+** Programming Started **
+auto erase enabled
+Info : device id = 0x10006452
+Warn : Cannot identify target as a STM32 family.
+Error: auto_probe failed
+embedded:startup.tcl:487: Error: ** Programming Failed **
+in procedure 'program'
+in procedure 'program_error' called at file "embedded:startup.tcl", line 543
+at file "embedded:startup.tcl", line 487
+
+
+

Solution: +Compile and install a recent version of openocd from source. +The latest official release (0.10.0 as of Nov 2020) doesn’t support the STM32F722 yet.

+
sudo apt-get install libtool libusb-1.0
+git clone https://git.code.sf.net/p/openocd/code openocd
+cd openocd/
+./bootstrap
+./configure --enable-stlink
+make
+sudo make install
+
+
+
+
+
+

Documentation

+
+

TODO

+

Documentation refactor in progress, changing significantly

+
    +
  • prerequisites +* Install Sphinx: pip install -U sphinx +* Install packages: pip install sphinx-copybutton sphinx-panels sphinx-rtd-theme

  • +
  • Run make html within the ./docs/reStructuredText folder

  • +
  • Open ./docs/reStructuredText/_build/index.html to view

  • +
+
+
+
+

Modifying libfibre

+

If you need to modify libfibre add CONFIG_BUILD_LIBFIBRE=true to your tup.config and rerun make. +After this you can start odrivetool (on your local PC) and it will use the updated libfibre.

+

To cross-compile libfibre for the Raspberry Pi, run make libfibre-linux-armhf or make libfibre-all. +This will require a docker container. See ;:ref:fibre-cpp readme <../Firmware/fibre-cpp/README.md> for details.

+
docker run -it -v "$(pwd)":/build -v /tmp/build:/build/build -w /build fibre-compiler configs/linux-armhf.config
+
+
+

If you’re satisfied with the changes don’t forget to generate binaries for all +supported systems using make libfibre-all.

+
+
+

Releases

+

We use GitHub Releases to provide firmware releases.

+
    +
  1. Cut off the changelog to reflect the new release

  2. +
  3. Merge the release candidate into master.

  4. +
  5. Push a (lightweight) tag to the master branch. Follow the existing naming convention.

  6. +
  7. If you changed something in libfibre, regenerate the binaries using make libfibre-all. +See Modifying libfibre for details.

  8. +
  9. Push the python tools to PyPI (see setup.py for details).

  10. +
  11. Edit the release on GitHub to add a title and description (copy&paste from changelog).

  12. +
+
+
+

Code Maintenance Notes

+

The cortex M4F processor has hardware single precision float unit. However double precision operations are not accelerated, and hence should be avoided. +The following regex is helpful for cleaning out double constants:

+

find:

+
([-+]?[0-9]+\.[0-9]+(?:[eE][-+]?[0-9]+)?)([^f0-9e])
+
+
+

replace:

+
+
\1f\2
+
+
+
+
+
+

Notes for Contributors

+

In general the project uses the Google C++ Style Guide with a few exceptions:

+
+
    +
  • The default indentation is 4 spaces.

  • +
  • The 80 character limit is not very strictly enforced, merely encouraged.

  • +
  • The file extensions *.cpp and *.hpp are used instead of *.cc and *.h.

  • +
+
+

Your help is welcome! However before you start working on a feature/change that will take you a non-negligible amount of time and that you plan to upstream please discuss your plans with us on GitHub or Discord. +This will ensure that your implementation is in line with the direction that ODrive is going.

+

When filing a PR please go through this checklist:

+
+
    +
  • Make sure you adhere to the same coding style that we use (see note above).

  • +
  • Update CHANGELOG.md.

  • +
  • If you removed/moved/renamed things in odrive-interface.yaml make sure to add corresponding bullet points tp the “API migration notes” section in the changelog. Use git to compare against the devel branch.

  • +
  • Also, for each removed/moved/renamed API item use your IDE’s search feature to search for occurrences of this name. Update the places you found (this will usually be documentation and test scripts).

  • +
  • If you added things to odrive-interface.yaml make sure the new things have decent documentation in the YAML file. We don’t expect 100% coverage but use good sense of what to document.

  • +
  • Make sure your PR doesn’t contain spurious changes that unnecessarily add or remove whitespace. These add noise and make the reviewer’s lifes harder.

  • +
  • If you changed any enums in odrive-interface.yaml, make sure you update enums.py and ODriveEnums.h. +The file includes instructions on how to do this. Check the diff to verify that none of the existing enumerators changed their value.

  • +
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/encoders.html b/docs/reStructuredText/_build/html/encoders.html new file mode 100644 index 00000000..74c6fc82 --- /dev/null +++ b/docs/reStructuredText/_build/html/encoders.html @@ -0,0 +1,490 @@ + + + + + + Encoders — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Encoders

+ +
+

Known and Supported Encoders

+

Be sure to read the ODrive Encoder Guide.

+
+

All encoder types supported by ODrive require that you do some sort of encoder calibration. This requires the following:

+
    +
  • Selecting an encoder and mounting it to your motor

  • +
  • Choosing an interface (e.g., AB, ABI or SPI)

  • +
  • Connecting the pins to the odrive

  • +
  • Loading the correct odrive firmware (the default will work in many cases)

  • +
  • Motor calibration

  • +
  • Saving the settings in the odrive for correct bootup

  • +
+
+

Encoder Without Index Signal

+

During encoder offset calibration the rotor must be allowed to rotate without any biased load during startup. +That means mass and weak friction loads are fine, but gravity or spring loads are not okay.

+

In the odrivetool, run

+
<axis>.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION
+
+
+

To verify everything went well, check the following variables:

+
+
    +
  • <axis>.error should be 0.

  • +
  • <axis>.encoder.config.phase_offset - This should print a number, like -326 or 1364.

  • +
  • <axis>.encoder.config.direction - This should print 1 or -1.

  • +
+
+
+
+

Encoder With Index Signal

+

If you have an encoder with an index (Z) signal, you can avoid doing the offset calibration on every startup, and instead use the index signal to re-sync the encoder to a stored calibration.

+

Below are the steps to do the one-time calibration and configuration. +Note that you can follow these steps with one motor at a time, or all motors together, as you wish.

+
    +
  • Since you will only do this once, it is recommended that you mechanically disengage the motor from anything other than the encoder, so that it can spin freely.

  • +
  • Set <axis>.encoder.config.use_index to True.

  • +
  • Run <axis>.requested_state = AXIS_STATE_ENCODER_INDEX_SEARCH. +This will make the motor turn in one direction until it finds the encoder index.

  • +
  • Follow the calibration instructions for an encoder without index signal.

  • +
  • Set <axis>.encoder.config.pre_calibrated to True to confirm that the offset is valid with respect to the index pulse.

  • +
  • If you would like to search for the index at startup, set <axis>.config.startup_encoder_index_search to True. +* If you’d rather do it manually, just run <axis>.requested_state = AXIS_STATE_ENCODER_INDEX_SEARCH on every bootup.

  • +
  • If you are looking to start your machine as quickly as possible on bootup, also set <axis>.motor.config.pre_calibrated to True to save the current motor calibration and avoid doing it again on bootup.

  • +
  • Save the configuration by typing <odrv>.save_configuration() Enter.

  • +
+

That’s it, now on every reboot the motor will turn in one direction until it finds the encoder index.

+
    +
  • If your motor has problems reaching the index location due to the mechanical load, you can increase <axis>.motor.config.calibration_current.

  • +
+
+ +
+

Hall Effect Encoders

+

Hall effect encoders can also be used with ODrive. The encoder CPR should be set to 6 * <# of motor pole pairs>. +Due to the low resolution of hall effect encoders compared to other types of encoders, low speed performance will be worse than other encoder types.

+

When the encoder mode is set to hall feedback, the pinout on the encoder port is as follows:

+ ++++ + + + + + + + + + + + + + + + + +

Label on ODrive

Hall feedback

A

Hall A

B

Hall B

Z

Hall C

+

To use hall effect encoders, the calibration sequence is different than incremental or absolute encoders. +You must first run AXIS_STATE_ENCODER_HALL_POLARITY_CALIBRATION before AXIS_STATE_ENCODER_OFFSET_CALIBRATION The hall polarity calibration will automatically determine the order and polarity of the hall signals. +When using AXIS_STATE_FULL_CALIBRATION_SEQUENCE, these steps are automatically used if the encoder is set to hall mode.

+
+
+

Startup Sequence Notes

+

The following are variables that MUST be set up for your encoder configuration. Your values will vary depending on your encoder:

+
<axis>.encoder.config.cpr = 8192
+
+
+
<axis>.encoder.config.mode = ENCODER_MODE_INCREMENTAL
+
+
+

The following are examples of values that can impact the success of calibration. +These are not all of the variables you have to set for startup. +Only change these when you understand why they are needed; your values will vary depending on your setup:

+
    +
  • <axis>.motor.config.motor_type = MOTOR_TYPE_HIGH_CURRENT The type of motor you have. Valid choices are high current or gimbal.

  • +
  • <axis>.encoder.config.calib_range = 0.05 Helps to relax the accuracy of encoder counts during calibration

  • +
  • <axis>.motor.config.calibration_current = 10.0 The motor current used for calibration. For large motors, this value can be increased to overcome friction and cogging.

  • +
  • <axis>.motor.config.resistance_calib_max_voltage = 12.0 Max motor voltage used for measuring motor resistance. For motor calibration, it must be possible for the motor current to reach the calibration current without the applied voltage exceeding this config setting.

  • +
  • <axis>.controller.config.vel_limit = 5 [turn/s] low values result in the spinning motor stopping abruptly during calibration.

  • +
+

Lots of other values can get you. It’s a process. Thankfully there are a lot of good people that will help you debug calibration problems.

+

If calibration works, congratulations.

+

Now try:

+
<axis>.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
+<axis>.controller.input_vel = 1.5
+
+
+

let it loop a few times and then set:

+
<axis>.requested_state = AXIS_STATE_IDLE
+
+
+

Do you still have no errors? Awesome. Now, setup the motor and encoder to use known calibration values. +This allows you to skip motor calibration and encoder offset calibration before using closed loop control. +Note that this only works if you are using an absolute encoder or the encoder index input (see “Encoder with index signal” above).

+
<axis>.encoder.config.pre_calibrated = True
+<axis>.motor.config.pre_calibrated  = True
+
+
+

And see if ODrive agrees that the calibration worked by just running

+
<axis>.encoder.config.pre_calibrated
+
+
+
+

Note

+

(using no “= True” ). Make sure that ‘pre_calibrated’ is in fact True.

+
+

Also, if you have calibrated and encoder.pre_calibrated is equal to true, and you had no errors so far, run this:

+
odrv0.save_configuration()
+odrv0.reboot()
+
+
+

and now see if after a reboot you can run:

+
<axis>.requested_state = AXIS_STATE_ENCODER_INDEX_SEARCH
+
+
+

without getting errors.

+
+
+
+

What Happens if Calibration Fails

+

There are subtle ways that encoder problems will impact your ODrive. +For example, ODrive may not complete the calibrate sequence when you go to:

+
<axis>.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE
+
+
+

Or, ODrive may complete the calibrate sequence after:

+
<axis>.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE
+
+
+

but then it fails after you go to:

+
<axis>.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
+
+
+

Or ODrive may just vibrate in an entertaining way. See this video.

+
+
+

Encoder Testing

+

There are things you can test to make sure your encoder is properly connected. +shadow_count tracks encoder motion, even before the encoder or motor are calibrated. +If your encoder is working, you should see this value change when you turn the motor. +Run the command:

+
<axis>.encoder.shadow_count
+
+
+

and look at your value. Then turn your motor by hand and see if that value changes. Also, notice that the command:

+
<axis>.encoder.config.cpr = 4000
+
+
+

must reflect the number of counts ODrive receives after one complete turn of the motor. +So use shadow_count to test if that is working properly.

+

You will probably never be able to properly debug if you have problems unless you use an oscilloscope. +If you have one, try the following: +Connect to the AB pins, see if you get square waves as you turn the motor. +Connect to the I pin, see if you get a pulse on a complete rotation. Sometimes this is hard to see.

+

If you are using SPI, use a logic analyzer and connect to the CLK, MISO, and CS pins. +Set a trigger for the CS pin and ensure that the encoder position is being sent and is increasing/decreasing as you spin the motor. +There is extremely cheap hardware that is supported by Sigrok for protocol analysis.

+
+
+

Encoder Noise

+

Noise is found in all circuits, life is just about figuring out if it is preventing your system from working. +Lots of users have no problems with noise interfering with their ODrive operation, others will tell you “I’ve been using the same encoder as you with no problems”. +Power to ‘em, that may be true, but it doesn’t mean it will work for you. +If you are concerned about noise, there are several possible sources:

+
    +
  • Importantly, encoder wires may be too close to motor wires, avoid overlap as much as possible

  • +
  • Long wires between encoder and ODrive

  • +
  • Use of ribbon cable

  • +
+

The following might mitigate noise problems. +Use shielded cable, or use twisted pairs, where one side of each twisted pair is tied to ground and the other side is tied to your signal. +If you are using SPI, use a 20-50 ohm resistor in series on CLK, which is more susceptible noise.

+

If you are using an encoder with an index signal, another problem that has been encountered is noise on the Z input of ODrive. +Symptoms for this problem include:

+
    +
  • difficulty with requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE, where your calibration sequence may not complete

  • +
  • strange behavior after performing <odrv>.save_configuration() and <odrv>.reboot()

  • +
  • when performing an index_search, the motor does not return to the same position each time.

  • +
+

One easy step that might fix the noise on the Z input is to solder a 22nF-47nF capacitor to the Z pin and the GND pin on the underside of the ODrive board.

+
+
+

Hall Feedback Pinout

+

If position accuracy is not a concern, you can use A/B/C hall effect encoders for position feedback.

+

To use this mode, configure the corresponding encoder mode: <encoder>.config.mode = ENCODER_MODE_HALL. +Configure the corresponding GPIOs as digital inputs:

+

For encoder 0:

+
<odrv>.config.gpio9_mode = GPIO_MODE_DIGITAL
+<odrv>.config.gpio10_mode = GPIO_MODE_DIGITAL
+<odrv>.config.gpio11_mode = GPIO_MODE_DIGITAL
+
+
+

For encoder 1:

+
<odrv>.config.gpio12_mode = GPIO_MODE_DIGITAL
+<odrv>.config.gpio13_mode = GPIO_MODE_DIGITAL
+<odrv>.config.gpio14_mode = GPIO_MODE_DIGITAL
+
+
+

In this mode, the pinout on the encoder port is as follows:

+ ++++ + + + + + + + + + + + + + + + + +

Label on ODrive

Hall feedback

A

Hall A

B

Hall B

Z

Hall C

+
+
+

SPI Encoders

+

Apart from (incremental) quadrature encoders, ODrive also supports absolute SPI encoders (since firmware v0.5). +These usually measure an absolute angle. +This means you don’t need to repeat the encoder calibration after every ODrive reboot. +Currently, the following modes are supported:

+
+
    +
  • CUI protocol: Compatible with the AMT23xx family (AMT232A, AMT232B, AMT233A, AMT233B).

  • +
  • AMS protocol: Compatible with AS5047P and AS5048A.

  • +
+
+

Some of these chips come with evaluation boards that can simplify mounting the chips to your motor. +For our purposes if you are using an evaluation board you should select the settings for 3.3v.

+
+

Note

+

The AMT23x family has a hardware bug that causes them to not properly tristate the MISO line. +To use them with ODrive, there are two workarounds. +One is to sequence power to the encoder a second or two after the ODrive recieves power. +This allows 1 encoder to be used without issue. +Another solution is to add a tristate buffer, such as the 74AHC1G125SE, on the MISO line between the ODrive and each AMT23x encoder. +Tie the enable pin on the buffer to the CS line for the respective encoder. +This allows for more than one AMT23x encoder, or one AMT23x and another SPI encoder, to be used at the same time.

+
+
    +
  1. Connect the encoder to the ODrive’s SPI interface:

    +
    +
      +
    • The encoder’s SCK, MISO (aka “DATA” on CUI encoders), MOSI (if present on the encoder), GND and 3.3V should connect to the ODrive pins with the same label. +If you want to save a wire with AMS encoders, you can also connect the encoder’s MOSI to the encoder’s VDD instead.

    • +
    • The encoder’s Chip Select (aka nCS/CSn) can be connected to any of the ODrive’s GPIOs (caution: GPIOs 1 and 2 are usually used by UART).

    • +
    +
    +
  2. +
+

If you are having calibration problems, make sure that your magnet is centered on the axis of rotation on the motor. +Some users report that this has a significant impact on calibration. +Also make sure that your magnet height is within range of the spec sheet.

+
    +
  1. In odrivetool, run:

    +
    +
    <axis>.encoder.config.abs_spi_cs_gpio_pin = 4  # or which ever GPIO pin you choose
    +<axis>.encoder.config.mode = ENCODER_MODE_SPI_ABS_CUI   # or ENCODER_MODE_SPI_ABS_AMS
    +<axis>.encoder.config.cpr = 2**14              # or 2**12 for AMT232A and AMT233A
    +<odrv>.save_configuration()
    +<odrv>.reboot()
    +
    +
    +
    +
  2. +
  3. Run the offset calibration and then save the calibration with <odrv>.save_configuration(). +The next time you reboot, the encoder should be immediately ready.

  4. +
+

Sometimes the encoder takes longer than the ODrive to start, in which case you need to clear the errors after every restart.

+

If you are having calibration problems - make sure your magnet is centered on the axis of rotation on the motor, some users report this has a significant impact on calibration. +Also make sure your magnet height is within range of the spec sheet.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/endstops.html b/docs/reStructuredText/_build/html/endstops.html new file mode 100644 index 00000000..10d97cdb --- /dev/null +++ b/docs/reStructuredText/_build/html/endstops.html @@ -0,0 +1,365 @@ + + + + + + Endstops and Homing — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Endstops and Homing

+ +

By default, the ODrive assumes that your motor encoder’s zero position is the same as your machine’s zero position, but in real life this is rarely the case. +In these systems it is useful to allow your motor to move until a physical or electronic device orders the system to stop. +That endstop can be used as a known reference point. Once the ODrive has hit that position it may then want to move to a final zero, or home, position. +The process of finding your machine’s zero position is known as homing.

+

ODrive supports the use of its GPIO pins to connect to phyiscal limit switches or other sensors that can serve as endstops. +Before you can home your machine, you must be able to adequately control your motor in AXIS_STATE_CLOSED_LOOP_CONTROL.

+
+

Endstop Configuration

+

Each axis supports two endstops: min_endstop and max_endstop. +For each endstop, the following properties are accessible through odrivetool:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Name

Type

Default

gpio_num

int

0

offset

float

0.0

debounce_ms

float

50.0

enabled

boolean

false

is_active_high

boolean

false

+
+

gpio_num

+

The GPIO pin number, according to the silkscreen labels on ODrive. Set with these commands:

+
<odrv>.<axis>.max_endstop.config.gpio_num = <1, 2, 3, 4, 5, 6, 7, 8>
+<odrv>.<axis>.min_endstop.config.gpio_num = <1, 2, 3, 4, 5, 6, 7, 8>
+
+
+
+
+

enabled

+

Enables/disables detection of the endstop. If disabled, homing and e-stop cannot take place. Set with:

+
<odrv>.<axis>.max_endstop.config.enabled = <True, False>
+<odrv>.<axis>.min_endstop.config.enabled = <True, False>
+
+
+
+
+

offset

+

This is the position of the endstops on the relevant axis, in turns. +For example, if you want a position command of 0 to represent a position 3 turns away from the endstop, the offset would be -3.0 (because the endstop is located at axis position -3.0).

+
<odrv>.<axis>.min_endstop.config.offset = <int>
+
+
+

This setting is only used for homing. Only the offset of the min_endstop is used.

+
+
+

debounce_ms

+

The debouncing time for this endstop. Most switches exhibit some sort of bounce, and this setting will help prevent the switch from triggering repeatedly. +It works for both HIGH and LOW transitions, regardless of the setting of is_active_high. +Debouncing is a good practice for digital inputs, read up on it here. debounce_ms has units of miliseconds.

+
<odrv>.<axis>.max_endstop.config.debounce_ms = <Float>
+<odrv>.<axis>.min_endstop.config.debounce_ms = <Float>
+
+
+
+
+

is_active_high

+

This is how you configure the endstop to be either “NPN” or “PNP”. +An “NPN” configuration would be is_active_high = False whereas a PNP configuration is is_active_high = True. +Refer to the following table for more information:

+

Typically configuration 1 or 3 is preferred when using mechanical switches as the most common failure mode leaves the switch open.

+
+
+

GPIO Configuration

+

The GPIOs that are used for the endstops need to be configured according to the diagram below.

+

Assuming your endstop is connected to GPIO X:

+
    +
  • Configuration 1, 2: <odrv>.config.gpioX_mode = GPIO_MODE_DIGITAL_PULL_DOWN

  • +
  • Configuration 3, 4: <odrv>.config.gpioX_mode = GPIO_MODE_DIGITAL_PULL_DOWN

  • +
+
+Endstop configuration +
+
+
+

Example

+

If we want to configure a 3D printer-style (configuration 4) minimum endstop for homing on GPIO 5 and we want our motor to move away from the endstop about a quarter turn with a 8192 cpr encoder, we would set:

+
<odrv>.config.gpio5_mode = GPIO_MODE_DIGITAL
+<odrv>.<axis>.min_endstop.config.gpio_num = 5
+<odrv>.<axis>.min_endstop.config.is_active_high = False
+<odrv>.<axis>.min_endstop.config.offset = -1.0*(8912/4)
+<odrv>.<axis>.min_endstop.config.enabled = True
+<odrv>.config.gpio5_mode = GPIO_MODE_DIGITAL_PULL_UP
+
+
+
+
+

Testing The Endstops

+

Once the endstops are configured you can test your endstops for correct functionality. +Try activating your endstops and check the states of these variables through odrivetool:

+
<odrv>.<axis>.max_endstop.endstop_state
+<odrv>.<axis>.min_endstop.endstop_state
+
+
+

A state of True means the switch is pressed. A state of False means the switch is NOT pressed. +As simple as that. Give it a try. Click your switches, or put a magnet on your hall switch and see if the states change.

+

After testing, don’t forget to save and reboot:

+
<odrv>.save_configuration()
+<odrv>.reboot()
+
+
+
+
+
+

Homing

+

There is one additional configuration parameter in controller.config specifically for the homing process:

+ +++++ + + + + + + + + + + + + +

Name

Type

Default

homing_speed

float

0.25f

+

homing_speed is the axis travel speed during homing, in [turns/second]. +If you are using SPI based encoders and the axis is homing in the wrong direction, you can enter a negative value for the homing speed and a negative value for the minimum endstop offset.

+

Set the homing speed to 0.25 turns / sec:

+
odrv0.axis0.controller.config.homing_speed = 0.25
+
+
+
+

Performing the Homing Sequence

+

Homing is possible once the ODrive has closed-loop control over the axis. +To trigger homing, we must enter:code:AXIS_STATE_HOMING. This starts the homing sequence, which works as follows:

+
    +
  1. The axis switches to INPUT_MODE_VEL_RAMP

  2. +
  3. The axis ramps up to homing_speed in the direction of min_endstop

  4. +
  5. The axis presses the min_endstop

  6. +
  7. The axis switches to INPUT_MODE_TRAP_TRAJ

  8. +
  9. The axis moves to the home position in a controlled manner

  10. +
+

It requires quite a few settings in addition to the endstop settings:

+
<odrv>.<axis>.controller.config.vel_ramp_rate
+<odrv>.<axis>.trap_traj.config.vel_limit
+<odrv>.<axis>.trap_traj.config.accel_limit
+<odrv>.<axis>.trap_traj.config.decel_limit
+
+
+

We realize this is a little excessive and we will work towards minimizing the setup, but this works well for smooth and reliable behaviour for now.

+
+
+

Homing at Startup

+

It is possible to configure the odrive to enter homing immediately after startup. +To enable homing at startup, the following must be configured:

+
<odrv>.<axis>.config.startup_homing = True
+
+
+
+
+
+

Additional Endstop Devices

+

In addition to phyiscal switches there are other options for wiring up your endstops - you will have to work out the details of connecting your device but here are some suggested approaches:

+
+endstop figure +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/fibre_types/com_odriverobotics_ODrive.html b/docs/reStructuredText/_build/html/fibre_types/com_odriverobotics_ODrive.html new file mode 100644 index 00000000..0b87f957 --- /dev/null +++ b/docs/reStructuredText/_build/html/fibre_types/com_odriverobotics_ODrive.html @@ -0,0 +1,3204 @@ + + + + + + ODrive Reference — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

ODrive Reference

+
+
+class ODrive
+
+
+test_function(delta)int
+
+
Parameters
+

delta (int) –

+
+
+
+ +
+
+get_adc_voltage(gpio)float
+

Reads the ADC voltage of the specified GPIO. The GPIO should be in GPIO_MODE_ANALOG_IN.

+
+
Parameters
+

gpio (int) –

+
+
+
+ +
+
+save_configuration()bool
+

Saves the current configuration to non-volatile memory and reboots the board.

+
+ +
+
+erase_configuration()
+
+ +
+
+reboot()
+
+ +
+
+enter_dfu_mode()
+
+ +
+
+get_interrupt_status(irqn)int
+

Returns information about the specified interrupt number.

+
+
Parameters
+

irqn (int) – -12…-1: processor interrupts, 0…239: NVIC interrupts

+
+
+
+ +
+
+get_dma_status(stream_num)int
+

Returns information about the specified DMA stream.

+
+
Parameters
+

stream_num (int) – 0…7: DMA1 streams, 8…15: DMA2 streams

+
+
+
+ +
+
+get_gpio_states()int
+

Returns the logic states of all GPIOs. Bit i represents the state of GPIOi.

+
+ +
+
+get_drv_fault()int
+
+ +
+
+clear_errors()
+

Clear all the errors of this device including all contained submodules.

+
+ +
+
+error: Property[ODrive.Error]
+
+ +
+
+vbus_voltage: Float32Property
+

Voltage on the DC bus as measured by the ODrive.

+
+ +
+
+ibus: Float32Property
+

Current on the DC bus as calculated by the ODrive.

+

A positive value means that the ODrive is consuming power from the power supply, +a negative value means that the ODrive is sourcing power to the power supply. + +This value is equal to the sum of the motor currents and the brake resistor currents. +The motor currents are measured, the brake resistor current is calculated based on +config.brake_resistance.

+
+ +
+
+ibus_report_filter_k: Float32Property
+

Filter gain for the reported ibus. Set to a value below 1.0 to get a smoother +line when plotting ibus. Set to 1.0 to disable. This filter is only applied to +the reported value and not for internal calculations.

+
+ +
+
+serial_number: Uint64Property
+
+ +
+
+hw_version_major: Uint8Property
+
+ +
+
+hw_version_minor: Uint8Property
+
+ +
+
+hw_version_variant: Uint8Property
+
+ +
+
+fw_version_major: Uint8Property
+
+ +
+
+fw_version_minor: Uint8Property
+
+ +
+
+fw_version_revision: Uint8Property
+
+ +
+
+fw_version_unreleased: Uint8Property
+

0 for official releases, 1 otherwise

+
+ +
+
+brake_resistor_armed: BoolProperty
+
+ +
+
+brake_resistor_saturated: BoolProperty
+
+ +
+
+brake_resistor_current: Float32Property
+

Commanded brake resistor current

+
+ +
+
+n_evt_sampling: Uint32Property
+

Number of input sampling events since startup (modulo 2^32)

+
+ +
+
+n_evt_control_loop: Uint32Property
+

Number of control loop iterations since startup (modulo 2^32)

+
+ +
+
+task_timers_armed: BoolProperty
+

Set by a profiling application to trigger sampling of a single +control iteration. Cleared by the device as soon as the sampling +is complete.

+
+ +
+
+task_times: ODrive.TaskTimes
+
+ +
+
+system_stats: ODrive.SystemStats
+
+ +
+
+user_config_loaded: Uint32Property
+
+ +
+
+misconfigured: BoolProperty
+

If this property is true, something is bad in the configuration. The +ODrive can still be used in this state but the user should investigate +which setting is problematic. This variable does not cover all +misconfigurations. + +Possible causes: + - A GPIO was set to a mode that it doesn’t support + - A GPIO was set to a mode for which the corresponding feature was + not enabled. Example: GPIO_MODE_UART_A was used without enabling + config.enable_uart_a. + - A feature was enabled which is not supported on this hardware. + Example: config.enable_uart_c set to true on ODrive v3.x. + - A GPIO was used as an interrupt input for two internal components + or two GPIOs that are mutually exclusive in their interrupt + capability were both used as interrupt input. + Example: Axis:config.step_gpio_pin of both axes were set to the same GPIO.

+
+ +
+
+oscilloscope: ODrive.Oscilloscope
+
+ +
+
+can: ODrive.Can
+
+ +
+
+test_property: Uint32Property
+
+ +
+
+otp_valid: BoolProperty
+
+ +
+ +
+
+class ODrive.Error
+
+
+CONTROL_ITERATION_MISSED = 1 (0x1)
+
+ +
+
+DC_BUS_UNDER_VOLTAGE = 2 (0x2)
+
+ +
+
+DC_BUS_OVER_VOLTAGE = 4 (0x4)
+
+ +
+
+DC_BUS_OVER_REGEN_CURRENT = 8 (0x8)
+
+ +
+
+DC_BUS_OVER_CURRENT = 16 (0x10)
+
+ +
+
+BRAKE_DEADTIME_VIOLATION = 32 (0x20)
+
+ +
+
+BRAKE_DUTY_CYCLE_NAN = 64 (0x40)
+
+ +
+
+INVALID_BRAKE_RESISTANCE = 128 (0x80)
+
+ +
+ +
+
+class ODrive.TaskTimes
+
+
+sampling: ODrive.TaskTimer
+
+ +
+
+control_loop_misc: ODrive.TaskTimer
+
+ +
+
+control_loop_checks: ODrive.TaskTimer
+
+ +
+
+dc_calib_wait: ODrive.TaskTimer
+
+ +
+ +
+
+class ODrive.SystemStats
+
+
+uptime: Uint32Property
+
+ +
+
+min_heap_space: Uint32Property
+
+ +
+
+max_stack_usage_axis: Uint32Property
+
+ +
+
+max_stack_usage_usb: Uint32Property
+
+ +
+
+max_stack_usage_uart: Uint32Property
+
+ +
+
+max_stack_usage_can: Uint32Property
+
+ +
+
+max_stack_usage_startup: Uint32Property
+
+ +
+
+max_stack_usage_analog: Uint32Property
+
+ +
+
+stack_size_axis: Uint32Property
+
+ +
+
+stack_size_usb: Uint32Property
+
+ +
+
+stack_size_uart: Uint32Property
+
+ +
+
+stack_size_startup: Uint32Property
+
+ +
+
+stack_size_can: Uint32Property
+
+ +
+
+stack_size_analog: Uint32Property
+
+ +
+
+prio_axis: Int32Property
+
+ +
+
+prio_usb: Int32Property
+
+ +
+
+prio_uart: Int32Property
+
+ +
+
+prio_startup: Int32Property
+
+ +
+
+prio_can: Int32Property
+
+ +
+
+prio_analog: Int32Property
+
+ +
+
+usb: ODrive.SystemStats.Usb
+
+ +
+
+i2c: ODrive.SystemStats.I2C
+
+ +
+ +
+
+class ODrive.Config
+
+
+enable_uart_a: BoolProperty
+

Enables/disables UART_A.

+

You also need to set the corresponding GPIOs to GPIO_MODE_UART_A. +Refer to [interfaces](interfaces.md) to see which pins support UART_A. +Changing this requires a reboot.

+
+ +
+
+enable_uart_b: BoolProperty
+

Enables/disables UART_B.

+

You also need to set the corresponding GPIOs to GPIO_MODE_UART_B. +Refer to [interfaces](interfaces.md) to see which pins support UART_B. +Changing this requires a reboot.

+
+ +
+
+enable_uart_c: BoolProperty
+

Not supported on ODrive v3.x.

+
+ +
+
+uart_a_baudrate: Uint32Property
+

Defines the baudrate used on the UART interface.

+

Some baudrates will have a small timing error due to hardware limitations. + +Here’s an (incomplete) list of baudrates for ODrive v3.x: + + Configured | Actual | Error [%] + ————-|---------------|———– + 1.2 KBps | 1.2 KBps | 0 + 2.4 KBps | 2.4 KBps | 0 + 9.6 KBps | 9.6 KBps | 0 + 19.2 KBps | 19.195 KBps | 0.02 + 38.4 KBps | 38.391 KBps | 0.02 + 57.6 KBps | 57.613 KBps | 0.02 + 115.2 KBps | 115.068 KBps | 0.11 + 230.4 KBps | 230.769 KBps | 0.16 + 460.8 KBps | 461.538 KBps | 0.16 + 921.6 KBps | 913.043 KBps | 0.93 + 1.792 MBps | 1.826 MBps | 1.9 + 1.8432 MBps | 1.826 MBps | 0.93 + +For more information refer to Section 30.3.4 and Table 142 (the column with f_PCLK = 42 MHz) in the +[STM datasheet](https://www.st.com/content/ccc/resource/technical/document/reference_manual/3d/6d/5a/66/b4/99/40/d4/DM00031020.pdf/files/DM00031020.pdf/jcr:content/translations/en.DM00031020.pdf).

+
+ +
+
+uart_b_baudrate: Uint32Property
+

Defines the baudrate used on the UART interface.

+

See uart_a_baudrate for details.

+
+ +
+
+uart_c_baudrate: Uint32Property
+

Not supported on ODrive v3.x.

+
+ +
+
+enable_can_a: BoolProperty
+

Enables CAN. Changing this setting requires a reboot.

+
+ +
+
+enable_i2c_a: BoolProperty
+

Enables I2C. The I2C pins on ODrive v3.x are in conflict with CAN. +This setting has no effect if enable_can_a is also true. +This setting has no effect on ODrive v3.2 or earlier. +Changing this setting requires a reboot.

+
+ +
+
+usb_cdc_protocol: Property[ODrive.StreamProtocolType]
+

The protocol that’s being run on the device’s virtual COM port on +USB. +Note that the ODrive has two independent interfaces on USB: One +is the virtual COM port (affected by this option) and the other +one is a vendor specific interface which always runs Fibre. +So changing this option does not affect the working of odrivetool.

+
+ +
+
+uart0_protocol: Property[ODrive.StreamProtocolType]
+
+ +
+
+uart1_protocol: Property[ODrive.StreamProtocolType]
+
+ +
+
+uart2_protocol: Property[ODrive.StreamProtocolType]
+
+ +
+
+max_regen_current: Float32Property
+
+ +
+
+brake_resistance: Float32Property
+

Value of the brake resistor connected to the ODrive.

+

If you set this to a lower value than the true brake resistance +then the ODrive will not meed the max_regen_current constraint +during braking, that is it will sink more than max_regen_current +into the power supply. Some power supplies don’t like this. + +If you set this to a higher value than the true brake resistance +then the ODrive will unnecessarily burn more power than required +during braking.

+
+ +
+
+enable_brake_resistor: BoolProperty
+

Enable/disable the use of a brake resistor.

+

Setting this to False even though a brake resistor is connected is +harmless. Setting this to True even though no brake resistor is +connected can break the power supply. +Changes to this value require a reboot to take effect.

+
+ +
+
+dc_bus_undervoltage_trip_level: Float32Property
+

Minimum voltage below which the motor stops operating.

+
+ +
+
+dc_bus_overvoltage_trip_level: Float32Property
+

Maximum voltage above which the motor stops operating.

+

This protects against cases in which the power supply fails to dissipate +the brake power if the brake resistor is disabled. +The default is 26V for the 24V board version and 52V for the 48V board version.

+
+ +
+
+enable_dc_bus_overvoltage_ramp: BoolProperty
+

Enables the DC bus overvoltage ramp feature.

+

If enabled, if the measured DC voltage exceeds dc_bus_overvoltage_ramp_start, +the ODrive will sink more power than usual into the the brake resistor +in an attempt to bring the voltage down again. + +The brake duty cycle is increased by the following amount: + + * ODrive:vbus_voltage == dc_bus_overvoltage_ramp_start => brake_duty_cycle += 0% + * ODrive:vbus_voltage == dc_bus_overvoltage_ramp_end => brake_duty_cycle += 100% + +Remarks: + - This feature is active even when all motors are disarmed. + - This feature is disabled if brake_resistance is non-positive.

+
+ +
+
+dc_bus_overvoltage_ramp_start: Float32Property
+

See enable_dc_bus_overvoltage_ramp.

+

Do not set this lower than your usual ODrive:vbus_voltage, unless you like fried brake resistors.

+
+ +
+
+dc_bus_overvoltage_ramp_end: Float32Property
+

See enable_dc_bus_overvoltage_ramp.

+

Must be larger than dc_bus_overvoltage_ramp_start, otherwise the ramp feature is disabled.

+
+ +
+
+dc_max_positive_current: Float32Property
+

Max current the power supply can source.

+
+ +
+
+dc_max_negative_current: Float32Property
+

Max current the power supply can sink.

+

You most likely want a non-positive value here. Set to -INFINITY to disable.

+
+ +
+
+error_gpio_pin: Uint32Property
+
+ +
+
+gpio3_analog_mapping: ODrive.Endpoint
+

Make sure the corresponding GPIO is in GPIO_MODE_ANALOG_IN.

+
+ +
+
+gpio4_analog_mapping: ODrive.Endpoint
+

Make sure the corresponding GPIO is in GPIO_MODE_ANALOG_IN.

+
+ +
+ +
+
+class ODrive.Can
+
+
+error: Property[ODrive.Can.Error]
+
+ +
+
+config: ODrive.Can.Config
+
+ +
+ +
+
+class ODrive.Endpoint
+
+
+endpoint: EndpointRefProperty
+
+ +
+
+min: Float32Property
+
+ +
+
+max: Float32Property
+
+ +
+ +
+
+class ODrive.Axis
+
+
+watchdog_feed()
+

Feed the watchdog to prevent watchdog timeouts.

+
+ +
+
+error: Property[ODrive.Axis.Error]
+
+ +
+
+step_dir_active: BoolProperty
+
+ +
+
+last_drv_fault: Uint32Property
+
+ +
+
+steps: Int64Property
+
+ +
+
+current_state: Property[ODrive.Axis.AxisState]
+
+ +
+
+requested_state: Property[ODrive.Axis.AxisState]
+
+ +
+
+is_homed: BoolProperty
+
+ +
+
+config: ODrive.Axis.Config
+
+ +
+
+motor: ODrive.Motor
+
+ +
+
+controller: ODrive.Controller
+
+ +
+
+encoder: ODrive.Encoder
+
+ +
+
+acim_estimator: ODrive.AcimEstimator
+
+ +
+
+sensorless_estimator: ODrive.SensorlessEstimator
+
+ +
+
+trap_traj: ODrive.TrapezoidalTrajectory
+
+ +
+
+min_endstop: ODrive.Endstop
+
+ +
+
+max_endstop: ODrive.Endstop
+
+ +
+
+mechanical_brake: ODrive.MechanicalBrake
+
+ +
+
+task_times: ODrive.Axis.TaskTimes
+
+ +
+ +
+
+class ODrive.ThermistorCurrentLimiter
+
+ +
+
+class ODrive.OnboardThermistorCurrentLimiter
+
+
+temperature: Float32Property
+
+ +
+
+config: ODrive.OnboardThermistorCurrentLimiter.Config
+
+ +
+ +
+
+class ODrive.OffboardThermistorCurrentLimiter
+
+
+temperature: Float32Property
+
+ +
+
+config: ODrive.OffboardThermistorCurrentLimiter.Config
+
+ +
+ +
+
+class ODrive.Motor
+
+
+last_error_time: Float32Property
+
+ +
+
+error: Property[ODrive.Motor.Error]
+
+ +
+
+is_armed: BoolProperty
+
+ +
+
+is_calibrated: BoolProperty
+
+ +
+
+current_meas_phA: Float32Property
+
+ +
+
+current_meas_phB: Float32Property
+
+ +
+
+current_meas_phC: Float32Property
+
+ +
+
+DC_calib_phA: Float32Property
+
+ +
+
+DC_calib_phB: Float32Property
+
+ +
+
+DC_calib_phC: Float32Property
+
+ +
+
+I_bus: Float32Property
+
+ +
+
+phase_current_rev_gain: Float32Property
+
+ +
+
+effective_current_lim: Float32Property
+
+ +
+
+max_allowed_current: Float32Property
+

Indicates the maximum current that can be measured by the current +sensors in the current hardware configuration. This value depends on +config.requested_current_range.

+
+ +
+
+max_dc_calib: Float32Property
+
+ +
+
+fet_thermistor: ODrive.OnboardThermistorCurrentLimiter
+
+ +
+
+motor_thermistor: ODrive.OffboardThermistorCurrentLimiter
+
+ +
+
+current_control: ODrive.Motor.CurrentControl
+
+ +
+
+n_evt_current_measurement: Uint32Property
+

Number of current measurement events since startup (modulo 2^32)

+
+ +
+
+n_evt_pwm_update: Uint32Property
+

Number of PWM update events since startup (modulo 2^32)

+
+ +
+
+config: ODrive.Motor.Config
+
+ +
+ +
+
+class ODrive.Oscilloscope
+
+
+get_val(index)float
+
+
Parameters
+

index (int) –

+
+
+
+ +
+
+size: Uint32Property
+
+ +
+ +
+
+class ODrive.AcimEstimator
+
+
+rotor_flux: Float32Property
+

estimated magnitude of the rotor flux

+
+ +
+
+slip_vel: Float32Property
+

estimated slip between physical and electrical angular velocity}

+
+ +
+
+phase_offset: Float32Property
+

estimate offset between physical and electrical angular position}

+
+ +
+
+stator_phase_vel: Float32Property
+

calculated setpoint for the electrical velocity}

+
+ +
+
+stator_phase: Float32Property
+

calculated setpoint for the electrical phase}

+
+ +
+
+config: ODrive.AcimEstimator.Config
+
+ +
+ +
+
+class ODrive.Controller
+
+
+move_incremental(displacement, from_input_pos)
+

Moves the axes’ goal point by a specified increment.

+
+
Parameters
+
    +
  • displacement (float) – The desired position change.

  • +
  • from_input_pos (bool) – If true, the increment is applied relative to input_pos. If false, the increment is applied relative to pos_setpoint, which usually corresponds roughly to the current position of the axis.

  • +
+
+
+
+ +
+
+start_anticogging_calibration()
+
+ +
+
+error: Property[ODrive.Controller.Error]
+
+ +
+
+last_error_time: Float32Property
+
+ +
+
+input_pos: Float32Property
+
+ +
+
+input_vel: Float32Property
+
+ +
+
+input_torque: Float32Property
+
+ +
+
+pos_setpoint: Float32Property
+
+ +
+
+vel_setpoint: Float32Property
+
+ +
+
+torque_setpoint: Float32Property
+
+ +
+
+trajectory_done: BoolProperty
+
+ +
+
+vel_integrator_torque: Float32Property
+
+ +
+
+anticogging_valid: BoolProperty
+
+ +
+
+autotuning_phase: Float32Property
+
+ +
+
+config: ODrive.Controller.Config
+
+ +
+
+autotuning: ODrive.Controller.Autotuning
+

Automatically generate sine waves for frequency-domain response tuning

+
+ +
+
+mechanical_power: Float32Property
+

Mechanical power estimate. Torque * velocity

+
+ +
+
+electrical_power: Float32Property
+

Electrical power estimate. Vdq dot Idq

+
+ +
+ +
+
+class ODrive.Encoder
+
+
+set_linear_count(count)
+
+
Parameters
+

count (int) –

+
+
+
+ +
+
+error: Property[ODrive.Encoder.Error]
+
+ +
+
+is_ready: BoolProperty
+
+ +
+
+index_found: BoolProperty
+
+ +
+
+shadow_count: Int32Property
+

Raw linear count from the encoder.

+
+ +
+
+count_in_cpr: Int32Property
+

Raw circular count from the encoder on [0, cpr)

+
+ +
+
+interpolation: Float32Property
+
+ +
+
+phase: Float32Property
+
+ +
+
+pos_estimate: Float32Property
+

Linear position estimate of the encoder, in turns. Also known as “multi-turn” position.

+
+ +
+
+pos_estimate_counts: Float32Property
+

Linear position estimate of the encoder, in counts. Equal to pos_estimate * config.cpr

+
+ +
+
+pos_circular: Float32Property
+

Circular position estimate of the encoder, as a decimal from [0, 1). Also known as “single-turn” position.

+
+ +
+
+pos_cpr_counts: Float32Property
+

Circular position estimate of the encoder, on the space [0, cpr).

+
+ +
+
+delta_pos_cpr_counts: Float32Property
+

Circular position delta of the encoder in the most recent loop. Primarily for debug purposes, it indicates much the encoder changed since the last time it was checked.

+
+ +
+
+hall_state: Uint8Property
+
+ +
+
+vel_estimate: Float32Property
+

Estimate of the linear velocity of an axis in turns/s

+
+ +
+
+vel_estimate_counts: Float32Property
+

Estimate of the linear velocity of an axis, in counts/s.

+
+ +
+
+calib_scan_response: Float32Property
+
+ +
+
+pos_abs: Int32Property
+

The last (valid) position from an absolute encoder, if used.

+
+ +
+
+spi_error_rate: Float32Property
+
+ +
+
+config: ODrive.Encoder.Config
+
+ +
+ +
+
+class ODrive.SensorlessEstimator
+
+
+error: Property[ODrive.SensorlessEstimator.Error]
+
+ +
+
+phase: Float32Property
+
+ +
+
+pll_pos: Float32Property
+
+ +
+
+phase_vel: Float32Property
+
+ +
+
+vel_estimate: Float32Property
+
+ +
+
+config: ODrive.SensorlessEstimator.Config
+
+ +
+ +
+
+class ODrive.TrapezoidalTrajectory
+
+
+config: ODrive.TrapezoidalTrajectory.Config
+
+ +
+ +
+
+class ODrive.Endstop
+
+
+endstop_state: BoolProperty
+
+ +
+
+config: ODrive.Endstop.Config
+
+ +
+ +
+
+class ODrive.MechanicalBrake
+
+
+engage()
+

This function engages the mechanical brake if one is present and enabled.

+
+ +
+
+release()
+

This function releases the mecahncal brake if one is present and enabled.

+
+ +
+
+config: ODrive.MechanicalBrake.Config
+
+ +
+ +
+
+class ODrive.TaskTimer
+
+
+start_time: Uint32Property
+
+ +
+
+end_time: Uint32Property
+
+ +
+
+length: Uint32Property
+
+ +
+
+max_length: Uint32Property
+
+ +
+ +
+
+class ODrive.GpioMode
+
+
+DIGITAL = 0 (0x0)
+
+ +
+
+DIGITAL_PULL_UP = 1 (0x1)
+
+ +
+
+DIGITAL_PULL_DOWN = 2 (0x2)
+
+ +
+
+ANALOG_IN = 3 (0x3)
+
+ +
+
+UART_A = 4 (0x4)
+
+ +
+
+UART_B = 5 (0x5)
+
+ +
+
+UART_C = 6 (0x6)
+
+ +
+
+CAN_A = 7 (0x7)
+
+ +
+
+I2C_A = 8 (0x8)
+
+ +
+
+SPI_A = 9 (0x9)
+
+ +
+
+PWM = 10 (0xA)
+
+ +
+
+ENC0 = 11 (0xB)
+
+ +
+
+ENC1 = 12 (0xC)
+
+ +
+
+ENC2 = 13 (0xD)
+
+ +
+
+MECH_BRAKE = 14 (0xE)
+
+ +
+
+STATUS = 15 (0xF)
+
+ +
+ +
+
+class ODrive.StreamProtocolType
+
+
+Fibre = 0 (0x0)
+
+ +
+
+Ascii = 1 (0x1)
+
+ +
+
+Stdout = 2 (0x2)
+
+ +
+
+AsciiAndStdout = 3 (0x3)
+
+ +
+ +
+
+class ODrive.SystemStats.Usb
+
+
+rx_cnt: Uint32Property
+
+ +
+
+tx_cnt: Uint32Property
+
+ +
+
+tx_overrun_cnt: Uint32Property
+
+ +
+ +
+
+class ODrive.SystemStats.I2C
+
+
+addr: Uint8Property
+
+ +
+
+addr_match_cnt: Uint32Property
+
+ +
+
+rx_cnt: Uint32Property
+
+ +
+
+error_cnt: Uint32Property
+
+ +
+ +
+
+class ODrive.Can.Error
+
+
+DUPLICATE_CAN_IDS = 1 (0x1)
+
+ +
+ +
+
+class ODrive.Can.Config
+
+
+baud_rate: Uint32Property
+
+ +
+
+protocol: Property[ODrive.Can.Protocol]
+
+ +
+ +
+
+class ODrive.Can.Protocol
+
+
+SIMPLE = 1 (0x1)
+
+ +
+ +
+
+class ODrive.Axis.Error
+
+
+INVALID_STATE = 1 (0x1)
+
+ +
+
+WATCHDOG_TIMER_EXPIRED = 2048 (0x800)
+
+ +
+
+MIN_ENDSTOP_PRESSED = 4096 (0x1000)
+
+ +
+
+MAX_ENDSTOP_PRESSED = 8192 (0x2000)
+
+ +
+
+ESTOP_REQUESTED = 16384 (0x4000)
+
+ +
+
+HOMING_WITHOUT_ENDSTOP = 131072 (0x20000)
+
+ +
+
+OVER_TEMP = 262144 (0x40000)
+
+ +
+
+UNKNOWN_POSITION = 524288 (0x80000)
+
+ +
+ +
+
+class ODrive.Axis.Config
+
+
+startup_motor_calibration: BoolProperty
+

run motor calibration at startup, skip otherwise

+
+ +
+ +

run encoder index search after startup, skip otherwise this only has an effect if encoder.config.use_index is also true

+
+ +
+
+startup_encoder_offset_calibration: BoolProperty
+

run encoder offset calibration after startup, skip otherwise

+
+ +
+
+startup_closed_loop_control: BoolProperty
+

enable closed loop control after calibration/startup

+
+ +
+
+startup_homing: BoolProperty
+

enable homing after calibration/startup

+
+ +
+
+enable_step_dir: BoolProperty
+

Enable step/dir input after calibration. Make sure to set the corresponding GPIO’s mode to GPIO_MODE_DIGITAL.

+
+ +
+
+step_dir_always_on: BoolProperty
+

Keep step/dir enabled while the motor is disabled. This is ignored if enable_step_dir is false. This setting only takes effect on a state transition into idle or out of closed loop control.

+
+ +
+
+enable_sensorless_mode: BoolProperty
+
+ +
+
+watchdog_timeout: Float32Property
+
+ +
+
+enable_watchdog: BoolProperty
+
+ +
+
+step_gpio_pin: Uint16Property
+
+ +
+
+dir_gpio_pin: Uint16Property
+
+ +
+
+calibration_lockin: ODrive.Axis.Config.CalibrationLockin
+
+ +
+
+sensorless_ramp: ODrive.Axis.LockinConfig
+
+ +
+
+general_lockin: ODrive.Axis.LockinConfig
+
+ +
+
+can: ODrive.Axis.CanConfig
+
+ +
+ +
+
+class ODrive.Axis.TaskTimes
+
+
+thermistor_update: ODrive.TaskTimer
+
+ +
+
+encoder_update: ODrive.TaskTimer
+
+ +
+
+sensorless_estimator_update: ODrive.TaskTimer
+
+ +
+
+endstop_update: ODrive.TaskTimer
+
+ +
+
+can_heartbeat: ODrive.TaskTimer
+
+ +
+
+controller_update: ODrive.TaskTimer
+
+ +
+
+open_loop_controller_update: ODrive.TaskTimer
+
+ +
+
+acim_estimator_update: ODrive.TaskTimer
+
+ +
+
+motor_update: ODrive.TaskTimer
+
+ +
+
+current_controller_update: ODrive.TaskTimer
+
+ +
+
+dc_calib: ODrive.TaskTimer
+
+ +
+
+current_sense: ODrive.TaskTimer
+
+ +
+
+pwm_update: ODrive.TaskTimer
+
+ +
+ +
+
+class ODrive.Axis.LockinConfig
+
+
+current: Float32Property
+
+ +
+
+ramp_time: Float32Property
+
+ +
+
+ramp_distance: Float32Property
+
+ +
+
+accel: Float32Property
+
+ +
+
+vel: Float32Property
+
+ +
+
+finish_distance: Float32Property
+
+ +
+
+finish_on_vel: BoolProperty
+
+ +
+
+finish_on_distance: BoolProperty
+
+ +
+
+finish_on_enc_idx: BoolProperty
+
+ +
+ +
+
+class ODrive.Axis.CanConfig
+
+
+node_id: Uint32Property
+
+ +
+
+is_extended: BoolProperty
+
+ +
+
+heartbeat_rate_ms: Uint32Property
+
+ +
+
+encoder_rate_ms: Uint32Property
+
+ +
+ +
+
+class ODrive.Axis.AxisState
+
+
+UNDEFINED = 0 (0x0)
+
+ +
+
+IDLE = 1 (0x1)
+
+ +
+
+STARTUP_SEQUENCE = 2 (0x2)
+
+ +
+
+FULL_CALIBRATION_SEQUENCE = 3 (0x3)
+
+ +
+
+MOTOR_CALIBRATION = 4 (0x4)
+
+ +
+ +
+ +
+
+ENCODER_OFFSET_CALIBRATION = 6 (0x6)
+
+ +
+
+CLOSED_LOOP_CONTROL = 7 (0x7)
+
+ +
+
+LOCKIN_SPIN = 8 (0x8)
+
+ +
+
+ENCODER_DIR_FIND = 9 (0x9)
+
+ +
+
+HOMING = 10 (0xA)
+
+ +
+
+ENCODER_HALL_POLARITY_CALIBRATION = 11 (0xB)
+
+ +
+
+ENCODER_HALL_PHASE_CALIBRATION = 12 (0xC)
+
+ +
+ +
+
+class ODrive.Axis.Config.CalibrationLockin
+
+
+current: Float32Property
+
+ +
+
+ramp_time: Float32Property
+
+ +
+
+ramp_distance: Float32Property
+
+ +
+
+accel: Float32Property
+
+ +
+
+vel: Float32Property
+
+ +
+ +
+
+class ODrive.OnboardThermistorCurrentLimiter.Config
+
+
+temp_limit_lower: Float32Property
+

The lower limit when the controller starts limiting current.

+
+ +
+
+temp_limit_upper: Float32Property
+

The upper limit when current limit reaches 0 Amps and an over temperature error is triggered.

+
+ +
+
+enabled: BoolProperty
+

Whether this thermistor is enabled.

+
+ +
+ +
+
+class ODrive.OffboardThermistorCurrentLimiter.Config
+
+
+gpio_pin: Uint16Property
+
+ +
+
+poly_coefficient_0: Float32Property
+
+ +
+
+poly_coefficient_1: Float32Property
+
+ +
+
+poly_coefficient_2: Float32Property
+
+ +
+
+poly_coefficient_3: Float32Property
+
+ +
+
+temp_limit_lower: Float32Property
+

The lower limit when the controller starts limiting current.

+
+ +
+
+temp_limit_upper: Float32Property
+

The upper limit when current limit reaches 0 Amps and an over temperature error is triggered.

+
+ +
+
+enabled: BoolProperty
+

Whether this thermistor is enabled.

+
+ +
+ +
+
+class ODrive.Motor.Error
+
+
+PHASE_RESISTANCE_OUT_OF_RANGE = 1 (0x1)
+
+ +
+
+PHASE_INDUCTANCE_OUT_OF_RANGE = 2 (0x2)
+
+ +
+
+DRV_FAULT = 8 (0x8)
+
+ +
+
+CONTROL_DEADLINE_MISSED = 16 (0x10)
+
+ +
+
+MODULATION_MAGNITUDE = 128 (0x80)
+
+ +
+
+CURRENT_SENSE_SATURATION = 1024 (0x400)
+
+ +
+
+CURRENT_LIMIT_VIOLATION = 4096 (0x1000)
+
+ +
+
+MODULATION_IS_NAN = 65536 (0x10000)
+
+ +
+
+MOTOR_THERMISTOR_OVER_TEMP = 131072 (0x20000)
+
+ +
+
+FET_THERMISTOR_OVER_TEMP = 262144 (0x40000)
+
+ +
+
+TIMER_UPDATE_MISSED = 524288 (0x80000)
+
+ +
+
+CURRENT_MEASUREMENT_UNAVAILABLE = 1048576 (0x100000)
+
+ +
+
+CONTROLLER_FAILED = 2097152 (0x200000)
+
+ +
+
+I_BUS_OUT_OF_RANGE = 4194304 (0x400000)
+
+ +
+
+BRAKE_RESISTOR_DISARMED = 8388608 (0x800000)
+
+ +
+
+SYSTEM_LEVEL = 16777216 (0x1000000)
+
+ +
+
+BAD_TIMING = 33554432 (0x2000000)
+
+ +
+
+UNKNOWN_PHASE_ESTIMATE = 67108864 (0x4000000)
+
+ +
+
+UNKNOWN_PHASE_VEL = 134217728 (0x8000000)
+
+ +
+
+UNKNOWN_TORQUE = 268435456 (0x10000000)
+
+ +
+
+UNKNOWN_CURRENT_COMMAND = 536870912 (0x20000000)
+
+ +
+
+UNKNOWN_CURRENT_MEASUREMENT = 1073741824 (0x40000000)
+
+ +
+
+UNKNOWN_VBUS_VOLTAGE = 2147483648 (0x80000000)
+
+ +
+
+UNKNOWN_VOLTAGE_COMMAND = 4294967296 (0x100000000)
+
+ +
+
+UNKNOWN_GAINS = 8589934592 (0x200000000)
+
+ +
+
+CONTROLLER_INITIALIZING = 17179869184 (0x400000000)
+
+ +
+
+UNBALANCED_PHASES = 34359738368 (0x800000000)
+
+ +
+ +
+
+class ODrive.Motor.CurrentControl
+
+
+p_gain: Float32Property
+
+ +
+
+i_gain: Float32Property
+
+ +
+
+I_measured_report_filter_k: Float32Property
+
+ +
+
+Id_setpoint: Float32Property
+
+ +
+
+Iq_setpoint: Float32Property
+
+ +
+
+Vd_setpoint: Float32Property
+
+ +
+
+Vq_setpoint: Float32Property
+
+ +
+
+phase: Float32Property
+
+ +
+
+phase_vel: Float32Property
+
+ +
+
+Ialpha_measured: Float32Property
+
+ +
+
+Ibeta_measured: Float32Property
+
+ +
+
+Id_measured: Float32Property
+
+ +
+
+Iq_measured: Float32Property
+
+ +
+
+power: Float32Property
+
+ +
+
+v_current_control_integral_d: Float32Property
+
+ +
+
+v_current_control_integral_q: Float32Property
+
+ +
+
+final_v_alpha: Float32Property
+
+ +
+
+final_v_beta: Float32Property
+
+ +
+ +
+
+class ODrive.Motor.Config
+
+
+pre_calibrated: BoolProperty
+
+ +
+
+pole_pairs: Int32Property
+
+ +
+
+calibration_current: Float32Property
+
+ +
+
+resistance_calib_max_voltage: Float32Property
+
+ +
+
+phase_inductance: Float32Property
+
+ +
+
+phase_resistance: Float32Property
+
+ +
+
+torque_constant: Float32Property
+
+ +
+
+motor_type: Property[ODrive.Motor.MotorType]
+
+ +
+
+current_lim: Float32Property
+
+ +
+
+current_lim_margin: Float32Property
+
+ +
+
+torque_lim: Float32Property
+
+ +
+
+inverter_temp_limit_lower: Float32Property
+
+ +
+
+inverter_temp_limit_upper: Float32Property
+
+ +
+
+requested_current_range: Float32Property
+
+ +
+
+current_control_bandwidth: Float32Property
+
+ +
+
+acim_gain_min_flux: Float32Property
+
+ +
+
+acim_autoflux_min_Id: Float32Property
+
+ +
+
+acim_autoflux_enable: BoolProperty
+
+ +
+
+acim_autoflux_attack_gain: Float32Property
+
+ +
+
+acim_autoflux_decay_gain: Float32Property
+
+ +
+
+R_wL_FF_enable: BoolProperty
+
+ +
+
+bEMF_FF_enable: BoolProperty
+
+ +
+
+I_bus_hard_min: Float32Property
+

If the controller fails to keep this motor’s DC current (ODrive.Motor:I_bus) +above this value the motor gets disarmed immediately. Most likely +you want a negative value here. Set to -inf to disable. Take noise +into account when chosing a value.

+
+ +
+
+I_bus_hard_max: Float32Property
+

If the controller fails to keep this motor’s DC current (ODrive.Motor:I_bus) +below this value the motor gets disarmed immediately. Usually this +is set in conjunction with I_bus_hard_min. Set to inf to disable. +Take noise into account when chosing a value.

+
+ +
+
+I_leak_max: Float32Property
+

In almost all scenarios, the currents on phase A, B and C should +add up to zero. A small amount of measurement noise is expected. +However if the sum of A, B, C currents exceeds this configuration +value, the motor gets disarmed immediately. + +Note that this feature is only works on devices with three current +sensors (e.g. ODrive v4).

+
+ +
+
+dc_calib_tau: Float32Property
+
+ +
+ +
+
+class ODrive.Motor.MotorType
+
+
+HIGH_CURRENT = 0 (0x0)
+
+ +
+
+GIMBAL = 1 (0x1)
+
+ +
+
+ACIM = 2 (0x2)
+
+ +
+ +
+
+class ODrive.AcimEstimator.Config
+
+
+slip_velocity: Float32Property
+
+ +
+ +
+
+class ODrive.Controller.Error
+
+
+OVERSPEED = 1 (0x1)
+
+ +
+
+INVALID_INPUT_MODE = 2 (0x2)
+
+ +
+
+UNSTABLE_GAIN = 4 (0x4)
+
+ +
+
+INVALID_MIRROR_AXIS = 8 (0x8)
+
+ +
+
+INVALID_LOAD_ENCODER = 16 (0x10)
+
+ +
+
+INVALID_ESTIMATE = 32 (0x20)
+
+ +
+
+INVALID_CIRCULAR_RANGE = 64 (0x40)
+
+ +
+
+SPINOUT_DETECTED = 128 (0x80)
+
+ +
+ +
+
+class ODrive.Controller.Config
+
+
+gain_scheduling_width: Float32Property
+
+ +
+
+enable_vel_limit: BoolProperty
+
+ +
+
+enable_torque_mode_vel_limit: BoolProperty
+

Enable velocity limit in torque control mode (requires a valid velocity estimator).

+
+ +
+
+enable_gain_scheduling: BoolProperty
+
+ +
+
+enable_overspeed_error: BoolProperty
+
+ +
+
+control_mode: Property[ODrive.Controller.ControlMode]
+
+ +
+
+input_mode: Property[ODrive.Controller.InputMode]
+
+ +
+
+pos_gain: Float32Property
+

units = (turn/s) / turn

+
+ +
+
+vel_gain: Float32Property
+

units = Nm / (turn/s)

+
+ +
+
+vel_integrator_gain: Float32Property
+

units = Nm / (turn/s * s)

+
+ +
+
+vel_integrator_limit: Float32Property
+

Limit the integrator output (independent of proportional gain output). Set to infinity to disable. Units = Nm

+
+ +
+
+vel_limit: Float32Property
+

Infinity to disable.

+
+ +
+
+vel_limit_tolerance: Float32Property
+

Ratio to vel_limit. Infinity to disable.

+
+ +
+
+vel_ramp_rate: Float32Property
+
+ +
+
+torque_ramp_rate: Float32Property
+
+ +
+
+circular_setpoints: BoolProperty
+
+ +
+
+circular_setpoint_range: Float32Property
+

circular range in [turns] for position setpoints when circular_setpoints is True

+
+ +
+
+steps_per_circular_range: Int32Property
+

Number of steps within the circular setpoint range. Set this and the circular setpoint range to powers of 2 for the best results.

+
+ +
+
+homing_speed: Float32Property
+
+ +
+
+inertia: Float32Property
+
+ +
+
+axis_to_mirror: Uint8Property
+
+ +
+
+mirror_ratio: Float32Property
+
+ +
+
+torque_mirror_ratio: Float32Property
+
+ +
+
+load_encoder_axis: Uint8Property
+

Default depends on Axis number and is set in load_configuration()

+
+ +
+
+input_filter_bandwidth: Float32Property
+
+ +
+
+anticogging: ODrive.Controller.Config.Anticogging
+
+ +
+
+mechanical_power_bandwidth: Float32Property
+

Bandwidth for mechanical power estimate. Used for spinout detection

+
+ +
+
+electrical_power_bandwidth: Float32Property
+

Bandwidth for electrical power estimate. Used for spinout detection. Dot product of Vdq and Idq

+
+ +
+
+spinout_mechanical_power_threshold: Float32Property
+

Mechanical power threshold for spinout detection. This should be a negative value

+
+ +
+
+spinout_electrical_power_threshold: Float32Property
+

Electrical power threshold for spinout detection. This should be a positive value

+
+ +
+ +
+
+class ODrive.Controller.Autotuning
+
+
+frequency: Float32Property
+
+ +
+
+pos_amplitude: Float32Property
+
+ +
+
+vel_amplitude: Float32Property
+
+ +
+
+torque_amplitude: Float32Property
+
+ +
+ +
+
+class ODrive.Controller.ControlMode
+
+
+VOLTAGE_CONTROL = 0 (0x0)
+
+ +
+
+TORQUE_CONTROL = 1 (0x1)
+
+ +
+
+VELOCITY_CONTROL = 2 (0x2)
+
+ +
+
+POSITION_CONTROL = 3 (0x3)
+
+ +
+ +
+
+class ODrive.Controller.InputMode
+
+
+INACTIVE = 0 (0x0)
+
+ +
+
+PASSTHROUGH = 1 (0x1)
+
+ +
+
+VEL_RAMP = 2 (0x2)
+
+ +
+
+POS_FILTER = 3 (0x3)
+
+ +
+
+MIX_CHANNELS = 4 (0x4)
+
+ +
+
+TRAP_TRAJ = 5 (0x5)
+
+ +
+
+TORQUE_RAMP = 6 (0x6)
+
+ +
+
+MIRROR = 7 (0x7)
+
+ +
+
+Tuning = 8 (0x8)
+
+ +
+ +
+
+class ODrive.Controller.Config.Anticogging
+
+
+index: Uint32Property
+
+ +
+
+pre_calibrated: BoolProperty
+
+ +
+
+calib_anticogging: BoolProperty
+
+ +
+
+calib_pos_threshold: Float32Property
+
+ +
+
+calib_vel_threshold: Float32Property
+
+ +
+
+cogging_ratio: Float32Property
+
+ +
+
+anticogging_enabled: BoolProperty
+
+ +
+ +
+
+class ODrive.Encoder.Error
+
+
+UNSTABLE_GAIN = 1 (0x1)
+
+ +
+
+CPR_POLEPAIRS_MISMATCH = 2 (0x2)
+
+ +
+
+NO_RESPONSE = 4 (0x4)
+
+ +
+
+UNSUPPORTED_ENCODER_MODE = 8 (0x8)
+
+ +
+
+ILLEGAL_HALL_STATE = 16 (0x10)
+
+ +
+
+INDEX_NOT_FOUND_YET = 32 (0x20)
+
+ +
+
+ABS_SPI_TIMEOUT = 64 (0x40)
+
+ +
+
+ABS_SPI_COM_FAIL = 128 (0x80)
+
+ +
+
+ABS_SPI_NOT_READY = 256 (0x100)
+
+ +
+
+HALL_NOT_CALIBRATED_YET = 512 (0x200)
+
+ +
+ +
+
+class ODrive.Encoder.Config
+
+
+mode: Property[ODrive.Encoder.Mode]
+
+ +
+
+use_index: BoolProperty
+
+ +
+
+index_offset: Float32Property
+
+ +
+
+use_index_offset: BoolProperty
+
+ +
+
+find_idx_on_lockin_only: BoolProperty
+
+ +
+
+abs_spi_cs_gpio_pin: Uint16Property
+

Make sure that the GPIO is in GPIO_MODE_DIGITAL.

+
+ +
+
+cpr: Int32Property
+
+ +
+
+phase_offset: Int32Property
+
+ +
+
+phase_offset_float: Float32Property
+
+ +
+
+direction: Int32Property
+
+ +
+
+pre_calibrated: BoolProperty
+
+ +
+
+enable_phase_interpolation: BoolProperty
+
+ +
+
+bandwidth: Float32Property
+
+ +
+
+calib_range: Float32Property
+
+ +
+
+calib_scan_distance: Float32Property
+
+ +
+
+calib_scan_omega: Float32Property
+
+ +
+
+ignore_illegal_hall_state: BoolProperty
+
+ +
+
+hall_polarity: Uint8Property
+
+ +
+
+hall_polarity_calibrated: BoolProperty
+
+ +
+
+sincos_gpio_pin_sin: Uint16Property
+

Analog sine signal of a sin/cos encoder. The corresponding GPIO must be in GPIO_MODE_ANALOG_IN.

+
+ +
+
+sincos_gpio_pin_cos: Uint16Property
+

Analog cosine signal of a sin/cos encoder. The corresponding GPIO must be in GPIO_MODE_ANALOG_IN.

+
+ +
+ +
+
+class ODrive.Encoder.Mode
+
+
+INCREMENTAL = 0 (0x0)
+
+ +
+
+HALL = 1 (0x1)
+
+ +
+
+SINCOS = 2 (0x2)
+
+ +
+
+SPI_ABS_CUI = 3 (0x3)
+
+ +
+
+SPI_ABS_AMS = 4 (0x4)
+
+ +
+
+SPI_ABS_AEAT = 5 (0x5)
+
+ +
+
+SPI_ABS_RLS = 6 (0x6)
+
+ +
+
+SPI_ABS_MA732 = 7 (0x7)
+
+ +
+ +
+
+class ODrive.SensorlessEstimator.Error
+
+
+UNSTABLE_GAIN = 1 (0x1)
+
+ +
+
+UNKNOWN_CURRENT_MEASUREMENT = 2 (0x2)
+
+ +
+ +
+
+class ODrive.SensorlessEstimator.Config
+
+
+observer_gain: Float32Property
+
+ +
+
+pll_bandwidth: Float32Property
+
+ +
+
+pm_flux_linkage: Float32Property
+
+ +
+ +
+
+class ODrive.TrapezoidalTrajectory.Config
+
+
+vel_limit: Float32Property
+
+ +
+
+accel_limit: Float32Property
+
+ +
+
+decel_limit: Float32Property
+
+ +
+ +
+
+class ODrive.Endstop.Config
+
+
+gpio_num: Uint16Property
+

Make sure the corresponding GPIO is in GPIO_MODE_DIGITAL.

+
+ +
+
+enabled: BoolProperty
+
+ +
+
+offset: Float32Property
+
+ +
+
+is_active_high: BoolProperty
+
+ +
+
+debounce_ms: Uint32Property
+
+ +
+ +
+
+class ODrive.MechanicalBrake.Config
+
+
+gpio_num: Uint16Property
+
+ +
+
+is_active_low: BoolProperty
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/genindex.html b/docs/reStructuredText/_build/html/genindex.html new file mode 100644 index 00000000..1fc68e92 --- /dev/null +++ b/docs/reStructuredText/_build/html/genindex.html @@ -0,0 +1,1501 @@ + + + + + + Index — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Index
  • +
  • +
  • +
+
+
+
+
+ + +

Index

+ +
+ A + | B + | C + | D + | E + | F + | G + | H + | I + | L + | M + | N + | O + | P + | R + | S + | T + | U + | V + | W + +
+

A

+ + + +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

H

+ + + +
+ +

I

+ + + +
+ +

L

+ + + +
+ +

M

+ + + +
+ +

N

+ + + +
+ +

O

+ + + +
+ +

P

+ + + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + + +
+ +

V

+ + + +
+ +

W

+ + + +
+ + + +
+
+
+ +
+ +
+

© Copyright 2021, ODrive Robotics.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/getting-started.html b/docs/reStructuredText/_build/html/getting-started.html new file mode 100644 index 00000000..d88be28b --- /dev/null +++ b/docs/reStructuredText/_build/html/getting-started.html @@ -0,0 +1,723 @@ + + + + + + Getting Started — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Getting Started

+ +
+

Hardware Requirements

+

You will need

+
    +
  • One or two brushless motors. It is fine, even recommended, to start testing with just a single motor and encoder.

  • +
  • One or two encoder(s).

  • +
  • A power supply (12V-24V for the 24V board variant, 12V-56V for the 56V board variant). A battery is also fine. Some advice on choosing a power supply can be found here.

  • +
+
    +
  • A power resistor. A good starting point would be the 50W resistor included with your ODrive.

  • +
+
+

Wiring up the ODrive

+
+

Warning

+

Firmware, software, and documentation is intended for use with ODrive motor controllers purchased from odriverobotics.com. ODrive Robotics does not sell products through any channel other than odriverobotics.com. We do not provide support for ODrives purchased elsewhere.

+
+
+

Warning

+

Make sure you have a good mechanical connection between the encoder and the motor, slip can cause disastrous oscillations or runaway.

+
+

All non-power I/O is 3.3V output and 5V tolerant on input, on ODrive v3.3 and newer.

+
+

Wiring up the motors

+

Connect the motor phases into the 3-phase screw terminals. It is not recommended to use a clip-on connector such as an alligator clip, as this can cause issues with the phase resistance/inductance measurements.

+
+
+

Wiring up the encoders

+

Connect the encoder(s) to J4. The A,B phases are required, and the Z (index pulse) is optional. The A,B and Z lines have 3.3k pull up resistors, for use with open-drain encoder outputs. For single ended push-pull signals with weak drive current (<4mA), you may want to desolder the pull-ups.

+_images/ODriveBasicWiring.png +
+
+

Safety & Power UP

+
+

Warning

+

Always think safety before powering up the ODrive if motors are attached. Consider what might happen if the motor spins as soon as power is applied.

+
+
    +
  • Unlike some devices, the ODrive does not recieve power over the USB port so the 24/56 volt power input is required even just to communicate with it using USB. It is ok to power up the ODrive before or after connecting the USB cable.

  • +
  • To power up the ODrive, connect the power source to the DC terminals. Make sure to pay attention to the polarity. Try to connect the power source first and then turn it on to avoid inrush current. If this can’t be avoided then a small spark is normal. This is caused by the capacitors charging up.

  • +
  • Make sure to avoid a ground loop! See the ground loop page for details.

  • +
+
+
+
+

Downloading and Installing odriveool

+

Most instructions in this guide refer to a utility called odrivetool, so you should install that first.

+
+
    +
  1. Install Python 3

    +
    +

    We recommend the Anaconda distribution because it packs a lot of useful scientific tools, however you can also install the standalone python.

    +
      +
    • Anaconda: Download the installer from here. Execute the downloaded file and follow the instructions.

    • +
    • Standalone Python: Download the installer for 3.8.6 from here. Execute the downloaded file and follow the instructions. As of Oct 2020, Matplotlib (required by odrivetool) had not been updated to work with 3.9, so please use 3.8.6.

    • +
    • If you have Python 2 installed alongside Python 3, replace pip by C:\Users\YOUR_USERNAME\AppData\Local\Programs\Python\Python36-32\Scripts\pip. If you have trouble with this step then refer to this walkthrough.

    • +
    +
    +
  2. +
  3. Launch the command prompt.

    +
    +
      +
    • Anaconda: In the start menu, type Anaconda Prompt Enter

    • +
    • Standalone Python In the start menu, type cmd Enter

    • +
    +
    +
  4. +
  5. Install odrivetool by running

    +
    +
    pip install --upgrade odrive
    +
    +
    +
    +
  6. +
+
+
+
+

Firmware

+

To simplify the start up procedure we suggest using the latest firmware verion. Please follow the instructions here to make sure your firmware is up to date.

+
+

ODrive v3.5 and later

+

Your board should come preflashed with the latest firmware. If you run into problems, check your firmware version and upgrade if possible.

+
+
+

ODrive v3.4 and earlier

+

Your board does not come preflashed with any firmware. Follow the instructions here on the ST Link procedure before you continue.

+
+
+
+
+

Start odrivetool

+
    +
  • To launch the main interactive ODrive tool, type odrivetool and Enter.

  • +
  • Connect your ODrive and wait for the tool to find it. If it dos not connect after a few seconds refer to the troubleshooting guide.

  • +
  • Now you can, for instance type odrv0.vbus_voltage Enter to inspect the boards main supply voltage.

  • +
+

It should look something like this:

+
ODrive control utility v0.5.4
+Please connect your ODrive.
+Type help() for help.
+
+Connected to ODrive 306A396A3235 as odrv0
+In [1]: odrv0.vbus_voltage
+Out[1]: 11.97055721282959
+
+
+

The tool you’re looking at is a fully capable Python command prompt, so you can type any valid python code. +You can read more about odrivetool here.

+
+
+

Motor Configuration

+
+

Warning

+

Read this section carefully, else you risk breaking something.

+
+

Configuration of a motor is done within the odrivetool console, type odrivetool and Enter to launch. Make sure you have completed :ref:` Start odrivetool <odrivetool-startup>` before continuing.

+

This section assumes that the motor being configured is connected to the ODrive as M0 (axis0). To configure M1, simply replace all instances of axis0 with axis1.

+

If any of the following steps fail, print the errors by running dump_errors(odrv0) and refer to the Error codes section for debugging. +Once the error(s) have been identified and corrected, run odrv0.clear_errors() to clear them before moving forward.

+
+

Working on a hoverboard?

+

There is a separate guide specifically for hoverboard motors

+
+
+

Setting the Limits

+

With odrivetool open you can assign variables directly by name. +For instance, to set the current limit of M0 to 10A you would type

+
odrv0.axis0.motor.config.current_lim = 10
+
+
+
+

Current limit

+
+

The default current limit, for safety reasons, is set to 10A. This is quite weak, but good for making sure the drive is stable. +To change the current limit,

+
odrv0.axis0.motor.config.current_lim = val
+
+
+

Once you have tuned the ODrive, you can increase this to 60A to increase performance.

+
+

Note

+

The motor current and the current drawn from the power supply is not the same in general. You should not look at the power supply current to see what is going on with the motor current.*

+
+
+
+
+

Velocity limit

+

The motor will be limited to this speed in [turn/s]. Again the default value is quite slow.

+
odrv0.axis0.controller.config.vel_limit = 2
+
+
+
+
+

Calibration Current

+

You can change odrv0.axis0.motor.config.calibration_current [A] to the largest value you feel comfortable leaving running through the motor continuously when the motor is stationary. +If you are using a small motor (i.e. 15A current rated) you may need to reduce calibration_current to a value smaller than the default.

+
+
+
+

Setting Other Hardware Parameters

+
+

Enable Brake Resistor

+

Set this to True if using a brake resistor. You need to save the configuration and reboot the ODrive for this to take effect.

+
odrv0.config.enable_brake_resistor
+
+
+
+
+

Brake Resistor Value

+

This is the resistance [Ohms] of the brake resistor. You can leave this at the default setting if you are not using a brake resistor.

+
odrv0.config.brake_resistance
+
+
+
+

Note

+

There may be some extra resistance in your wiring and in the screw terminals, so if you are getting issues while braking you may want to increase this parameter by around 0.05 ohm.

+
+
+
+

Negative Current

+

This is the amount of current [Amps] allowed to flow back into the power supply. The convention is that it is negative. By default, it is set to a conservative value of 10mA. If you are using a brake resistor and getting DC_BUS_OVER_REGEN_CURRENT errors, raise it slightly. +If you are not using a brake resistor and you intend to send braking current back to the power supply, set this to a safe level for your power source. Note that in that case, it should be higher than your motor current limit + current limit margin.

+
odrv0.config.dc_max_negative_current
+
+
+
+
+

Pole Pairs

+

This is the number of magnet poles in the rotor, divided by two. To find this, you can simply count the number of permanent magnets in the rotor, if you can see them.

+
odrv0.axis0.motor.config.pole_pairs
+
+
+
+

Note

+

This is not the same as the number of coils in the stator.

+
+

A good way to find the number of pole pairs is with a current limited power supply. Connect any two of the three phases to a power supply outputting around 2A, spin the motor by hand, and count the number of detents. +This will be the number of pole pairs. If you can’t distinguish the detents from the normal cogging present when the motor is disconnected, increase the current. +Another way is sliding a loose magnet in your hand around the rotor, and counting how many times it stops. This will be the number of pole pairs. If you use a ferrous piece of metal instead of a magnet, you will get the number of magnet poles.

+
+
+

Torque Constant

+

This is the ratio of torque produced by the motor per Amp of current delivered to the motor. This should be set to 8.27 / (motor KV).

+
odrv0.axis0.motor.config.torque_constant
+
+
+

If you decide that you would rather command torque in units of Amps, you could simply set the torque constant to 1.

+
+
+

Motor Type

+

This is the type of motor being used. Currently two types of motors are supported: High-current motors (MOTOR_TYPE_HIGH_CURRENT, [0]) and gimbal motors (MOTOR_TYPE_GIMBAL, [2]).

+
odrv0.axis0.motor.config.motor_type
+
+
+
+

Note

+

When using gimbal motors, current_lim and calibration_current actually mean voltage limit and calibration voltage respectively, since we don’t use current feedback. +This means that if you set it to 10, it means 10V, despite the name of the parameter.

+
+
+
+

Motor Thermistor

+

If using motor thermistor please see the thermistors page for setup.

+
+
+
+
+

Encoder Configuration

+
+

With Encoder

+

Set the encoder count per revolution [CPR] value

+
odrv0.axis0.encoder.config.cpr
+
+
+

This is 4x the Pulse Per Revolution (PPR) value. Usually this is indicated in the datasheet of your encoder. +If you purchased an encoder from our shop, you can find the parameters here.

+
+
+

Without Encoder

+ +
+
+
+

Save Configuration

+

You can save all .config parameters to persistent memory so the ODrive remembers them between power cycles. This will reboot the board.

+
odrv0.save_configuration()
+
+
+
+
+

Position control of M0

+

Let’s get motor 0 up and running. The procedure for motor 1 is exactly the same, so feel free to substitute axis1 wherever it says axis0.

+
+
    +
  1. Start the calibratrion sequence by entering

  2. +
+
odrv0.axis0.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE
+
+
+

and press Enter.

+

After about 2 seconds you should hear a beep. Then the motor will turn slowly in one direction for a few seconds, then back in the other direction.

+

The startup procedure is demonstrated here.

+
+

Note

+

The rotor must be allowed to rotate without any biased load during startup. +That means mass and weak friction loads are fine, but gravity or spring loads are not okay. +Also note that in the video, the motors spin after initialization, but in the current software the default behaviour is not like that.

+
+
    +
  1. Start closed loop control by typing

    +
    +
    odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
    +
    +
    +

    and press Enter. From now on the ODrive will try to hold the motor’s position. If you try to turn it by hand, it will fight you gently. That is unless you bump up odrv0.axis0.motor.config.current_lim, in which case it will fight you more fiercely. +If the motor begins to vibrate either immediately or after being disturbed you will need to lower the controller gains.

    +
    +
  2. +
  3. Send the motor a new position setpoint with

    +
    +
    odrv0.axis0.controller.input_pos = 1
    +
    +
    +

    The units are in turns.

    +
    +
  4. +
  5. At this point you will probably want to Properly tune the motor controller in order to maximize system performance.

  6. +
+
+
+

Other Control Modes

+

The default control mode is unfiltered position control in the absolute encoder reference frame. +The ODrive can be used with a variety of control modes, to learn more, check out the control modes documentation page.

+
+
+
+

Watchdog Timer

+

Each axis has a configurable watchdog timer that can stop the motors if the +control connection to the ODrive is interrupted.

+

Each axis has a configurable watchdog timeout: axis.config.watchdog_timeout, +measured in seconds. Set

+
axis.config.enable_watchdog = True
+
+
+

to turn on this feature.

+

The watchdog is fed using the axis.watchdog_feed() method of each axis. +Some ascii commands feed the watchdog automatically.

+
+
+

What’s next?

+

You can now: +* Properly tune the motor controller to unlock the full potential of the ODrive. +* See what other commands and parameters are available, in order to better control the ODrive. +* Control the ODrive from your own program or hook it up to an existing system through one of it’s interfaces. +* See how you can improve the behavior during the startup procedure, like bypassing encoder calibration. +* The CAN communication is the most reliable way of talking to ODrive in a real application. Check out the CAN Guide and CAN Protocol

+

If you have any issues or any questions please get in touch. The ODrive Community warmly welcomes you.

+
+
+

Upgrading from 0.4.12

+

A new version (0.5.4) of ODrive firmware has released, complete with a new odrivetool. +Follow the installation instructions, making sure to add the --upgrade flag to pip commands, and check out the Changelog for changes!

+

The odrivetool will stage and restore your configuration. +This probably isn’t wise for the 0.4.12 -> 0.5.1 upgrade, so we suggest using odrv0.erase_configuration() immediately after connecting the first time.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/ground-loops.html b/docs/reStructuredText/_build/html/ground-loops.html new file mode 100644 index 00000000..85a167f6 --- /dev/null +++ b/docs/reStructuredText/_build/html/ground-loops.html @@ -0,0 +1,211 @@ + + + + + + Ground Loops — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Ground Loops

+ +

For electrical devices to communicate, most of the time they require a common ground connection. +Best practice is to connect the grounds back to a single point, called a “star ground”. +If there are multiple paths to ground, a “ground loop” is formed. +Ground loops and wire inductance can cause issues for high current electronics like ODrive. +As an example of what can go wrong, look at the diagram below.

+
+

The Problem

+Ground Loop with inductance +

The issue is the inductance of the power wires between the ODrive and power supply. +The inductance and the high current drawn by the ODrive causes V_1 to not be the same as V_2. +If the voltage caused by the wire inductance and current is high enough, the 0-5V gpio signals can swing much higher or lower than the normal 0-5V range. +This causes a current to flow through the ODrive GPIO pins.

+
+
+

Solutions

+
+

Reduce Power Wire

+

All wires have some amount of inductance. The inductance is proportional to the length of the wires and the area of the loop formed by the positive and negative power wires. +It is beneficial to keep those wires as short as possible and as close together as possible. This reduces the effect of the problem but does not eliminate it!

+
+
+

Isolation

+

To fix this, the ground loop must be broken. This can be achieved by isolating the power supplies (no common V-) and connecting a signal ground between the RPi and ODrive. +An example of this is a single ODrive connected to a battery and a device like a RPi connected to a mains power supply or different battery. +If more than one ODrive is in use and they share a power supply, you have a ground loop again.

+

The best way to fix this is to isolate the data connection between the RPi and ODrive(s). The diagram below illustrates where the isolator should go.

+Ground Loop fixed by isolator +

By isolating the data connection (whether it is GPIO, USB, or UART), the ground loop is broken. +Isolation can be achieved by using a USB isolator or a signal isolator for GPIO connections.

+

Here are some examples of USB isolators:

+ +

These are generic devices. If Aliexpress is not an option for you, you can probably find them available in your area from a different vendor. +In the US, these types of isolators are available from Amazon and Ebay.

+

For GPIO connections, like UART, Step/dir, PWM, etc, you can use signal isolators like the ISO7762F from Texas Instruments. +Keep in mind that isolators have a speed limit. Devices like optocouplers might be too slow for UART connections or Step/direction. +Check the datasheet!

+
+
+

Current Limiting

+

If isolators are not an option, you can use series resistors to limit the injection current to a safe level. Place a resistor on the recieving side of all connections to or from the ODrive GPIO pins. +4.7kOhms is a good value, but anything from 3.3kOhms to 10kOhms should work. Series resistors offer some protection for the ODrive, but the ground loop problem can still cause the GPIOs to be pulled high or low for short periods of time. +The ODrive and your other device will most likely be safe but communications might be interrupted.

+

As an example, for UART, you would place a resistor close to the RX pin of the ODrive and another one close to the RX pin of the other device (like an Arduino). +This allows the driving side, the TX pins, to adequately drive the bus capacitance.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/hoverboard.html b/docs/reStructuredText/_build/html/hoverboard.html new file mode 100644 index 00000000..eff132cd --- /dev/null +++ b/docs/reStructuredText/_build/html/hoverboard.html @@ -0,0 +1,385 @@ + + + + + + Hoverboard motor and remote control setup guide — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Hoverboard motor and remote control setup guide

+ +

By popular request here follows a step-by-step guide on how to setup the ODrive to drive hoverboard motors using RC PWM input. +Each step is accompanied by some explanation so hopefully you can carry over some of the steps to other setups and configurations.

+
+

Hoverboard Motor Wiring

+

Hoverboard motors come with three motor phases (usually colored yellow, blue, green) which are thicker, and a set of 5 thinner wires for the hall sensor feedback (usually colored red, yellow, blue, green, black).

+

You may wire the motor phases in any order into a motor connector on the ODrive, as we will calibrate the phase alignment later anyway. Wire the hall feedback into the ODrive J4 connector (make sure that the motor channel number matches) as follows:

+ ++++ + + + + + + + + + + + + + + + + + + + + + + +

Hall wire

J4 signal

Red

5V

Yellow

A

Blue

B

Green

Z

Black

GND

+
+

Note

+

In order to be compatible with encoder inputs, the ODrive doesn’t have any filtering capacitors on the pins where the hall sensors connect. +Therefore to get a reliable hall signal, it is recommended that you add some filter capacitors to these pins. +We recommend about 22nF between each signal pin and GND. +You can see instructions here.

+
+
+
+

Hoverboard Motor Configuration

+
+
Standard 6.5 inch hoverboard hub motors have 30 permanent magnet poles, and thus 15 pole pairs.

If you have a different motor you need to count the magnets or have a reliable datasheet for this information.

+
+
+
odrv0.axis0.motor.config.pole_pairs = 15
+
+
+

Hoverboard hub motors are quite high resistance compared to the hobby aircraft motors, so we want to use a bit higher voltage for the motor calibration, and set up the current sense gain to be more sensitive. +The motors are also fairly high inductance, so we need to reduce the bandwidth of the current controller from the default to keep it stable. +The KV rating of the motor also should be known. It can be measured using the “drill test”, detailed here. +If you can’t perform this test, a typical value is 16.

+
odrv0.axis0.motor.config.resistance_calib_max_voltage = 4
+odrv0.axis0.motor.config.requested_current_range = 25 #Requires config save and reboot
+odrv0.axis0.motor.config.current_control_bandwidth = 100
+odrv0.axis0.motor.config.torque_constant = 8.27 / <measured KV>
+
+
+

If you set the encoder to hall mode (instead of incremental). +See the pinout for instructions on how to plug in the hall feedback. +The hall feedback has 6 states for every pole pair in the motor. +Since we have 15 pole pairs, we set the cpr to 15*6 = 90. +Since hall sensors are low resolution feedback, we also bump up the offset calibration displacement to get better calibration accuracy.

+
odrv0.axis0.encoder.config.mode = ENCODER_MODE_HALL
+odrv0.axis0.encoder.config.cpr = 90
+odrv0.axis0.encoder.config.calib_scan_distance = 150
+odrv0.config.gpio9_mode = GPIO_MODE_DIGITAL
+odrv0.config.gpio10_mode = GPIO_MODE_DIGITAL
+odrv0.config.gpio11_mode = GPIO_MODE_DIGITAL
+
+
+

Since the hall feedback only has 90 counts per revolution, we want to reduce the velocity tracking bandwidth to get smoother velocity estimates. +We can also set these fairly modest gains that will be a bit sloppy but shouldn’t shake your rig apart if it’s built poorly. +Make sure to tune the gains up when you have everything else working to a stiffness that is applicable to your application. +Lets also start in velocity control mode since that is probably what you want for a wheeled robot. Note that in velocity mode pos_gain isn’t used but I have given you a recommended value anyway in case you wanted to run position control mode.

+
+
+

Note

+

The gains used here are dependent on the torque_constant and cpr config settings. The values for hoverboard motors are very different from the stock settings. Do not skip the above steps and go straight to these settings!

+
+
+
odrv0.axis0.encoder.config.bandwidth = 100
+odrv0.axis0.controller.config.pos_gain = 1
+odrv0.axis0.controller.config.vel_gain = 0.02 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr
+odrv0.axis0.controller.config.vel_integrator_gain = 0.1 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr
+odrv0.axis0.controller.config.vel_limit = 10
+odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL
+
+
+

In the next step we are going to start powering the motor and so we want to make sure that some of the above settings that require a reboot are applied first.

+
odrv0.save_configuration()
+odrv0.reboot()
+
+
+

Make sure the motor is free to move, then activate the motor calibration.

+
odrv0.axis0.requested_state = AXIS_STATE_MOTOR_CALIBRATION
+
+
+

You can read out all the data pertaining to the motor:

+
odrv0.axis0.motor
+
+
+

Check to see that there is no error and that the phase resistance and inductance are reasonable. Here are the results I got:

+
error = 0x0000 (int)
+phase_inductance = 0.00033594953129068017 (float)
+phase_resistance = 0.1793474406003952 (float)
+
+
+

If all looks good then you can tell the ODrive that saving this calibration to persistent memory is OK:

+
odrv0.axis0.motor.config.pre_calibrated = True
+
+
+

Next step is to check the alignment between the motor and the hall sensor. +Because of this step you are allowed to plug the motor phases in random order and also the hall signals can be random. +Just don’t change it after calibration.

+

Make sure the motor is free to move and run:

+
odrv0.axis0.requested_state = AXIS_STATE_ENCODER_HALL_POLARITY_CALIBRATION
+
+
+

Check the status of the encoder object:

+
odrv0.axis0.encoder
+
+
+

Check that there are no errors.

+
error = 0x0000 (int)
+
+
+

If the hall encoder polarity calibration was successful, run the encoder offset calibration.

+
odrv0.axis0.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION
+
+
+

Check the status of the encoder object:

+
odrv0.axis0.encoder
+
+
+

Check that there are no errors. +If your hall sensors has a standard timing angle then phase_offset_float should be close to 0.5 mod 1. Meaning values close to -1.5, -0.5, 0.5, or 1.5, etc are all good.

+
error = 0x0000 (int)
+config:
+  phase_offset_float = 0.5126956701278687 (float)
+
+
+

If all looks good then you can tell the ODrive that saving this calibration to presistent memory is OK:

+
odrv0.axis0.encoder.config.pre_calibrated = True
+
+
+

OK, we are now done with the motor configuration! Time to save, reboot, and then test it. +The ODrive starts in idle (we will look at changing this later) so we can enable closed loop control.

+
odrv0.save_configuration()
+odrv0.reboot()
+odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
+odrv0.axis0.controller.input_vel = 2
+# Your motor should spin here
+odrv0.axis0.controller.input_vel = 0
+odrv0.axis0.requested_state = AXIS_STATE_IDLE
+
+
+

Hopefully you got your motor to spin! Feel free to repeat all of the above for the other axis if appropriate.

+
+
+

PWM Input

+

If you want to drive your hoverboard wheels around with an RC remote control you can use the :ref: RC PWM input <rc-pwm>. There is more information in that link. +Lets use GPIO 3/4 for the velocity inputs so that we don’t have to disable UART. +Then let’s map the full stick range of these inputs to some suitable velocity setpoint range. +We also have to reboot to activate the PWM input.

+
odrv0.config.gpio3_pwm_mapping.min = -2
+odrv0.config.gpio3_pwm_mapping.max = 2
+odrv0.config.gpio3_pwm_mapping.endpoint = odrv0.axis0.controller._input_vel_property
+
+odrv0.config.gpio4_pwm_mapping.min = -2
+odrv0.config.gpio4_pwm_mapping.max = 2
+odrv0.config.gpio4_pwm_mapping.endpoint = odrv0.axis1.controller._input_vel_property
+
+
+
odrv0.save_configuration()
+odrv0.reboot()
+
+
+

Now we can check that the sticks are writing to the velocity setpoint. +Move the stick, print input_vel, move to a different position, check again.

+
In [1]: odrv0.axis1.controller.input_vel
+Out[1]: 0.01904754638671875
+
+In [2]: odrv0.axis1.controller.input_vel
+Out[2]: 0.01904754638671875
+
+In [3]: odrv0.axis1.controller.input_vel
+Out[3]: 1.152389526367188
+
+In [4]: odrv0.axis1.controller.input_vel
+Out[4]: 1.81905517578125
+
+In [5]: odrv0.axis1.controller.input_vel
+Out[5]: -0.990474700927734
+
+
+

Ok, now we should be able to turn on the drive and control the wheels!

+
odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
+odrv0.axis1.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
+
+
+
+
+

Safety

+

Be sure to setup the Failsafe feature on your RC Receiver so that if connection is lost between the remote and the receiver, the receiver outputs 0 and 0 for the velocity setpoint of both axes (or whatever is safest for your configuration). Also note that if the receiver turns off (loss of power, etc) or if the signal from the receiver to the ODrive is lost (wire comes unplugged, etc), the ODrive will continue the last commanded velocity setpoint. There is currently no timeout function in the ODrive for PWM inputs.

+
+
+

Automatic Startup

+

Try to reboot and then activate AXIS_STATE_CLOSED_LOOP_CONTROL on both axis. +Check that everything is operational and works as expected. +If so, you can now make the ODrive turn on the motor power automatically after booting. +This is useful if you are going to be running the ODrive without a PC or other logic board.

+
odrv0.axis0.config.startup_closed_loop_control = True
+odrv0.axis1.config.startup_closed_loop_control = True
+odrv0.save_configuration()
+odrv0.reboot()
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/index.html b/docs/reStructuredText/_build/html/index.html new file mode 100644 index 00000000..12d564f0 --- /dev/null +++ b/docs/reStructuredText/_build/html/index.html @@ -0,0 +1,683 @@ + + + + + + Getting Started — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Getting Started

+ +
+

Hardware Requirements

+

You will need

+
    +
  • One or two brushless motors. It is fine, even recommended, to start testing with just a single motor and encoder.

  • +
  • One or two encoder(s).

  • +
  • A power supply (12V-24V for the 24V board variant, 12V-56V for the 56V board variant). A battery is also fine. Some advice on choosing a power supply can be found here.

  • +
+
    +
  • A power resistor. A good starting point would be the 50W resistor included with your ODrive.

  • +
+
+

Wiring up the ODrive

+
+

Warning

+

Firmware, software, and documentation is intended for use with ODrive motor controllers purchased from odriverobotics.com. ODrive Robotics does not sell products through any channel other than odriverobotics.com. We do not provide support for ODrives purchased elsewhere.

+
+
+

Warning

+

Make sure you have a good mechanical connection between the encoder and the motor, slip can cause disastrous oscillations or runaway.

+
+

All non-power I/O is 3.3V output and 5V tolerant on input, on ODrive v3.3 and newer.

+
+

Wiring up the motors

+

Connect the motor phases into the 3-phase screw terminals. It is not recommended to use a clip-on connector such as an alligator clip, as this can cause issues with the phase resistance/inductance measurements.

+
+
+

Wiring up the encoders

+

Connect the encoder(s) to J4. The A,B phases are required, and the Z (index pulse) is optional. The A,B and Z lines have 3.3k pull up resistors, for use with open-drain encoder outputs. For single ended push-pull signals with weak drive current (<4mA), you may want to desolder the pull-ups.

+_images/ODriveBasicWiring.png +
+
+

Safety & Power UP

+
+

Warning

+

Always think safety before powering up the ODrive if motors are attached. Consider what might happen if the motor spins as soon as power is applied.

+
+
    +
  • Unlike some devices, the ODrive does not recieve power over the USB port so the 24/56 volt power input is required even just to communicate with it using USB. It is ok to power up the ODrive before or after connecting the USB cable.

  • +
  • To power up the ODrive, connect the power source to the DC terminals. Make sure to pay attention to the polarity. Try to connect the power source first and then turn it on to avoid inrush current. If this can’t be avoided then a small spark is normal. This is caused by the capacitors charging up.

  • +
  • Make sure to avoid a ground loop! See the ground loop page for details.

  • +
+
+
+
+

Downloading and Installing odriveool

+

Most instructions in this guide refer to a utility called odrivetool, so you should install that first.

+
+
    +
  1. Install Python 3

    +
    +

    We recommend the Anaconda distribution because it packs a lot of useful scientific tools, however you can also install the standalone python.

    +
      +
    • Anaconda: Download the installer from here. Execute the downloaded file and follow the instructions.

    • +
    • Standalone Python: Download the installer for 3.8.6 from here. Execute the downloaded file and follow the instructions. As of Oct 2020, Matplotlib (required by odrivetool) had not been updated to work with 3.9, so please use 3.8.6.

    • +
    • If you have Python 2 installed alongside Python 3, replace pip by C:\Users\YOUR_USERNAME\AppData\Local\Programs\Python\Python36-32\Scripts\pip. If you have trouble with this step then refer to this walkthrough.

    • +
    +
    +
  2. +
  3. Launch the command prompt.

    +
    +
      +
    • Anaconda: In the start menu, type Anaconda Prompt Enter

    • +
    • Standalone Python In the start menu, type cmd Enter

    • +
    +
    +
  4. +
  5. Install odrivetool by running

    +
    +
    pip install --upgrade odrive
    +
    +
    +
    +
  6. +
+
+
+
+

Firmware

+

To simplify the start up procedure we suggest using the latest firmware verion. Please follow the instructions here to make sure your firmware is up to date.

+
+

ODrive v3.5 and later

+

Your board should come preflashed with the latest firmware. If you run into problems, check your firmware version and upgrade if possible.

+
+
+

ODrive v3.4 and earlier

+

Your board does not come preflashed with any firmware. Follow the instructions here on the ST Link procedure before you continue.

+
+
+
+
+

Start odrivetool

+
    +
  • To launch the main interactive ODrive tool, type odrivetool and Enter.

  • +
  • Connect your ODrive and wait for the tool to find it. If it dos not connect after a few seconds refer to the troubleshooting guide.

  • +
  • Now you can, for instance type odrv0.vbus_voltage Enter to inspect the boards main supply voltage.

  • +
+

It should look something like this:

+
ODrive control utility v0.5.4
+Please connect your ODrive.
+Type help() for help.
+
+Connected to ODrive 306A396A3235 as odrv0
+In [1]: odrv0.vbus_voltage
+Out[1]: 11.97055721282959
+
+
+

The tool you’re looking at is a fully capable Python command prompt, so you can type any valid python code. +You can read more about odrivetool here.

+
+
+

Motor Configuration

+
+

Warning

+

Read this section carefully, else you risk breaking something.

+
+

Configuration of a motor is done within the odrivetool console, type odrivetool and Enter to launch. Make sure you have completed :ref:` Start odrivetool <odrivetool-startup>` before continuing.

+

This section assumes that the motor being configured is connected to the ODrive as M0 (axis0). To configure M1, simply replace all instances of axis0 with axis1.

+

If any of the following steps fail, print the errors by running dump_errors(odrv0) and refer to the Error codes section for debugging. +Once the error(s) have been identified and corrected, run odrv0.clear_errors() to clear them before moving forward.

+
+

Working on a hoverboard?

+

There is a separate guide specifically for hoverboard motors

+
+
+

Setting the Limits

+

With odrivetool open you can assign variables directly by name. +For instance, to set the current limit of M0 to 10A you would type

+
odrv0.axis0.motor.config.current_lim = 10
+
+
+
+

Current limit

+
+

The default current limit, for safety reasons, is set to 10A. This is quite weak, but good for making sure the drive is stable. +To change the current limit,

+
odrv0.axis0.motor.config.current_lim = val
+
+
+

Once you have tuned the ODrive, you can increase this to 60A to increase performance.

+
+

Note

+

The motor current and the current drawn from the power supply is not the same in general. You should not look at the power supply current to see what is going on with the motor current.*

+
+
+
+
+

Velocity limit

+

The motor will be limited to this speed in [turn/s]. Again the default value is quite slow.

+
odrv0.axis0.controller.config.vel_limit = 2
+
+
+
+
+

Calibration Current

+

You can change odrv0.axis0.motor.config.calibration_current [A] to the largest value you feel comfortable leaving running through the motor continuously when the motor is stationary. +If you are using a small motor (i.e. 15A current rated) you may need to reduce calibration_current to a value smaller than the default.

+
+
+
+

Setting Other Hardware Parameters

+
+

Enable Brake Resistor

+

Set this to True if using a brake resistor. You need to save the configuration and reboot the ODrive for this to take effect.

+
odrv0.config.enable_brake_resistor
+
+
+
+
+

Brake Resistor Value

+

This is the resistance [Ohms] of the brake resistor. You can leave this at the default setting if you are not using a brake resistor.

+
odrv0.config.brake_resistance
+
+
+
+

Note

+

There may be some extra resistance in your wiring and in the screw terminals, so if you are getting issues while braking you may want to increase this parameter by around 0.05 ohm.

+
+
+
+

Negative Current

+

This is the amount of current [Amps] allowed to flow back into the power supply. The convention is that it is negative. By default, it is set to a conservative value of 10mA. If you are using a brake resistor and getting DC_BUS_OVER_REGEN_CURRENT errors, raise it slightly. +If you are not using a brake resistor and you intend to send braking current back to the power supply, set this to a safe level for your power source. Note that in that case, it should be higher than your motor current limit + current limit margin.

+
odrv0.config.dc_max_negative_current
+
+
+
+
+

Pole Pairs

+

This is the number of magnet poles in the rotor, divided by two. To find this, you can simply count the number of permanent magnets in the rotor, if you can see them.

+
odrv0.axis0.motor.config.pole_pairs
+
+
+
+

Note

+

This is not the same as the number of coils in the stator.

+
+

A good way to find the number of pole pairs is with a current limited power supply. Connect any two of the three phases to a power supply outputting around 2A, spin the motor by hand, and count the number of detents. +This will be the number of pole pairs. If you can’t distinguish the detents from the normal cogging present when the motor is disconnected, increase the current. +Another way is sliding a loose magnet in your hand around the rotor, and counting how many times it stops. This will be the number of pole pairs. If you use a ferrous piece of metal instead of a magnet, you will get the number of magnet poles.

+
+
+

Torque Constant

+

This is the ratio of torque produced by the motor per Amp of current delivered to the motor. This should be set to 8.27 / (motor KV).

+
odrv0.axis0.motor.config.torque_constant
+
+
+

If you decide that you would rather command torque in units of Amps, you could simply set the torque constant to 1.

+
+
+

Motor Type

+

This is the type of motor being used. Currently two types of motors are supported: High-current motors (MOTOR_TYPE_HIGH_CURRENT, [0]) and gimbal motors (MOTOR_TYPE_GIMBAL, [2]).

+
odrv0.axis0.motor.config.motor_type
+
+
+
+

Note

+

When using gimbal motors, current_lim and calibration_current actually mean voltage limit and calibration voltage respectively, since we don’t use current feedback. +This means that if you set it to 10, it means 10V, despite the name of the parameter.

+
+
+
+

Motor Thermistor

+

If using motor thermistor please see the thermistors page for setup.

+
+
+
+
+

Encoder Configuration

+
+

With Encoder

+

Set the encoder count per revolution [CPR] value

+
odrv0.axis0.encoder.config.cpr
+
+
+

This is 4x the Pulse Per Revolution (PPR) value. Usually this is indicated in the datasheet of your encoder. +If you purchased an encoder from our shop, you can find the parameters here.

+
+
+

Without Encoder

+ +
+
+
+

Save Configuration

+

You can save all .config parameters to persistent memory so the ODrive remembers them between power cycles. This will reboot the board.

+
odrv0.save_configuration()
+
+
+
+
+

Position control of M0

+

Let’s get motor 0 up and running. The procedure for motor 1 is exactly the same, so feel free to substitute axis1 wherever it says axis0.

+
+
    +
  1. Start the calibratrion sequence by entering

  2. +
+
odrv0.axis0.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE
+
+
+

and press Enter.

+

After about 2 seconds you should hear a beep. Then the motor will turn slowly in one direction for a few seconds, then back in the other direction.

+

The startup procedure is demonstrated here.

+
+

Note

+

The rotor must be allowed to rotate without any biased load during startup. +That means mass and weak friction loads are fine, but gravity or spring loads are not okay. +Also note that in the video, the motors spin after initialization, but in the current software the default behaviour is not like that.

+
+
    +
  1. Start closed loop control by typing

    +
    +
    odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
    +
    +
    +

    and press Enter. From now on the ODrive will try to hold the motor’s position. If you try to turn it by hand, it will fight you gently. That is unless you bump up odrv0.axis0.motor.config.current_lim, in which case it will fight you more fiercely. +If the motor begins to vibrate either immediately or after being disturbed you will need to lower the controller gains.

    +
    +
  2. +
  3. Send the motor a new position setpoint with

    +
    +
    odrv0.axis0.controller.input_pos = 1
    +
    +
    +

    The units are in turns.

    +
    +
  4. +
  5. At this point you will probably want to Properly tune the motor controller in order to maximize system performance.

  6. +
+
+
+

Other Control Modes

+

The default control mode is unfiltered position control in the absolute encoder reference frame. +The ODrive can be used with a variety of control modes, to learn more, check out the control modes documentation page.

+
+
+
+

Watchdog Timer

+

Each axis has a configurable watchdog timer that can stop the motors if the +control connection to the ODrive is interrupted.

+

Each axis has a configurable watchdog timeout: axis.config.watchdog_timeout, +measured in seconds. Set

+
axis.config.enable_watchdog = True
+
+
+

to turn on this feature.

+

The watchdog is fed using the axis.watchdog_feed() method of each axis. +Some ascii commands feed the watchdog automatically.

+
+
+

What’s next?

+

You can now: +* Properly tune the motor controller to unlock the full potential of the ODrive. +* See what other commands and parameters are available, in order to better control the ODrive. +* Control the ODrive from your own program or hook it up to an existing system through one of it’s interfaces. +* See how you can improve the behavior during the startup procedure, like bypassing encoder calibration. +* The CAN communication is the most reliable way of talking to ODrive in a real application. Check out the CAN Guide and CAN Protocol

+

If you have any issues or any questions please get in touch. The ODrive Community warmly welcomes you.

+
+
+

Upgrading from 0.4.12

+

A new version (0.5.4) of ODrive firmware has released, complete with a new odrivetool. +Follow the installation instructions, making sure to add the --upgrade flag to pip commands, and check out the Changelog for changes!

+

The odrivetool will stage and restore your configuration. +This probably isn’t wise for the 0.4.12 -> 0.5.1 upgrade, so we suggest using odrv0.erase_configuration() immediately after connecting the first time.

+
+
+
+
+
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/migration.html b/docs/reStructuredText/_build/html/migration.html new file mode 100644 index 00000000..40e003e9 --- /dev/null +++ b/docs/reStructuredText/_build/html/migration.html @@ -0,0 +1,280 @@ + + + + + + Migration Guide — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Migration Guide

+ +
+

v0.5.1 -> v0.5.2

+

The change from v0.5.1 to v0.5.2 had fewer breaking changes than the v0.4.12 to v0.5.1 change.

+
+
+

GPIO Modes

+

The GPIO configuration is now more explicit. For example, to use gpio1 for for step signals (as part of a step/dir interface), it must be set to

+
odrv0.config.gpio1_mode = GPIO_MODE_DIGITAL
+
+
+
+
+

Braking Behavior

+

Before using the brake resistor, it must be explicitly enabled as follows:

+
odrv0.config.enable_brake_resistor = True
+
+
+

and then save the configuration and reboot for the setting to take effect.

+
+
+

Step/Direction Settings

+

Previously, steps were added incrementally to input_pos. +This caused issues with accumulated floating point rounding error. +Now, an absolute step count is used. +This change requires that the circular setpoints mode is used odrv0.axis0.controller.config.circular_setpoints = True when step/dir signals are used.

+

In addition, odrv0.axis0.config.turns_per_step has been removed and odrv0.axis0.controller.config.steps_per_circular_range is used.

+

For example:

+
+

previously:

+
odrv0.axis0.config.turns_per_step = 1.0/1024.0
+
+
+
+
+

v0.5.2

+
odrv0.axis0.controller.config.circular_setpoints = True
+odrv0.axis0.controller.config.circular_setpoint_range = 1.0
+odrv0.axis0.controller.config.steps_per_circular_range = 1024
+
+
+

For best results, set both the circular range and steps per circular range to powers of 2.

+
+
+
+

API changes

+

For other API changes, see the Changelog file on github.

+
+
+

v0.4.12 -> v0.5.1

+

Certain changes occurred between firmware versions v0.4.12 and v0.5.1 that will break existing configurations. +This document is a guide for how to take a working v0.4.12 ODrive config and change it to work with firmware v0.5.1.

+
+
+

Unit Changes

+

ODrive now uses units of [turns], [turns/s], and [turns/s^2] instead of [counts], [counts/s], and [counts/s^2]. +In addition, the motor controller class now has an input command of torque in [Nm] instead of current in [Amps]. +In general, every user-facing parameter that has to do with position or velocity is affected by the unit change.

+
+

Note

+

For the torque to be in correct in [Nm] you need to configure the motor.config.torque_constant. +See the updated getting started for more details.

+
+
+
+

Control Parameter Names

+

ODrive now uses input_pos, input_vel, and input_torque as commands instead of pos_setpoint, vel_setpoint, and current_setpoint.

+
+
+

Guide

+

For a working v0.4.12 ODrive configuration, use the following equations to convert parameters as required.

+
    +
  • pos_gain is unaffected ( [counts/s / count] *> [turns/s / turns] )

  • +
  • vel_gain is vel_gain_old * torque_constant * encoder cpr

  • +
  • vel_integrator_gain is vel_integrator_gain_old * torque_constant * encoder cpr

  • +
+

For other values, [turns] = [counts] / [encoder cpr]. Converting [counts/s] and [counts/s^2] is similar.

+
+

Affected Variables

+
    +
  • axis.controller.input_pos

  • +
  • axis.controller.input_vel

  • +
  • axis.controller.input_torque

  • +
  • axis.controller.config.vel_limit

  • +
  • axis.controller.config.vel_ramp_rate

  • +
  • axis.controller.config.current_ramp_rate is now axis.controller.config.torque_ramp_rate

  • +
  • axis.controller.config.circular_setpoint_range

  • +
  • axis.controller.config.inertia

  • +
  • axis.controller.config.homing_speed

  • +
  • axis.controller.pos_setpoint

  • +
  • axis.controller.vel_setpoint

  • +
  • axis.controller.torque_setpoint instead of axis.controller.current_setpoint

  • +
  • axis.trap_traj.config.vel_limit

  • +
  • axis.trap_traj.config.accel_limit

  • +
  • axis.trap_traj.config.decel_limit

  • +
  • axis.encoder.pos_estimate

  • +
  • axis.encoder.pos_estimate_circular

  • +
  • axis.encoder.vel_estimate

  • +
  • axis.config.counts_per_step is now turns_per_step for the step/direction interface

  • +
  • axis.sensorless_estimator.vel_estimate is in mechanical [turns/s] instead of electrical [radians/s]

  • +
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/native-protocol.html b/docs/reStructuredText/_build/html/native-protocol.html new file mode 100644 index 00000000..d723d147 --- /dev/null +++ b/docs/reStructuredText/_build/html/native-protocol.html @@ -0,0 +1,171 @@ + + + + + + Native Protocol — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Native Protocol

+

This protocol is what the odrivetool uses to talk to the ODrive. +If you have a choice, this is the recommended protocol for all applications. +The native protocol runs on USB and on UART.

+
+

Python

+

The odrivetool you installed as part of the Getting Started guide comes with a library that you can use to easily control the ODrive from Python.

+

Assuming you already installed the odrive library (pip install odrive), the simplest program to control the ODrive is this:

+
import odrive
+odrv0 = odrive.find_any()
+print(str(odrv0.vbus_voltage))
+
+
+

For a more comprehensive example, see tools/odrive_demo.py.

+
+
+

Other Languages

+

We don’t have an official library for you just yet. Check the community, there might be someone working on it. +If you want to write a library yourself, refer to the native protocol specification. +You are of course welcome to contribute it back.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/objects.inv b/docs/reStructuredText/_build/html/objects.inv new file mode 100644 index 00000000..602f1659 Binary files /dev/null and b/docs/reStructuredText/_build/html/objects.inv differ diff --git a/docs/reStructuredText/_build/html/odrivetool.html b/docs/reStructuredText/_build/html/odrivetool.html new file mode 100644 index 00000000..22a927ef --- /dev/null +++ b/docs/reStructuredText/_build/html/odrivetool.html @@ -0,0 +1,495 @@ + + + + + + odrivetool — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

odrivetool

+ +

The odrivetool is the accompanying PC program for the ODrive. It’s main purpose is to provide an interactive shell to control the device manually, as well as some supporting functions like firmware update.

+
+

Installation

+

Refer to the Getting Started guide.

+

Type odrivetool --help to see what features are available.

+
+
+

Multiple ODrives

+

By default, odrivetool will connect to any ODrive it finds. +If this is not what you want, you can select a specific ODrive.

+

To find the serial number of your ODrive, run odrivetool, connect exactly one ODrive and power it up. +You should see this:

+
Connected to ODrive 306A396A3235 as odrv0
+In [1]:
+
+
+

306A396A3235 is the serial number of this particular ODrive. +If you want odrivetool to ignore all other devices you would close it and then run:

+
odrivetool --serial-number 306A396A3235
+
+
+
+
+

Configuration Backup

+

You can use odrivetool to back up and restore device configurations or transfer the configuration of one ODrive to another one.

+
+
    +
  • To save the configuration to a file on the PC, run

    +
    +
    odrivetool backup-config my_config.json
    +
    +
    +
    +
  • +
  • To restore the configuration form such a file, run

    +
    +
    odrivetool restore-config my_config.json
    +
    +
    +
    +
  • +
+
+
+

Note

+

The encoder offset calibration is not restored because this would be dangerous if you transfer the calibration values of one axis to another axis.

+
+
+
+

Device Firmware Update

+
+

Attention

+

DFU is not supported on ODrive v3.4 or earlier. You need to flash with the external programmer instead.

+
+

To update the ODrive to the newest firmware release, simply open up a terminal and run the following command:

+
odrivetool dfu
+
+
+

You should then see

+
ODrive control utility v0.3.7.dev
+Waiting for ODrive...
+Found ODrive 308039673235 (v3.5-24V) with firmware v0.3.7-dev
+Checking online for newest firmware... found v0.3.7
+Downloading firmware...
+Putting device 308039673235 into DFU mode...
+Erasing... done
+Flashing... done
+Verifying... done
+
+
+
+

Note

+

This command will connect to GitHub servers to retrieve the latest firmware.

+
+

If you have a non-default configuration saved on the device, odrivetool will try to carry over the configuration across the firmware update. +If any of the settings are removed or renamed, you will get warning messages.

+
+

Flashing Custom Firmware

+

If you want to flash a specific firmware file instead of automatically downloading one, you can run

+
odrivetool dfu path/to/firmware/file.hex
+
+
+

You can download one of the officially released firmware files from here. +.. You will need one of the .hex files (not the .elf`file). +On Windows you will need one of the :code:.hex` files, and for Linux and Mac you will want the .elf file. +Make sure you select the file that matches your board version.

+

To compile firmware from source, refer to the developer guide.

+
+
+

Troubleshooting

+
+

During the update, a new device called “STM32 BOOTLOADER” will appear. +Open the Zadig utility and set the driver for “STM32 BOOTLOADER” to libusb-win32. +After that the firmware update will continue.

+
+

On some machines you will need to unplug and plug back in the USB cable to make the PC understand that we switched from regular mode to bootloader mode.

+

Forcing DFU Mode

+

If the DFU script can’t find the device, try forcing it into DFU mode:

+
+

Flick the DIP switch that says “DFU, RUN” to “DFU” and power cycle the board. +If that alone doesn’t work, also connect the pin “GPIO6” to “GND”. +After you’re done upgrading firmware, don’t forget to put the switch back into the “RUN” position and power cycle the board again.

+
+
+
+

Alternative DFU Tool

+

Some people have had issues using the python dfu tool, so below is a guide on how to manually use different tools.

+

Before starting the below steps, you need to get firmware binary. +You can download one of the officially released firmware files from here. +Make sure you select the file that matches your board version. +On Windows you will need one of the .hex files, and for Linux and Mac you will want the .elf file.

+

To compile firmware from source, refer to the developer guide.

+

Multi-platform

+

ST has a tool called STM32CubeProgrammer.

+
    +
  1. Download the tool here. +You will need to make an account with ST to download the tool.

  2. +
  3. Install the tool. On Windows, make sure to let it make a desktop shortcut.

  4. +
  5. Force the ODrive into DFU mode.

  6. +
  7. Launch STM32CubeProgrammer.

  8. +
  9. Under “Memory & File edition”, there are two tabs called “Device memory” and “Open file”. +Click “Open file” and choose the ODrive firmware hex file that you downloaded or compiled.

  10. +
  11. In the top right, there is a dropdown menu containing the different methods to connect to an STM32 device. Choose “USB”.

  12. +
  13. Under “USB configuration”, a USB port should be automatically selected and the ODrive serial number should be present next to “Serial number.”

  14. +
  15. Click “Connect” above “USB configuration”.

  16. +
  17. Click the tab with the name of your firmware file (example: ODriveFirmware_v3.6-56V.hex) if it is not already selected.

  18. +
  19. Click “Download” to flash your ODrive with the firmware. Your ODrive is now flashed!

  20. +
  21. Close STM32CubeProgrammer.

  22. +
  23. Turn off the power to the ODrive and set the DIP swtich back to RUN mode.

  24. +
+
+

You can use the DfuSe app from ST.

+
    +
  1. Download the tool here. +Unfortunately they make you create a login to download. Sorry about that.

  2. +
  3. After installing the tool, launch DfuFileMgr.exe which probably got added to the start menu as “Dfu file manager”.

  4. +
  5. Select “I want to GENERATE a DFU file from S19, HEX or BIN files”, press OK.

  6. +
  7. Click the button that says “S19 or Hex…”, find the ODriveFirmware.hex file you built or downloaded.

  8. +
  9. Leave all the other settings as default and click the “Generate…” button.

  10. +
  11. Save the output file as ODriveFirmware.dfu. Note that the success message has a warning sign for some reason…

  12. +
  13. Launch DfuSeDemo.exe which probably got added to the start menu as “DfuSeDemo”.

  14. +
  15. Force the ODrive into DFU mode, as per the instructions above “How to force DFU mode”.

  16. +
  17. In the top left it should now be connected to “STM Device in DFU Mode”.

  18. +
  19. If it doesn’t appear, it may be because the driver is set to libusb by Zadig. We need to set it back to the original driver. +Follow these instructions.

  20. +
  21. If, after doing the above step, the ODrive still installs itself as a libusb device in Device Manager, you can try to delete the libusb driver (this is OK, since we can use Zadig to install it again). +You can simply delete the file C:\Windows\System32\drivers\libusb0.sys.

  22. +
  23. In the bottom right section called “Upgrade or Verify Action” click the button “Choose…”.

  24. +
  25. Locate the ODriveFirmware.dfu we made before.

  26. +
  27. Click button “Upgrade”.

  28. +
  29. If you get a warning that it’s not possible to check that it’s the correct device type: click yes to continue.

  30. +
  31. Congratulations your ODrive should now be flashed; you can now quit DfuSeDemo.

  32. +
  33. Turn off the power to the ODrive and set the DIP switch back to RUN mode.

  34. +
+
+
+ +
+
+

Liveplotter

+

Liveplotter is used for the graphical plotting of odrive parameters (i.e. position) in real time. +To start liveplotter, close any other instances of liveplotter and run

+
odrivetool liveplotter
+
+
+

from a new anaconda prompt window. By default two parameters are plotted on startup; the encoder position of axis 1 and axis 2. +In the below example the motors are running in closed_loop_control while they are being forced off position by hand.

+
+_images/liveplotter-pos-estimate.png +
+

To change what parameters are plotted open odrivetool (located in Anaconda3\Scripts or ODrive-master\tools) with a text editor and modify the liveplotter function:

+
# If you want to plot different values, change them here.
+# You can plot any number of values concurrently.
+cancellation_token = start_liveplotter(lambda: [
+    odrv0.axis0.encoder.pos_estimate,
+    odrv0.axis1.encoder.pos_estimate,
+])
+
+
+

For example, to plot the approximate motor torque [Nm] and the velocity [RPM] of axis0, you would modify the function to read:

+
# If you want to plot different values, change them here.
+# You can plot any number of values concurrently.
+cancellation_token = start_liveplotter(lambda: [
+    ((odrv0.axis0.encoder.vel_estimate*60), # turns/s to rpm
+    ((odrv0.axis0.motor.current_control.Iq_setpoint * my_odrive.axis0.motor.config.torque_constant), # Torque [Nm]
+])
+
+
+

In the example below the motor is forced off axis by hand and held there. +In response the motor controller increases the torque (orange line) to counteract this disturbance up to a peak of 500 N.cm at which point the motor current limit is reached. +When the motor is released it returns back to its commanded position very quickly as can be seen by the spike in the motor velocity (blue line).

+
+_images/liveplotter-iq-omega.png +

Liveplotter Torque Velocity Plot

+
+

To change the scale and sample rate of the plot modify the following parameters located at the beginning of utils.py (located in Anaconda3\Lib\site-packages\odrive):

+
data_rate = 100
+plot_rate = 10
+num_samples = 1000
+
+
+

For more examples on how to interact with the plotting functionality refer to these Matplotlib examples.

+
+

Liveplotter from Interactive odrivetool Instance

+

You can also run start_liveplotter(...) directly from the interactive odrivetool prompt. +This is useful if you want to issue commands or otherwise keep interacting with the odrive while plotting.

+

For example you can type the following directly into the interactive prompt:

+
start_liveplotter(lambda: [odrv0.axis0.encoder.pos_estimate])
+
+
+

Just like the examples above, you can list several parameters to plot separated by comma in the square brackets. +In general, you can plot any variable that you are able to read like normal in odrivetool.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/pinout.html b/docs/reStructuredText/_build/html/pinout.html new file mode 100644 index 00000000..9504d253 --- /dev/null +++ b/docs/reStructuredText/_build/html/pinout.html @@ -0,0 +1,428 @@ + + + + + + ODrive v3.x Pinout — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

ODrive v3.x Pinout

+
+ +
+ + ++++++++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Table Title

#

Label

GPIO_MODE_DIGITAL

GPIO_MODE_ANALOG_IN

GPIO_MODE_UART_A

GPIO_MODE_UART_B

GPIO_MODE_PWM

GPIO_MODE_CAN_A

GPIO_MODE_I2C_A

GPIO_MODE_ENC0

GPIO_MODE_ENC1

GPIO_MODE_MECH_BRAKE

0

not a pin

1

GPIO1 (+)

general purpose

analog input

UART_A.TX

PWM0.0

mechanical brake

2

GPIO2 (+)

general purpose

analog input

UART_A.RX

PWM0.1

mechanical brake

3

GPIO3

general purpose

analog input

UART_B.TX

PWM0.2

mechanical brake

4

GPIO4

general purpose

analog input

UART_B.RX

PWM0.3

mechanical brake

5

GPIO5

general purpose

analog input (*)

mechanical brake

6

GPIO6 (*) (+)

general purpose

mechanical brake

7

GPIO7 (*) (+)

general purpose

mechanical brake

8

GPIO8 (*) (+)

general purpose

mechanical brake

9

M0.A

general purpose

ENC0.A

10

M0.B

general purpose

ENC0.B

11

M0.Z

general purpose

12

M1.A

general purpose

I2C.SCL

ENC1.A

13

M1.B

general purpose

I2C.SDA

ENC1.B

14

M1.Z

general purpose

15

not exposed

general purpose

CAN_A.RX

I2C.SCL

16

not exposed

general purpose

CAN_A.TX

I2C.SDA

+

key:

+
    +
  • (*) ODrive v3.5 and later.

  • +
  • (+) On ODrive v3.5 and later these pins have noise suppression filters. This is useful for step/dir input.

  • +
+
+

Notes

+
    +
  • Changes to the pin configuration only take effect after odrv0.save_configuration() and odrv0.reboot()

  • +
  • Bold font marks the default configuration.

  • +
  • If a GPIO is set to an unsupported mode it will be left uninitialized.

  • +
  • When setting a GPIO to a special purpose mode (e.g. GPIO_MODE_UART_A) you must also enable the corresponding feature (e.g. <odrv>.config.enable_uart_a).

  • +
  • Digital mode is a general purpose mode that can be used for these functions: step, dir, enable, encoder index, hall effect encoder, SPI encoder nCS.

  • +
  • You must also connect GND between ODrive and your other board.

  • +
  • ODrive v3.3 and onward have 5V tolerant GPIO pins.

  • +
  • Simultaneous operation of UART_A and UART_B is currently not supported.

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/protocol.html b/docs/reStructuredText/_build/html/protocol.html new file mode 100644 index 00000000..bd5cea57 --- /dev/null +++ b/docs/reStructuredText/_build/html/protocol.html @@ -0,0 +1,308 @@ + + + + + + ODrive Communication Protocol — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

ODrive Communication Protocol

+ +

Communicating with an ODrive consists of a series of endpoint operations. +An endpoint can theoretically be any kind data serialized in any way. +There is a default serialization implementation for POD types; for custom types +you must (de)serialize yourself. In the future we may provide a default serializer +for structs. +The available endpoints can be enumerated by reading the JSON from endpoint 0 +and can theoretically be different for each communication interface (they are not in practice).

+

Each endpoint operation can send bytes to one endpoint (referenced by its ID) +and at the same time receive bytes from the same endpoint. The semantics of +these payloads are specific to each endpoint’s type, the name of which is +indicated in the JSON.

+

For instance an int32 endpoint’s input and output is a 4 byte little endian +representation. In general the convention for combined read/write requests is +exchange, i.e. the returned value is the old value. Custom endpoint handlers +may be non-compliant.

+

There is a packet based version and a stream based variant of the protocol. Each +variant is employed as appropriate. For instance USB runs the packet based variant +by default while UART runs the stream based variant.

+
+

Packet Format

+

We will call the ODrive “server” and the PC “client”. A request is a message +from the PC to the ODrive and a response is a message from the ODrive to the +PC.

+

Each request-response transaction corresponds to a single endpoint operation.

+
+

Request

+
+
    +
  • +
    Bytes 0, 1 Sequence number, MSB = 0
      +
    • Currently the server does not care about ordering and does not filter resent messages.

    • +
    +
    +
    +
  • +
  • +
    Bytes 2, 3 Endpoint ID
      +
    • The IDs of all endpoints can be obtained from the JSON definition. The JSON definition can be obtained by reading from endpoint 0. +If (and only if) the MSB is set to 1 the client expects a response for this request.

    • +
    +
    +
    +
  • +
  • +
    Bytes 4, 5 Expected response size
      +
    • The number of bytes that should be returned to the client. If the client doesn’t need any response data, it can set this value to 0. The operation will still be acknowledged if the +MSB in EndpointID is set.

    • +
    +
    +
    +
  • +
  • +
    Bytes 6 to N-3 Payload
      +
    • The length of the payload is determined by the total packet size. The format of the payload depends on the endpoint type. The endpoint type can be obtained from the JSON definition.

    • +
    +
    +
    +
  • +
  • +
    Bytes N-2, N-1
      +
    • For endpoint 0: Protocol version (currently 1). A server shall ignore packets with other values.

    • +
    • For all other endpoints: The CRC16 calculated over the JSON definition using the algorithm described below, except that the initial value is set to the protocol version (currently 1). A server shall ignore packets that set this field incorrectly.

    • +
    +
    +
    +
  • +
+
+
+
+

Response

+
+
    +
  • +
    Bytes 0, 1 Sequence number, MSB = 1
      +
    • The sequence number of the request to which this is the response.

    • +
    +
    +
    +
  • +
  • +
    Bytes 2, 3 Payload
      +
    • The length of the payload tends to be equal to the number of expected bytes as indicated +in the request. The server must not expect the client to accept more bytes than it requested.

    • +
    +
    +
    +
  • +
+
+
+
+
+

Stream Format

+

The stream based format is just a wrapper for the packet format.

+
+
    +
  • Byte 0 Sync byte 0xAA

  • +
  • +
    Byte 1 Packet length
      +
    • Currently both parties shall only emit and accept values of 0 through 127.

    • +
    +
    +
    +
  • +
  • Byte 2 CRC8 of bytes 0 and 1 (see below for details)

  • +
  • Bytes 3 to N-3 Packet

  • +
  • Bytes N-2, N-1 CRC16 (see below for details)

  • +
+
+
+
+

CRC Algorithms

+
+

CRC8

+
+
    +
  • Polynomial: 0x37

  • +
  • Initial value: 0x42

  • +
  • No input reflection, no result reflection, no final XOR operation

  • +
  • Examples: +* 0x01, 0x02, 0x03, 0x04 => 0x61 +* 0x05, 0x04, 0x03, 0x02, 0x01 => 0x64

  • +
+
+
+
+

CRC16

+
+
    +
  • Polynomial: 0x3d65

  • +
  • Initial value: 0x1337 (or 0x0001 for the JSON CRC)

  • +
  • No input reflection, no result reflection, no final XOR operation

  • +
  • Examples: +* 0x01, 0x02, 0x03, 0x04 => 0x672E +* 0x05, 0x04, 0x03, 0x02, 0x01 => 0xE251

  • +
+
+

You can use this online calculator to verify your implementation.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/rc-pwm.html b/docs/reStructuredText/_build/html/rc-pwm.html new file mode 100644 index 00000000..88e50162 --- /dev/null +++ b/docs/reStructuredText/_build/html/rc-pwm.html @@ -0,0 +1,185 @@ + + + + + + RC PWM input — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

RC PWM input

+

You can control the ODrive directly from a hobby RC receiver.

+

Any of the numerical parameters that are writable from the ODrive Tool can be hooked up to a PWM input. +The Pinout tells you which pins are PWM input capable. As an example, we’ll configure GPIO4 to control the angle of axis 0. +We want the axis to move within a range of -2 to 2 turns.

+
    +
  1. Make sure you’re able control the axis 0 angle by writing to odrv0.axis0.controller.input_pos. +If you need help with this follow the getting started guide.

  2. +
  3. If you want to control your ODrive with the PWM input without using anything else to activate the ODrive, you can configure the ODrive such that axis 0 automatically goes operational at startup. +See here for more information.

  4. +
  5. In ODrive Tool, configure the PWM input mapping

    +
    +
    odrv0.config.gpio4_mode = GPIO_MODE_PWM
    +odrv0.config.gpio4_pwm_mapping.min = -2
    +odrv0.config.gpio4_pwm_mapping.max = 2
    +odrv0.config.gpio4_pwm_mapping.endpoint = odrv0.axis0.controller._input_pos_property
    +
    +
    +
    +
    +

    Note

    +

    you can disable the input by setting odrv0.config.gpio4_pwm_mapping.endpoint = None

    +
    +
  6. +
  7. Save the configuration and reboot

    +
    +
    odrv0.save_configuration()
    +odrv0.reboot()
    +
    +
    +
    +
  8. +
  9. With the ODrive powered off, connect the RC receiver ground to the ODrive’s GND and one of the RC receiver signals to GPIO4. +You may try to power the receiver from the ODrive’s 5V supply if it doesn’t draw too much power. Power up the the RC transmitter. +You should now be able to control axis 0 from one of the RC sticks.

  10. +
+

Be sure to setup the Failsafe feature on your RC Receiver so that if connection is lost between the remote and the receiver, the receiver outputs 0 for the velocity setpoint of both axes (or whatever is safest for your configuration). +Also note that if the receiver turns off (loss of power, etc) or if the signal from the receiver to the ODrive is lost (wire comes unplugged, etc), the ODrive will continue the last commanded velocity setpoint. +There is currently no timeout function in the ODrive for PWM inputs.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/search.html b/docs/reStructuredText/_build/html/search.html new file mode 100644 index 00000000..50e6baa3 --- /dev/null +++ b/docs/reStructuredText/_build/html/search.html @@ -0,0 +1,160 @@ + + + + + + Search — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Search
  • +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2021, ODrive Robotics.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/searchindex.js b/docs/reStructuredText/_build/html/searchindex.js new file mode 100644 index 00000000..a248686a --- /dev/null +++ b/docs/reStructuredText/_build/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["analog-input","ascii-protocol","can-guide","can-protocol","commands","configuring-eclipse","configuring-vscode","control","control-modes","developer-guide","encoders","endstops","fibre_types/com_odriverobotics_ODrive","getting-started","ground-loops","hoverboard","index","migration","native-protocol","odrivetool","pinout","protocol","rc-pwm","specifications","step-direction","testing","thermistors","troubleshooting","uart","usb"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,sphinx:56},filenames:["analog-input.rst","ascii-protocol.rst","can-guide.rst","can-protocol.rst","commands.rst","configuring-eclipse.rst","configuring-vscode.rst","control.rst","control-modes.rst","developer-guide.rst","encoders.rst","endstops.rst","fibre_types\\com_odriverobotics_ODrive.rst","getting-started.rst","ground-loops.rst","hoverboard.rst","index.rst","migration.rst","native-protocol.rst","odrivetool.rst","pinout.rst","protocol.rst","rc-pwm.rst","specifications.rst","step-direction.rst","testing.rst","thermistors.rst","troubleshooting.rst","uart.rst","usb.rst"],objects:{"":{ODrive:[12,0,1,""]},"ODrive.AcimEstimator":{Config:[12,0,1,""],config:[12,1,1,""],phase_offset:[12,1,1,""],rotor_flux:[12,1,1,""],slip_vel:[12,1,1,""],stator_phase:[12,1,1,""],stator_phase_vel:[12,1,1,""]},"ODrive.AcimEstimator.Config":{slip_velocity:[12,1,1,""]},"ODrive.Axis":{AxisState:[12,0,1,""],CanConfig:[12,0,1,""],Config:[12,0,1,""],Error:[12,0,1,""],LockinConfig:[12,0,1,""],TaskTimes:[12,0,1,""],acim_estimator:[12,1,1,""],config:[12,1,1,""],controller:[12,1,1,""],current_state:[12,1,1,""],encoder:[12,1,1,""],error:[12,1,1,""],is_homed:[12,1,1,""],last_drv_fault:[12,1,1,""],max_endstop:[12,1,1,""],mechanical_brake:[12,1,1,""],min_endstop:[12,1,1,""],motor:[12,1,1,""],requested_state:[12,1,1,""],sensorless_estimator:[12,1,1,""],step_dir_active:[12,1,1,""],steps:[12,1,1,""],task_times:[12,1,1,""],trap_traj:[12,1,1,""],watchdog_feed:[12,2,1,""]},"ODrive.Axis.AxisState":{CLOSED_LOOP_CONTROL:[12,1,1,""],ENCODER_DIR_FIND:[12,1,1,""],ENCODER_HALL_PHASE_CALIBRATION:[12,1,1,""],ENCODER_HALL_POLARITY_CALIBRATION:[12,1,1,""],ENCODER_INDEX_SEARCH:[12,1,1,""],ENCODER_OFFSET_CALIBRATION:[12,1,1,""],FULL_CALIBRATION_SEQUENCE:[12,1,1,""],HOMING:[12,1,1,""],IDLE:[12,1,1,""],LOCKIN_SPIN:[12,1,1,""],MOTOR_CALIBRATION:[12,1,1,""],STARTUP_SEQUENCE:[12,1,1,""],UNDEFINED:[12,1,1,""]},"ODrive.Axis.CanConfig":{encoder_rate_ms:[12,1,1,""],heartbeat_rate_ms:[12,1,1,""],is_extended:[12,1,1,""],node_id:[12,1,1,""]},"ODrive.Axis.Config":{CalibrationLockin:[12,0,1,""],calibration_lockin:[12,1,1,""],can:[12,1,1,""],dir_gpio_pin:[12,1,1,""],enable_sensorless_mode:[12,1,1,""],enable_step_dir:[12,1,1,""],enable_watchdog:[12,1,1,""],general_lockin:[12,1,1,""],sensorless_ramp:[12,1,1,""],startup_closed_loop_control:[12,1,1,""],startup_encoder_index_search:[12,1,1,""],startup_encoder_offset_calibration:[12,1,1,""],startup_homing:[12,1,1,""],startup_motor_calibration:[12,1,1,""],step_dir_always_on:[12,1,1,""],step_gpio_pin:[12,1,1,""],watchdog_timeout:[12,1,1,""]},"ODrive.Axis.Config.CalibrationLockin":{accel:[12,1,1,""],current:[12,1,1,""],ramp_distance:[12,1,1,""],ramp_time:[12,1,1,""],vel:[12,1,1,""]},"ODrive.Axis.Error":{ESTOP_REQUESTED:[12,1,1,""],HOMING_WITHOUT_ENDSTOP:[12,1,1,""],INVALID_STATE:[12,1,1,""],MAX_ENDSTOP_PRESSED:[12,1,1,""],MIN_ENDSTOP_PRESSED:[12,1,1,""],OVER_TEMP:[12,1,1,""],UNKNOWN_POSITION:[12,1,1,""],WATCHDOG_TIMER_EXPIRED:[12,1,1,""]},"ODrive.Axis.LockinConfig":{accel:[12,1,1,""],current:[12,1,1,""],finish_distance:[12,1,1,""],finish_on_distance:[12,1,1,""],finish_on_enc_idx:[12,1,1,""],finish_on_vel:[12,1,1,""],ramp_distance:[12,1,1,""],ramp_time:[12,1,1,""],vel:[12,1,1,""]},"ODrive.Axis.TaskTimes":{acim_estimator_update:[12,1,1,""],can_heartbeat:[12,1,1,""],controller_update:[12,1,1,""],current_controller_update:[12,1,1,""],current_sense:[12,1,1,""],dc_calib:[12,1,1,""],encoder_update:[12,1,1,""],endstop_update:[12,1,1,""],motor_update:[12,1,1,""],open_loop_controller_update:[12,1,1,""],pwm_update:[12,1,1,""],sensorless_estimator_update:[12,1,1,""],thermistor_update:[12,1,1,""]},"ODrive.Can":{Config:[12,0,1,""],Error:[12,0,1,""],Protocol:[12,0,1,""],config:[12,1,1,""],error:[12,1,1,""]},"ODrive.Can.Config":{baud_rate:[12,1,1,""],protocol:[12,1,1,""]},"ODrive.Can.Error":{DUPLICATE_CAN_IDS:[12,1,1,""]},"ODrive.Can.Protocol":{SIMPLE:[12,1,1,""]},"ODrive.Config":{brake_resistance:[12,1,1,""],dc_bus_overvoltage_ramp_end:[12,1,1,""],dc_bus_overvoltage_ramp_start:[12,1,1,""],dc_bus_overvoltage_trip_level:[12,1,1,""],dc_bus_undervoltage_trip_level:[12,1,1,""],dc_max_negative_current:[12,1,1,""],dc_max_positive_current:[12,1,1,""],enable_brake_resistor:[12,1,1,""],enable_can_a:[12,1,1,""],enable_dc_bus_overvoltage_ramp:[12,1,1,""],enable_i2c_a:[12,1,1,""],enable_uart_a:[12,1,1,""],enable_uart_b:[12,1,1,""],enable_uart_c:[12,1,1,""],error_gpio_pin:[12,1,1,""],gpio3_analog_mapping:[12,1,1,""],gpio4_analog_mapping:[12,1,1,""],max_regen_current:[12,1,1,""],uart0_protocol:[12,1,1,""],uart1_protocol:[12,1,1,""],uart2_protocol:[12,1,1,""],uart_a_baudrate:[12,1,1,""],uart_b_baudrate:[12,1,1,""],uart_c_baudrate:[12,1,1,""],usb_cdc_protocol:[12,1,1,""]},"ODrive.Controller":{Autotuning:[12,0,1,""],Config:[12,0,1,""],ControlMode:[12,0,1,""],Error:[12,0,1,""],InputMode:[12,0,1,""],anticogging_valid:[12,1,1,""],autotuning:[12,1,1,""],autotuning_phase:[12,1,1,""],config:[12,1,1,""],electrical_power:[12,1,1,""],error:[12,1,1,""],input_pos:[12,1,1,""],input_torque:[12,1,1,""],input_vel:[12,1,1,""],last_error_time:[12,1,1,""],mechanical_power:[12,1,1,""],move_incremental:[12,2,1,""],pos_setpoint:[12,1,1,""],start_anticogging_calibration:[12,2,1,""],torque_setpoint:[12,1,1,""],trajectory_done:[12,1,1,""],vel_integrator_torque:[12,1,1,""],vel_setpoint:[12,1,1,""]},"ODrive.Controller.Autotuning":{frequency:[12,1,1,""],pos_amplitude:[12,1,1,""],torque_amplitude:[12,1,1,""],vel_amplitude:[12,1,1,""]},"ODrive.Controller.Config":{Anticogging:[12,0,1,""],anticogging:[12,1,1,""],axis_to_mirror:[12,1,1,""],circular_setpoint_range:[12,1,1,""],circular_setpoints:[12,1,1,""],control_mode:[12,1,1,""],electrical_power_bandwidth:[12,1,1,""],enable_gain_scheduling:[12,1,1,""],enable_overspeed_error:[12,1,1,""],enable_torque_mode_vel_limit:[12,1,1,""],enable_vel_limit:[12,1,1,""],gain_scheduling_width:[12,1,1,""],homing_speed:[12,1,1,""],inertia:[12,1,1,""],input_filter_bandwidth:[12,1,1,""],input_mode:[12,1,1,""],load_encoder_axis:[12,1,1,""],mechanical_power_bandwidth:[12,1,1,""],mirror_ratio:[12,1,1,""],pos_gain:[12,1,1,""],spinout_electrical_power_threshold:[12,1,1,""],spinout_mechanical_power_threshold:[12,1,1,""],steps_per_circular_range:[12,1,1,""],torque_mirror_ratio:[12,1,1,""],torque_ramp_rate:[12,1,1,""],vel_gain:[12,1,1,""],vel_integrator_gain:[12,1,1,""],vel_integrator_limit:[12,1,1,""],vel_limit:[12,1,1,""],vel_limit_tolerance:[12,1,1,""],vel_ramp_rate:[12,1,1,""]},"ODrive.Controller.Config.Anticogging":{anticogging_enabled:[12,1,1,""],calib_anticogging:[12,1,1,""],calib_pos_threshold:[12,1,1,""],calib_vel_threshold:[12,1,1,""],cogging_ratio:[12,1,1,""],index:[12,1,1,""],pre_calibrated:[12,1,1,""]},"ODrive.Controller.ControlMode":{POSITION_CONTROL:[12,1,1,""],TORQUE_CONTROL:[12,1,1,""],VELOCITY_CONTROL:[12,1,1,""],VOLTAGE_CONTROL:[12,1,1,""]},"ODrive.Controller.Error":{INVALID_CIRCULAR_RANGE:[12,1,1,""],INVALID_ESTIMATE:[12,1,1,""],INVALID_INPUT_MODE:[12,1,1,""],INVALID_LOAD_ENCODER:[12,1,1,""],INVALID_MIRROR_AXIS:[12,1,1,""],OVERSPEED:[12,1,1,""],SPINOUT_DETECTED:[12,1,1,""],UNSTABLE_GAIN:[12,1,1,""]},"ODrive.Controller.InputMode":{INACTIVE:[12,1,1,""],MIRROR:[12,1,1,""],MIX_CHANNELS:[12,1,1,""],PASSTHROUGH:[12,1,1,""],POS_FILTER:[12,1,1,""],TORQUE_RAMP:[12,1,1,""],TRAP_TRAJ:[12,1,1,""],Tuning:[12,1,1,""],VEL_RAMP:[12,1,1,""]},"ODrive.Encoder":{Config:[12,0,1,""],Error:[12,0,1,""],Mode:[12,0,1,""],calib_scan_response:[12,1,1,""],config:[12,1,1,""],count_in_cpr:[12,1,1,""],delta_pos_cpr_counts:[12,1,1,""],error:[12,1,1,""],hall_state:[12,1,1,""],index_found:[12,1,1,""],interpolation:[12,1,1,""],is_ready:[12,1,1,""],phase:[12,1,1,""],pos_abs:[12,1,1,""],pos_circular:[12,1,1,""],pos_cpr_counts:[12,1,1,""],pos_estimate:[12,1,1,""],pos_estimate_counts:[12,1,1,""],set_linear_count:[12,2,1,""],shadow_count:[12,1,1,""],spi_error_rate:[12,1,1,""],vel_estimate:[12,1,1,""],vel_estimate_counts:[12,1,1,""]},"ODrive.Encoder.Config":{abs_spi_cs_gpio_pin:[12,1,1,""],bandwidth:[12,1,1,""],calib_range:[12,1,1,""],calib_scan_distance:[12,1,1,""],calib_scan_omega:[12,1,1,""],cpr:[12,1,1,""],direction:[12,1,1,""],enable_phase_interpolation:[12,1,1,""],find_idx_on_lockin_only:[12,1,1,""],hall_polarity:[12,1,1,""],hall_polarity_calibrated:[12,1,1,""],ignore_illegal_hall_state:[12,1,1,""],index_offset:[12,1,1,""],mode:[12,1,1,""],phase_offset:[12,1,1,""],phase_offset_float:[12,1,1,""],pre_calibrated:[12,1,1,""],sincos_gpio_pin_cos:[12,1,1,""],sincos_gpio_pin_sin:[12,1,1,""],use_index:[12,1,1,""],use_index_offset:[12,1,1,""]},"ODrive.Encoder.Error":{ABS_SPI_COM_FAIL:[12,1,1,""],ABS_SPI_NOT_READY:[12,1,1,""],ABS_SPI_TIMEOUT:[12,1,1,""],CPR_POLEPAIRS_MISMATCH:[12,1,1,""],HALL_NOT_CALIBRATED_YET:[12,1,1,""],ILLEGAL_HALL_STATE:[12,1,1,""],INDEX_NOT_FOUND_YET:[12,1,1,""],NO_RESPONSE:[12,1,1,""],UNSTABLE_GAIN:[12,1,1,""],UNSUPPORTED_ENCODER_MODE:[12,1,1,""]},"ODrive.Encoder.Mode":{HALL:[12,1,1,""],INCREMENTAL:[12,1,1,""],SINCOS:[12,1,1,""],SPI_ABS_AEAT:[12,1,1,""],SPI_ABS_AMS:[12,1,1,""],SPI_ABS_CUI:[12,1,1,""],SPI_ABS_MA732:[12,1,1,""],SPI_ABS_RLS:[12,1,1,""]},"ODrive.Endpoint":{endpoint:[12,1,1,""],max:[12,1,1,""],min:[12,1,1,""]},"ODrive.Endstop":{Config:[12,0,1,""],config:[12,1,1,""],endstop_state:[12,1,1,""]},"ODrive.Endstop.Config":{debounce_ms:[12,1,1,""],enabled:[12,1,1,""],gpio_num:[12,1,1,""],is_active_high:[12,1,1,""],offset:[12,1,1,""]},"ODrive.Error":{BRAKE_DEADTIME_VIOLATION:[12,1,1,""],BRAKE_DUTY_CYCLE_NAN:[12,1,1,""],CONTROL_ITERATION_MISSED:[12,1,1,""],DC_BUS_OVER_CURRENT:[12,1,1,""],DC_BUS_OVER_REGEN_CURRENT:[12,1,1,""],DC_BUS_OVER_VOLTAGE:[12,1,1,""],DC_BUS_UNDER_VOLTAGE:[12,1,1,""],INVALID_BRAKE_RESISTANCE:[12,1,1,""]},"ODrive.GpioMode":{ANALOG_IN:[12,1,1,""],CAN_A:[12,1,1,""],DIGITAL:[12,1,1,""],DIGITAL_PULL_DOWN:[12,1,1,""],DIGITAL_PULL_UP:[12,1,1,""],ENC0:[12,1,1,""],ENC1:[12,1,1,""],ENC2:[12,1,1,""],I2C_A:[12,1,1,""],MECH_BRAKE:[12,1,1,""],PWM:[12,1,1,""],SPI_A:[12,1,1,""],STATUS:[12,1,1,""],UART_A:[12,1,1,""],UART_B:[12,1,1,""],UART_C:[12,1,1,""]},"ODrive.MechanicalBrake":{Config:[12,0,1,""],config:[12,1,1,""],engage:[12,2,1,""],release:[12,2,1,""]},"ODrive.MechanicalBrake.Config":{gpio_num:[12,1,1,""],is_active_low:[12,1,1,""]},"ODrive.Motor":{Config:[12,0,1,""],CurrentControl:[12,0,1,""],DC_calib_phA:[12,1,1,""],DC_calib_phB:[12,1,1,""],DC_calib_phC:[12,1,1,""],Error:[12,0,1,""],I_bus:[12,1,1,""],MotorType:[12,0,1,""],config:[12,1,1,""],current_control:[12,1,1,""],current_meas_phA:[12,1,1,""],current_meas_phB:[12,1,1,""],current_meas_phC:[12,1,1,""],effective_current_lim:[12,1,1,""],error:[12,1,1,""],fet_thermistor:[12,1,1,""],is_armed:[12,1,1,""],is_calibrated:[12,1,1,""],last_error_time:[12,1,1,""],max_allowed_current:[12,1,1,""],max_dc_calib:[12,1,1,""],motor_thermistor:[12,1,1,""],n_evt_current_measurement:[12,1,1,""],n_evt_pwm_update:[12,1,1,""],phase_current_rev_gain:[12,1,1,""]},"ODrive.Motor.Config":{I_bus_hard_max:[12,1,1,""],I_bus_hard_min:[12,1,1,""],I_leak_max:[12,1,1,""],R_wL_FF_enable:[12,1,1,""],acim_autoflux_attack_gain:[12,1,1,""],acim_autoflux_decay_gain:[12,1,1,""],acim_autoflux_enable:[12,1,1,""],acim_autoflux_min_Id:[12,1,1,""],acim_gain_min_flux:[12,1,1,""],bEMF_FF_enable:[12,1,1,""],calibration_current:[12,1,1,""],current_control_bandwidth:[12,1,1,""],current_lim:[12,1,1,""],current_lim_margin:[12,1,1,""],dc_calib_tau:[12,1,1,""],inverter_temp_limit_lower:[12,1,1,""],inverter_temp_limit_upper:[12,1,1,""],motor_type:[12,1,1,""],phase_inductance:[12,1,1,""],phase_resistance:[12,1,1,""],pole_pairs:[12,1,1,""],pre_calibrated:[12,1,1,""],requested_current_range:[12,1,1,""],resistance_calib_max_voltage:[12,1,1,""],torque_constant:[12,1,1,""],torque_lim:[12,1,1,""]},"ODrive.Motor.CurrentControl":{I_measured_report_filter_k:[12,1,1,""],Ialpha_measured:[12,1,1,""],Ibeta_measured:[12,1,1,""],Id_measured:[12,1,1,""],Id_setpoint:[12,1,1,""],Iq_measured:[12,1,1,""],Iq_setpoint:[12,1,1,""],Vd_setpoint:[12,1,1,""],Vq_setpoint:[12,1,1,""],final_v_alpha:[12,1,1,""],final_v_beta:[12,1,1,""],i_gain:[12,1,1,""],p_gain:[12,1,1,""],phase:[12,1,1,""],phase_vel:[12,1,1,""],power:[12,1,1,""],v_current_control_integral_d:[12,1,1,""],v_current_control_integral_q:[12,1,1,""]},"ODrive.Motor.Error":{BAD_TIMING:[12,1,1,""],BRAKE_RESISTOR_DISARMED:[12,1,1,""],CONTROLLER_FAILED:[12,1,1,""],CONTROLLER_INITIALIZING:[12,1,1,""],CONTROL_DEADLINE_MISSED:[12,1,1,""],CURRENT_LIMIT_VIOLATION:[12,1,1,""],CURRENT_MEASUREMENT_UNAVAILABLE:[12,1,1,""],CURRENT_SENSE_SATURATION:[12,1,1,""],DRV_FAULT:[12,1,1,""],FET_THERMISTOR_OVER_TEMP:[12,1,1,""],I_BUS_OUT_OF_RANGE:[12,1,1,""],MODULATION_IS_NAN:[12,1,1,""],MODULATION_MAGNITUDE:[12,1,1,""],MOTOR_THERMISTOR_OVER_TEMP:[12,1,1,""],PHASE_INDUCTANCE_OUT_OF_RANGE:[12,1,1,""],PHASE_RESISTANCE_OUT_OF_RANGE:[12,1,1,""],SYSTEM_LEVEL:[12,1,1,""],TIMER_UPDATE_MISSED:[12,1,1,""],UNBALANCED_PHASES:[12,1,1,""],UNKNOWN_CURRENT_COMMAND:[12,1,1,""],UNKNOWN_CURRENT_MEASUREMENT:[12,1,1,""],UNKNOWN_GAINS:[12,1,1,""],UNKNOWN_PHASE_ESTIMATE:[12,1,1,""],UNKNOWN_PHASE_VEL:[12,1,1,""],UNKNOWN_TORQUE:[12,1,1,""],UNKNOWN_VBUS_VOLTAGE:[12,1,1,""],UNKNOWN_VOLTAGE_COMMAND:[12,1,1,""]},"ODrive.Motor.MotorType":{ACIM:[12,1,1,""],GIMBAL:[12,1,1,""],HIGH_CURRENT:[12,1,1,""]},"ODrive.OffboardThermistorCurrentLimiter":{Config:[12,0,1,""],config:[12,1,1,""],temperature:[12,1,1,""]},"ODrive.OffboardThermistorCurrentLimiter.Config":{enabled:[12,1,1,""],gpio_pin:[12,1,1,""],poly_coefficient_0:[12,1,1,""],poly_coefficient_1:[12,1,1,""],poly_coefficient_2:[12,1,1,""],poly_coefficient_3:[12,1,1,""],temp_limit_lower:[12,1,1,""],temp_limit_upper:[12,1,1,""]},"ODrive.OnboardThermistorCurrentLimiter":{Config:[12,0,1,""],config:[12,1,1,""],temperature:[12,1,1,""]},"ODrive.OnboardThermistorCurrentLimiter.Config":{enabled:[12,1,1,""],temp_limit_lower:[12,1,1,""],temp_limit_upper:[12,1,1,""]},"ODrive.Oscilloscope":{get_val:[12,2,1,""],size:[12,1,1,""]},"ODrive.SensorlessEstimator":{Config:[12,0,1,""],Error:[12,0,1,""],config:[12,1,1,""],error:[12,1,1,""],phase:[12,1,1,""],phase_vel:[12,1,1,""],pll_pos:[12,1,1,""],vel_estimate:[12,1,1,""]},"ODrive.SensorlessEstimator.Config":{observer_gain:[12,1,1,""],pll_bandwidth:[12,1,1,""],pm_flux_linkage:[12,1,1,""]},"ODrive.SensorlessEstimator.Error":{UNKNOWN_CURRENT_MEASUREMENT:[12,1,1,""],UNSTABLE_GAIN:[12,1,1,""]},"ODrive.StreamProtocolType":{Ascii:[12,1,1,""],AsciiAndStdout:[12,1,1,""],Fibre:[12,1,1,""],Stdout:[12,1,1,""]},"ODrive.SystemStats":{I2C:[12,0,1,""],Usb:[12,0,1,""],i2c:[12,1,1,""],max_stack_usage_analog:[12,1,1,""],max_stack_usage_axis:[12,1,1,""],max_stack_usage_can:[12,1,1,""],max_stack_usage_startup:[12,1,1,""],max_stack_usage_uart:[12,1,1,""],max_stack_usage_usb:[12,1,1,""],min_heap_space:[12,1,1,""],prio_analog:[12,1,1,""],prio_axis:[12,1,1,""],prio_can:[12,1,1,""],prio_startup:[12,1,1,""],prio_uart:[12,1,1,""],prio_usb:[12,1,1,""],stack_size_analog:[12,1,1,""],stack_size_axis:[12,1,1,""],stack_size_can:[12,1,1,""],stack_size_startup:[12,1,1,""],stack_size_uart:[12,1,1,""],stack_size_usb:[12,1,1,""],uptime:[12,1,1,""],usb:[12,1,1,""]},"ODrive.SystemStats.I2C":{addr:[12,1,1,""],addr_match_cnt:[12,1,1,""],error_cnt:[12,1,1,""],rx_cnt:[12,1,1,""]},"ODrive.SystemStats.Usb":{rx_cnt:[12,1,1,""],tx_cnt:[12,1,1,""],tx_overrun_cnt:[12,1,1,""]},"ODrive.TaskTimer":{end_time:[12,1,1,""],length:[12,1,1,""],max_length:[12,1,1,""],start_time:[12,1,1,""]},"ODrive.TaskTimes":{control_loop_checks:[12,1,1,""],control_loop_misc:[12,1,1,""],dc_calib_wait:[12,1,1,""],sampling:[12,1,1,""]},"ODrive.TrapezoidalTrajectory":{Config:[12,0,1,""],config:[12,1,1,""]},"ODrive.TrapezoidalTrajectory.Config":{accel_limit:[12,1,1,""],decel_limit:[12,1,1,""],vel_limit:[12,1,1,""]},ODrive:{AcimEstimator:[12,0,1,""],Axis:[12,0,1,""],Can:[12,0,1,""],Config:[12,0,1,""],Controller:[12,0,1,""],Encoder:[12,0,1,""],Endpoint:[12,0,1,""],Endstop:[12,0,1,""],Error:[12,0,1,""],GpioMode:[12,0,1,""],MechanicalBrake:[12,0,1,""],Motor:[12,0,1,""],OffboardThermistorCurrentLimiter:[12,0,1,""],OnboardThermistorCurrentLimiter:[12,0,1,""],Oscilloscope:[12,0,1,""],SensorlessEstimator:[12,0,1,""],StreamProtocolType:[12,0,1,""],SystemStats:[12,0,1,""],TaskTimer:[12,0,1,""],TaskTimes:[12,0,1,""],ThermistorCurrentLimiter:[12,0,1,""],TrapezoidalTrajectory:[12,0,1,""],brake_resistor_armed:[12,1,1,""],brake_resistor_current:[12,1,1,""],brake_resistor_saturated:[12,1,1,""],can:[12,1,1,""],clear_errors:[12,2,1,""],enter_dfu_mode:[12,2,1,""],erase_configuration:[12,2,1,""],error:[12,1,1,""],fw_version_major:[12,1,1,""],fw_version_minor:[12,1,1,""],fw_version_revision:[12,1,1,""],fw_version_unreleased:[12,1,1,""],get_adc_voltage:[12,2,1,""],get_dma_status:[12,2,1,""],get_drv_fault:[12,2,1,""],get_gpio_states:[12,2,1,""],get_interrupt_status:[12,2,1,""],hw_version_major:[12,1,1,""],hw_version_minor:[12,1,1,""],hw_version_variant:[12,1,1,""],ibus:[12,1,1,""],ibus_report_filter_k:[12,1,1,""],misconfigured:[12,1,1,""],n_evt_control_loop:[12,1,1,""],n_evt_sampling:[12,1,1,""],oscilloscope:[12,1,1,""],otp_valid:[12,1,1,""],reboot:[12,2,1,""],save_configuration:[12,2,1,""],serial_number:[12,1,1,""],system_stats:[12,1,1,""],task_timers_armed:[12,1,1,""],task_times:[12,1,1,""],test_function:[12,2,1,""],test_property:[12,1,1,""],user_config_loaded:[12,1,1,""],vbus_voltage:[12,1,1,""]}},objnames:{"0":["py","class","Python class"],"1":["py","attribute","Python attribute"],"2":["py","method","Python method"]},objtypes:{"0":"py:class","1":"py:attribute","2":"py:method"},terms:{"000":2,"000000":2,"00033594953129068017":15,"001":2,"001995":2,"016kg":19,"01904754638671875":15,"021":2,"043":12,"0483":[9,19,25],"064kg":19,"0666":[9,13,16,25],"068":12,"087744":1,"099978":2,"0d32":19,"0x0":12,"0x0000":15,"0x0001":21,"0x00c":2,"0x01":[3,21,29],"0x010":3,"0x018":3,"0x02":21,"0x03":[21,29],"0x04":21,"0x05":21,"0x08000000":19,"0x0c":3,"0x1":12,"0x10":12,"0x100":12,"0x1000":12,"0x10000":12,"0x100000":12,"0x1000000":12,"0x10000000":12,"0x100000000":12,"0x10006452":9,"0x1337":21,"0x1f":3,"0x2":12,"0x20":12,"0x200":[3,12],"0x2000":12,"0x20000":12,"0x200000":12,"0x2000000":12,"0x20000000":12,"0x200000000":12,"0x21f":3,"0x223":3,"0x23":3,"0x2c":3,"0x3":12,"0x300":3,"0x31f":3,"0x37":21,"0x3d65":21,"0x3f":[2,3],"0x4":12,"0x40":12,"0x400":12,"0x4000":12,"0x40000":12,"0x400000":12,"0x4000000":12,"0x40000000":12,"0x400000000":12,"0x42":21,"0x5":12,"0x6":12,"0x61":21,"0x64":21,"0x672e":21,"0x7":12,"0x8":12,"0x80":12,"0x800":12,"0x80000":12,"0x800000":12,"0x8000000":12,"0x80000000":12,"0x800000000":12,"0x81":29,"0x82":29,"0x83":29,"0x9":12,"0xa":12,"0xaa":21,"0xb":12,"0xc":12,"0xd":12,"0xe":12,"0xe251":21,"0xf":12,"0xffffff":3,"100":[9,12,13,15,16,19],"1000":[2,8,19],"1000000":2,"100m":7,"101963":2,"1024":[12,17,24],"1048576":12,"1073741824":12,"10a":[13,16],"10hz":[2,7],"10kohm":14,"10ma":[13,16],"10v":[13,16],"113":19,"115":12,"115200":[9,25,28],"120":[2,23],"12000000":[2,9,25],"1209":[13,16,19],"120ohm":2,"120r":2,"120uf":[9,13,16],"123":1,"125":[2,7],"125000":2,"127":21,"128":12,"128kg":19,"12mhz":2,"12v":[13,16,23],"131072":12,"134217728":12,"1364":10,"142":12,"150":[15,26],"152389526367188":15,"15a":[13,16],"16384":12,"16777215":3,"16777216":12,"17179869184":12,"1793474406003952":15,"1800":9,"194110":19,"195":12,"199988":2,"2000":9,"2018":[6,9,13,16],"201980":2,"2020":[9,13,16],"2048":12,"2097152":12,"2147483648":12,"2200":19,"22nf":[10,15],"230":12,"239":12,"2400":27,"243":27,"244":27,"245":27,"246":27,"247":27,"24v":[9,12,13,16,19],"250":2,"250000":2,"250khz":24,"256":12,"25f":11,"262144":[12,19],"268435456":12,"26v":12,"270kv":4,"299986":2,"2pi":4,"301976":2,"306a396a3235":[13,16,19],"308039673235":19,"326":10,"3333":[9,25],"33554432":12,"34359738368":12,"35000":23,"3748":[9,25],"374b":[9,25],"385f324d3037":19,"388237123123":19,"391":12,"399986":2,"3kohm":14,"4000":[10,27],"401972":2,"4096":[0,12],"40a":23,"4194304":12,"4294967296":12,"456":1,"460":12,"461":12,"470nf":26,"470uf":[9,13,16],"47nf":10,"487":9,"48v":[9,12,13,16,19],"4ma":[13,16],"500":[2,19],"500000":[2,3],"5000rpm":27,"50a":[13,16],"50w":[13,16],"512":12,"5126956701278687":15,"51328895422":4,"524288":12,"52v":12,"536870912":12,"538":12,"543":9,"56v":[13,16,19],"60a":[13,16],"613":12,"65536":12,"67108864":12,"74ahc1g125s":10,"75a":23,"769":12,"792":12,"7kohm":14,"7uf":26,"81905517578125":15,"8192":[10,11,12],"826":12,"8388608":12,"840m":23,"8432":12,"8589934592":12,"8912":11,"8khz":7,"90a":[13,16,23],"913":12,"921":12,"97055721282959":[13,16],"990474700927734":15,"\u03c9":[13,16],"abstract":3,"boolean":11,"break":[2,9,12,13,16,17,25],"byte":[2,3,19,21],"case":[4,7,8,9,10,11,12,13,15,16,25],"class":[7,12,17,29],"default":[2,4,8,9,10,11,12,13,15,16,19,20,21,24,28,29],"enum":[9,27],"export":[13,16],"final":[11,19,21],"float":[1,2,8,9,11,12,15,17,24],"function":[2,3,6,8,9,11,12,13,15,16,19,20,22,26,28],"import":[18,27],"int":[11,12,15],"long":[4,10,24],"new":[1,4,7,9,13,16,19,25,27],"null":19,"public":[9,25],"return":[4,10,12,19,21,27],"short":14,"switch":[2,4,11,19],"true":[4,6,8,9,10,11,12,13,15,16,17,24,28],"try":[2,4,8,9,10,11,13,15,16,19,22,27],"while":[2,7,8,10,12,13,16,19,21,26],AMS:[9,10,25],And:10,For:[1,2,3,4,7,8,9,10,11,12,13,14,16,17,18,19,21,24,26,27,29],IDE:[5,6],IDs:[3,21],NOT:11,Not:[1,12],ONE:9,One:[10,12,13,16],Pos:2,That:[1,2,10,11,13,16],The:[1,2,3,4,6,7,8,10,12,13,15,16,17,18,19,21,22,23,24,26,27,28,29],Then:[2,9,10,13,15,16,19,25],There:[1,2,7,9,10,11,13,15,16,21,22,25,26,27],These:[3,9,10,14,25],Use:[1,2,8,9,10,24,27],Used:[9,12],Using:[4,8,19],With:[8,22,27],Yes:19,___:9,__dict__:27,__getattribute__:27,_build:9,_input_pos_properti:22,_input_vel_properti:15,_test:[9,25],abi:10,abl:[9,10,11,15,19,22,25],about:[7,10,11,12,13,15,16,19,21],abov:[4,9,10,12,13,15,16,19,27],abruptli:10,abs_spi_com_fail:12,abs_spi_cs_gpio_pin:[10,12],abs_spi_not_readi:12,abs_spi_timeout:12,absent:7,absolut:[8,10,12,13,16,17],accel:[4,10,12],accel_limit:[8,11,12,17],acceler:[7,8,9],accept:[4,9,21],access:[1,11],accompani:[15,19],accord:[2,7,11],accordingli:27,account:[12,19],accumul:17,accuraci:[10,15],achiev:[2,4,7,13,14,16,24],acim:12,acim_autoflux_attack_gain:12,acim_autoflux_decay_gain:12,acim_autoflux_en:12,acim_autoflux_min_id:12,acim_estim:12,acim_estimator_upd:12,acim_gain_min_flux:12,acimestim:12,acknowledg:21,across:[13,16,19],act:2,action:[4,19],activ:[6,8,9,11,12,15,22,24],actual:[8,9,12,13,16,25],adapt:[7,9],adapter_nsrst_delai:9,adc:[0,12,26],add:[2,3,9,10,12,13,15,16,25,26,27],added:[9,17,19,27],adding:[2,13,16,27],addit:[17,26],addr:12,addr_match_cnt:12,address:[2,3],adequ:[11,14],adher:9,advic:[13,16],affect:12,after:[4,7,9,10,11,12,13,15,16,19,20,23,24,25,26],again:[9,10,12,13,14,15,16,19,27],against:[2,9,12,27],aggress:8,agre:10,air:23,aircraft:15,aka:[3,10],aliexpress:14,align:15,all:[1,2,3,4,6,7,8,9,10,12,13,14,15,16,18,19,21,25,27],allig:[13,16],allow:[2,3,4,7,8,10,11,13,14,15,16],almost:12,alon:19,along:[9,25,27],alongsid:[13,16],alreadi:[13,16,18,19],also:[0,1,2,4,5,7,8,9,10,12,13,15,16,19,20,22,24,25,26,27],alt:19,altern:[2,6,27],although:[2,3],alwai:[7,9,12,13,16],amazon:14,amount:[9,12,13,14,16,24],amp:[12,13,16,17,23],amplifi:[13,16],amt232a:10,amt232b:10,amt233a:10,amt233b:10,amt23x:10,amt23xx:10,anaconda3:19,anaconda:[13,16,19],analog:[9,12,20,25,26],analog_in:12,analog_input_test:[9,25],analysi:10,analyz:10,angl:[7,8,10,15,22],angular:12,ani:[0,1,4,9,10,13,15,16,19,21,22,24,26,28],anoth:[8,10,13,14,16,19],anticog:12,anticogging_en:12,anticogging_valid:12,anymor:7,anyth:[10,14,22],anywai:15,apart:[10,15],api:[9,27],app:19,appdata:[13,16],appdir:[9,25],appear:[9,19,25],append:[9,25],appli:[4,6,7,10,12,13,15,16],applic:[2,8,12,13,15,16,18],approach:[4,11,27],appropri:[8,15,19,21],approxim:19,apt:[2,9,13,16,19,25],arbitr:3,arch:9,architectur:[2,29],arduino:[2,9,14,25],area:[2,14],argument:[9,25],aris:9,arm:[6,9,19,25],arm_gcc_root:[6,9],armhf:9,armmb:9,around:[1,4,7,8,9,13,15,16,24,25,27],arrai:2,arriv:7,arrow:5,articl:2,as5047p:10,as5048a:10,ascii:[8,9,12,13,16,25,28,29],asciiandstdout:12,ask:[8,9],assign:[3,4,13,16],associ:29,assum:[2,9,11,13,16,18,25,29],asterisk:1,attach:[13,16,29],attempt:12,attent:[13,16],attr:[9,13,16,25,27],attribut:[4,27],attributeerror:27,aur:9,author:8,auto:[6,9],auto_prob:9,autocomplet:6,autocrlf:9,automat:[4,7,8,9,10,12,13,16,19,22,24,26],automobil:2,automot:2,autotun:12,autotuning_phas:12,avail:[2,3,7,9,13,14,16,19,21,23,26],averag:[13,16],avh:[9,25],avoid:[3,9,10,13,16],awai:[11,27],awar:24,awesom:10,axes:[4,9,12,15,22,24,25],axi:[1,2,3,8,10,11,12,13,15,16,17,19,22,24,26,27],axis0:[1,2,3,4,7,8,11,13,15,16,17,19,22,26,27],axis1:[2,3,4,13,15,16,19,26],axis_error_:27,axis_id:3,axis_state_closed_loop_control:[4,10,11,13,15,16],axis_state_encoder_hall_polarity_calibr:[10,15],axis_state_encoder_index_search:10,axis_state_encoder_offset_calibr:[10,15],axis_state_full_calibration_sequ:[10,13,16],axis_state_hom:11,axis_state_idl:[4,10,15,24],axis_state_motor_calibr:15,axis_to_mirror:12,axisconfig:9,axisst:12,back:[2,7,9,10,13,14,16,18,19,24],bad:12,bad_tim:12,bandwidth:[7,8,12,15],base:[2,3,7,9,11,12,21,26,29],bash:[9,25,27],bashrc:[13,16],basic:[1,3,13,16,23],batteri:[13,14,16],baud:[2,3],baud_rat:[2,3,12],baudrat:[12,28],bcm2835:[9,25],beakpoint:5,becaus:[2,7,11,13,15,16,19],becom:[4,7],been:[6,7,10,13,16,17,24,27],beep:[13,16],befor:[0,1,2,3,4,6,7,8,9,10,11,13,16,17,19,25],begin:[2,9,13,16,19],beginn:2,behavior:[10,13,16,24],behaviour:[11,13,16],being:[7,10,12,13,16,19],bell:27,below:[4,7,9,10,11,12,14,19,21,26],belt:[8,27],bemf_ff_en:12,benefici:14,besid:23,best:[7,12,14,17,24],beta:26,better:[7,13,15,16],between:[0,2,7,10,12,13,14,15,16,17,20,22,26,27],bias:[10,13,16],bin:[9,13,16,19,25],binari:[9,19],binutil:9,bit:[0,3,12,13,15,16],bitrat:2,bitshift:2,bitwis:1,black:15,block:6,blue:[8,15,19],board:[2,3,5,6,9,10,12,13,15,16,19,20,26,27],bold:20,bool:12,boolproperti:12,boot0:19,boot:[9,15,25],bootload:19,bootstrap:9,bootup:[10,24],borrow:2,both:[3,4,11,12,13,15,16,17,21,22,23,24,28],bottom:[13,16,19],bounc:11,brace:[13,16],bracket:19,brake:[9,12,20,25],brake_deadtime_viol:12,brake_duty_cycl:12,brake_duty_cycle_nan:12,brake_resist:[12,13,16],brake_resistor_arm:12,brake_resistor_curr:12,brake_resistor_disarm:12,brake_resistor_satur:12,branch:[6,9],breakpoint:[6,9],brew:[9,13,16,19],bring:12,broken:14,brows:5,brushless:[13,16],buffer:10,bug:[9,10,13,16],build:[5,19,25],built:[2,9,15,19],bullet:9,bump:[13,15,16],burn:[12,26],bus:[3,9,12,13,14,16],button:[6,19],bypass:[13,16],cabl:[1,10,13,16,19,27],calcul:[1,7,12,21,26],calib_anticog:12,calib_pos_threshold:12,calib_rang:[10,12],calib_scan_dist:[12,15],calib_scan_omega:12,calib_scan_respons:12,calib_vel_threshold:12,calibr:[4,9,12,15,19,25,27],calibration_curr:[10,12,13,16],calibration_lockin:[10,12],calibration_test:[9,25],calibrationlockin:12,calibratrion:[13,16],call:[3,5,6,7,9,10,13,14,16,19,21,27],calul:[13,16],can0:[2,9,25],can:[0,1,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19,20,21,22,24,25,26,27,28,29],can_a:[12,20],can_heartbeat:12,can_id:3,can_node_id:3,can_node_id_extend:3,can_test:[9,25],cancellation_token:19,canconfig:12,candid:9,candump:2,canh:2,canhelp:2,canl:2,cannot:[4,11],cansimpl:2,cantool:2,capabl:[12,13,16,22],capacit:14,capacitor:[9,10,13,15,16,26,27],captur:27,card:[9,25],care:[2,3,21],carefulli:[13,16],carri:[9,15,19],cascad:[4,7],cask:[9,19],cat:27,caus:[10,12,13,14,16,17,27],caution:10,ccc:12,cdc:[27,29],celsiu:26,center:[9,10,25],certain:17,cfg:[9,19,25],chanc:2,chang:[1,4,6,7,8,9,10,11,12,13,15,16,19,20,24,25,26,28],changelog:[9,13,16,17],channel:[13,15,16,23],charact:[1,9],charg:[13,16],chase:8,cheap:[10,27],check:[1,3,9,10,11,12,13,14,15,16,18,19,25,27],checklist:9,checkout:9,checksum:1,cherri:9,chip:[9,10],chmod:[9,25],choic:[10,18],choos:[8,9,10,13,16,19,24],chose:[12,13,16,24],chown:[9,25],circuit:10,circular:[12,17,24],circular_setpoint:[8,12,17,24],circular_setpoint_rang:[12,17,24],circular_setpoints_rang:8,clamp:7,clarifi:26,clean:[9,27],clear:[1,10,12,13,16,27],clear_error:[12,13,16,27],clearli:[13,16],cli:[9,25],click:[11,19,27],client:21,clip:[13,16],clk:10,clock:9,clone:[6,9,25],close:[4,9,10,11,12,13,14,15,16,19,23,24,25,26],closed_loop_control:[12,19],closed_loop_test:[9,25],cmd:[2,13,16],cmd_id:3,cmdline:[9,25],coast:8,code:[2,5,11,13,16,19],codebas:6,cog:[10,13,16],cogging_ratio:12,coil:[7,13,16],collis:3,color:15,colour:2,column:[5,12],com:[1,9,12,13,16,25,29],combin:21,come:[4,10,13,15,16,18,22,27],comfort:[13,16],comma:19,command:[3,6,7,8,9,10,11,12,13,15,16,17,19,22,24,25,29],comment:[1,6],commit:9,common:[11,14],commun:[1,13,14,16,18,29],commut:27,compar:[9,10,15],compat:[1,2,3,10,15,19],compil:[6,9,19,25,27],complet:[4,9,10,12,13,16],complex:7,compliant:21,compon:[9,12,13,16,25],composit:29,comprehens:18,comput:[2,9],concern:10,concurr:19,condens:4,config:[0,1,2,3,4,6,7,8,9,10,11,12,13,15,16,17,19,20,22,24,25,26,28,29],config_board_vers:9,config_build_libfibr:9,config_debug:[6,9],configur:[0,2,10,12,17,20,22,25,26,28,29],confirm:[2,10,27],conflict:[2,3,9,12,27],conform:2,confus:[13,16],congratul:[10,19],conjunct:12,connect:[1,2,3,4,5,6,9,10,11,12,13,14,15,16,19,20,22,24,25,28],connector:[9,13,15,16],conserv:[13,16],consid:[13,16],consist:[9,21,25,29],consol:[1,9,13,16,25],constant:[7,9,26],constraint:12,consum:12,contact:27,contain:[7,9,12,19],content:[9,12,25],context:2,continu:[2,6,8,13,15,16,19,22,23],contribut:18,control:[1,2,3,9,10,11,12,18,19,22,24,25,26,27],control_deadline_miss:12,control_iteration_miss:12,control_loop_check:12,control_loop_misc:12,control_mod:[4,8,12,15],control_mode_torque_control:8,control_mode_velocity_control:[4,8,15],controller_fail:12,controller_initi:12,controller_upd:12,controlmod:12,conveni:[2,26],convent:[9,13,16,21],converg:4,convert:[7,17,19],conveyor:8,cool:[5,23],copi:[9,19],copper:2,copybutton:9,core:9,correct:[1,2,8,9,10,11,13,16,17,19,25,27],correctli:[4,7,8,19,27],correl:8,correspond:[3,9,10,12,20,21],cortex:[6,9],cos:[9,12,25],cosin:12,could:[13,16],count:[4,10,12,13,15,16,17,23,27],count_in_cpr:12,counteract:19,counts_per_step:17,coupl:[9,10,25,27],coupler:27,cours:18,cover:12,coverag:[9,25],cpp:[7,9],cpptool:6,cpr:[10,11,12,13,15,16,17],cpr_polepairs_mismatch:12,crash:3,creat:[2,6,9,19,26],crlf:9,cross:[5,9],crystal:2,csn:10,ctrl1:6,ctrl:[2,5,6,9,25],cubemx:9,cui:[9,10,25,27],curl:[9,13,16,25],current:[8,9,10,12,15,17,19,20,21,22,23,25,26,27],current_cmd:7,current_control:[4,12,19],current_control_bandwidth:[7,12,15],current_controller_upd:12,current_error:7,current_feedback:7,current_feedforward:7,current_gain:7,current_integr:7,current_integrator_gain:7,current_lim:[4,8,12,13,16],current_lim_margin:12,current_limit_viol:12,current_meas_pha:12,current_meas_phb:12,current_meas_phc:12,current_measurement_unavail:12,current_ramp_r:17,current_sens:12,current_sense_satur:12,current_setpoint:[4,17],current_st:[4,12],currentcontrol:12,custom:[9,21],cut:9,cycl:[12,13,16,19],cylind:[13,16],d5065:4,danfoss:2,danger:19,data:[2,4,10,14,15,21,29],data_r:19,databas:2,datasheet:[12,13,14,15,16,26],date:[13,16],dc_bus_over_curr:12,dc_bus_over_regen_curr:[12,13,16],dc_bus_over_voltag:12,dc_bus_overvoltage_ramp_end:12,dc_bus_overvoltage_ramp_start:12,dc_bus_overvoltage_trip_level:12,dc_bus_under_voltag:12,dc_bus_undervoltage_trip_level:12,dc_calib:12,dc_calib_pha:12,dc_calib_phb:12,dc_calib_phc:12,dc_calib_tau:12,dc_calib_wait:12,dc_max_negative_curr:[12,13,16],dc_max_positive_curr:12,debounc:11,debounce_m:12,debug:[5,10,12,13,16,25,27],debug_print:9,debugg:[6,9,25],decel_limit:[8,11,12,17],deceler:[8,13,16],decent:9,decid:[13,16],decim:12,decode_messag:2,deconflict:3,decreas:[7,10],defin:[9,12],definit:[2,21],degre:26,delet:[9,19,25],deliv:[7,13,16],delta:12,delta_pos_cpr_count:12,demonstr:[13,16],dep:[13,16],depend:[6,9,10,12,13,15,16,21,23],describ:[2,3,9,21,25],descript:[4,9,25],descriptor:[4,19,29],deseri:2,design:[2,6],desir:[1,3,7,12,13,16],desktop:19,desold:[13,16],despit:[13,16],destin:[1,8],detail:[8,9,11,12,13,15,16,17,21,23,25,27],detect:[11,12,27],detent:[13,16],determin:[10,21,26],dev:[1,9,19,25],devel:9,develop:[6,19,25,27],devic:[1,2,3,4,6,9,12,13,14,16,24,27,28,29],devnum:19,df11:19,dfu:[9,27],dfufilemgr:19,dfuse:19,dfusedemo:19,diagram:[7,11,14,19],dial:7,dictionari:2,did:9,didn:9,diff:9,differ:[2,3,4,7,9,10,13,14,15,16,19,21,25,26,27],differenti:2,difficulti:10,digit:[10,11,12,20],digital_pull_down:12,digital_pull_up:12,dip:[2,19],dir:[8,9,12,14,17,20,24,25],dir_gpio_pin:[12,24],direct:[4,9,10,11,12,13,14,16,25],directli:[4,6,7,8,9,13,16,19,22,26],directori:[9,19],disabl:[8,11,12,15,22,28],disarm:12,disastr:[13,16],disconnect:[13,16],discord:9,discuss:9,disengag:10,displac:[12,15],displai:7,dissip:[12,13,16],distinguish:[13,16],distribut:[13,16],disturb:[7,8,13,16,19],divid:[13,16],dm00031020:12,dma1:12,dma2:12,dma:12,doc:[2,9],docker:9,document:[3,12,13,16,17,25,27,29],doe:[1,3,4,7,9,10,12,13,14,16,21,27],doesn:[6,9,10,12,13,15,16,19,21,22],doing:[6,10,19],domain:12,don:[1,3,5,9,10,11,12,13,15,16,18,19,24,25,26],done:[0,3,6,9,13,15,16,19,25],dos:[13,16],dot:12,doubl:9,down:[5,7,12,26,27],download:[6,9,19,25],doxygen:9,drain:[13,16],drape:27,draw:22,drawn:[13,14,16],drill:15,drive:[13,14,15,16],driver:[7,9,13,16,19,27,29],drop:[9,25],dropdown:19,drv:[13,16],drv_fault:12,dtoverlai:[2,9,25],dtparam:[2,9,25],due:[7,8,9,10,12,27],dump:27,dump_error:[13,16],duplicate_can_id:12,dure:[4,6,10,11,12,13,16,19,26],duti:[12,13,16],dynam:7,eabi:[6,9,19,25],each:[1,2,3,4,7,8,9,10,11,13,15,16,21,29],earlier:[2,12,19],eas:9,easi:[2,10],easiest:[9,25],easili:18,eat:[13,16],ebai:14,echo:[1,13,16],eclips:9,edit:[5,9,19],editor:[6,19],effect:[7,9,12,13,14,16,17,20,25,26,27],effective_current_lim:12,either:[3,11,13,16,28],electr:[2,12,13,14,16,17,26],electrical_pow:12,electrical_power_bandwidth:12,electron:[11,14],element:26,elev:19,elf:19,elimin:14,els:[13,15,16,22,27],elsewher:[13,16],embed:[6,9,19],emi:27,emit:21,emploi:21,emul:[9,25],enabl:[2,4,7,8,9,10,12,15,17,20,24,25,26,28],enable_ascii_protocol_on_usb:29,enable_brake_resistor:[12,13,16,17],enable_can_a:12,enable_dc_bus_overvoltage_ramp:12,enable_gain_schedul:12,enable_i2c_a:12,enable_overspeed_error:12,enable_phase_interpol:12,enable_sensorless_mod:[4,12],enable_step_dir:[12,24],enable_torque_mode_vel_limit:[8,12],enable_uart:[9,25],enable_uart_a:[12,20,28],enable_uart_b:[12,28],enable_uart_c:12,enable_vel_limit:12,enable_watchdog:[12,13,16],enc0:[12,20],enc1:[12,20],enc2:12,encod:[1,7,8,9,11,12,15,17,19,20,23,25,27],encode_messag:2,encoder_dir_find:12,encoder_error_:27,encoder_hall_phase_calibr:12,encoder_hall_polarity_calibr:12,encoder_index_search:12,encoder_mode_hal:[10,15],encoder_mode_increment:10,encoder_mode_spi_abs_am:10,encoder_mode_spi_abs_cui:10,encoder_offset_calibr:12,encoder_pll_bandwidth:27,encoder_rate_m:12,encoder_test:[9,25],encoder_upd:12,encount:[1,10],encourag:9,end:[2,9,13,16,19,25],end_tim:12,endian:[3,21],endpoint:[12,15,21,22,29],endpointid:21,endpointrefproperti:12,endstop:12,endstop_st:[11,12],endstop_upd:12,enforc:9,engag:12,enough:[7,8,14,27],ensur:[7,9,10,26,27],enter:[0,6,10,11,13,16,27],enter_dfu_mod:12,entertain:10,entir:24,enumer:[9,21],env:27,environ:[6,9,25,27],equal:[10,12,21],equat:17,eras:[1,9,19],erase_configur:[4,12,13,16],erpm:23,error:[1,9,10,12,13,15,16,17,24,26],error_cnt:12,error_gpio_pin:12,essenti:[2,7,9,25],estim:[12,15,27],estop_request:12,etc:[2,5,9,13,14,15,16,22,25],evalu:10,even:[8,9,10,12,13,16,27],event:12,ever:10,everi:[4,9,10,15,17,25,26],everyth:[9,10,15,19],exactli:[13,16,19],exampl:[1,4,7,8,9,10,12,13,14,16,17,18,19,21,22,24,26],exce:[8,12],exceed:[10,26,27],except:[9,21],excess:[11,13,16,27],exchang:21,exclud:[9,25],exclus:12,exe:19,execut:[8,13,16],exhibit:[7,11],exist:[5,9,13,16,17],exit:[2,19],expect:[7,8,9,12,15,21,26,27],experi:27,explan:[7,15],explicit:17,explicitli:17,expos:20,ext:6,extend:3,extens:[6,9],extern:[2,7,8,9,19,25],extra:[2,13,16],extrem:10,extrud:8,f_pclk:12,face:17,facil:[9,25],fact:10,factor:[13,16],factori:4,fail:[6,9,12,13,16],failsaf:[15,22],failur:[2,11],fairli:[13,15,16],fals:[8,11,12,24,28],famili:10,familiar:29,fan:23,far:[10,27],fast:[2,13,16],fat32:[9,25],favorit:1,fdisk:[9,25],featur:[5,7,9,12,13,15,16,19,20,22],fed:[7,13,16],feed:[0,1,12,13,16],feedback:[4,8,13,15,16],feedforward:7,feel:[9,13,15,16],ferrou:[13,16],fet:7,fet_thermistor:[12,26,27],fet_thermistor_over_temp:12,few:[4,9,10,11,13,16,26],fewer:17,fibr:[3,9,12,27],fibre_test:[9,25],field:[21,26],fierc:[13,16],fight:[13,16,27],figur:10,file:[1,5,6,9,12,13,16,17,19,25,27],filter:[4,12,15,20,21,27],final_v_alpha:12,final_v_beta:12,find:[4,5,9,10,11,13,14,16,19,25],find_ani:18,find_idx_on_lockin_onli:12,fine:[3,10,13,16],finish:[5,9,19],finish_dist:12,finish_on_dist:12,finish_on_enc_idx:12,finish_on_vel:12,firmli:[13,16],firmwar:[4,5,10,17,25],first:[2,3,5,6,9,10,13,15,16,19,26,27],fix:[9,10,13,14,16,27],flag:[2,9,13,16,27],flash:[5,25],flat:7,flex:27,flexibl:7,flick:19,float32properti:12,flow:[13,14,16],flux:12,flywheel:27,focus:2,folder:[5,6,9],follow:[0,1,2,3,4,7,9,10,11,12,13,15,16,17,19,22,25,26,27,29],font:20,forc:[7,19],forev:8,forget:[9,11,19],fork:9,form:[14,19],format:[3,9,19,23,25,27],formula:9,forum:[2,27],forward:[1,13,16],found:[4,9,10,13,16,19,26,27],four:3,frac:7,fragil:24,frame:[2,8,13,16],free:[13,15,16],freeli:[10,13,16],frequenc:[8,12],fri:12,friction:[10,13,16],from:[1,2,3,6,7,8,9,10,11,12,14,15,17,18,21,22,25,26,27],from_goal_point:8,from_input_po:12,fry:[9,25],fssl:[13,16],ftdi:1,full:[2,7,13,15,16,24,27],full_calibration_sequ:12,fulli:[13,16],further:[13,16,26,27],furthermor:4,furthest:2,futur:[3,21],fw_version_major:[4,12],fw_version_minor:[4,12],fw_version_revis:[4,12],fw_version_unreleas:12,gain:[7,8,12,13,15,16,27],gain_scheduling_width:12,gate:7,gcc:[5,9,19,25],gcode:1,gdb:[6,9],gdwarf:9,gear:27,gener:[1,2,8,12,13,14,16,17,19,20,21,25,27,29],general_lockin:12,gentli:[13,16],get:[2,3,4,6,9,10,12,15,17,18,19,22,24,25],get_adc_voltag:[0,12],get_dma_statu:12,get_drv_fault:12,get_gpio_st:12,get_interrupt_statu:12,get_val:12,gian:27,gimbal:[10,12,13,16],git:[6,9,25,27],github:[9,17,19,25],githubusercont:[13,16],give:[1,2,3,8,11],given:[3,4,15,27],gnd:[9,10,15,19,20,22,24,28],gnda:26,gnu:[6,9],goal:[1,4,8,12],goe:[4,22,26],going:[3,9,13,15,16],gonna:9,good:[2,8,9,10,11,13,14,15,16,26],googl:9,got:[15,19],gpio10_mod:[10,15],gpio11_mod:[10,15],gpio12_mod:10,gpio13_mod:10,gpio14_mod:10,gpio1:[0,1,17,19,20,28],gpio1_mod:[17,28],gpio2:[1,20],gpio2_mod:28,gpio3:20,gpio3_analog_map:[0,12],gpio3_mod:28,gpio3_pwm_map:15,gpio4:[20,22],gpio4_analog_map:[0,12],gpio4_mod:[22,28],gpio4_pwm_map:[15,22],gpio5:20,gpio5_mod:11,gpio6:[19,20],gpio7:[20,24],gpio7_mod:24,gpio8:[20,24],gpio8_mod:24,gpio9_mod:[10,15],gpio:[0,9,10,12,14,15,20,24,25,26,27,28],gpio_mode_analog_in:[0,12,20],gpio_mode_can_a:20,gpio_mode_digit:[10,11,12,15,17,20,24,28],gpio_mode_digital_pull_down:[11,24],gpio_mode_digital_pull_up:11,gpio_mode_enc0:20,gpio_mode_enc1:20,gpio_mode_i2c_a:20,gpio_mode_mech_brak:20,gpio_mode_pwm:[20,22],gpio_mode_uart_a:[12,20],gpio_mode_uart_b:[12,20,28],gpio_num:12,gpio_pin:[12,26],gpioi:12,gpiomod:12,gpiox_mod:[0,11],gpl:9,grai:[13,16],graph:7,graphic:[9,19],graviti:[10,13,16],great:2,greater:[2,4],green:15,grep:[19,27],grind:4,ground:[10,13,16,22,24,26,27],group:[3,29],grow:8,grub:27,guarante:3,gui:[9,25],guid:[3,6,10,13,16,18,19,22],had:[10,13,16,17,19],half:[8,13,16],hall:[4,9,11,12,13,15,16,20,25],hall_not_calibrated_yet:12,hall_polar:12,hall_polarity_calibr:12,hall_stat:12,halt:[5,6,9,19],hand:[10,13,16,19],handl:[2,8],handler:21,happen:[9,13,16],hard:[8,9,10,25,27],harder:9,hardwar:[4,9,10,12,24,25],harmless:12,has:[0,2,6,7,9,10,11,12,13,15,16,17,19,24,26],hat:[2,9,25],have:[1,2,3,6,7,8,9,10,11,12,13,14,15,16,18,19,20,25,26,27,29],hdmi:[9,25],header:2,hear:[13,16],heartbeat:2,heartbeat_rate_m:12,heat:27,heatsink:23,height:10,held:19,help:[2,7,9,10,11,13,16,19,22,26,27],henc:9,here:[1,4,6,7,9,11,12,13,14,15,16,19,22,23,25,27,28],hex:[4,19],hexadecim:4,high:[2,10,11,13,14,15,16],high_curr:12,higher:[8,12,13,14,15,16,26,29],highest:2,highlight:5,hit:[2,5,11],hla_swd:9,hobbi:[13,15,16,22],hold:[7,13,16],home:12,homebrew:[13,16],homing_spe:[11,12,17],homing_without_endstop:12,hook:[13,16,22],hopefulli:15,host:[1,2,7,29],hostnam:[9,25],hous:[7,27],how:[0,1,2,3,4,11,13,15,16,17,19,26,27],howev:[2,9,12,13,16,27],hpp:9,html:9,http:[9,12,13,16,25],hub:15,human:1,hundr:4,hw_version_major:[4,12],hw_version_minor:[4,12],hw_version_revis:4,hw_version_vari:12,hwil:[9,25],i2c:[2,12,20],i2c_a:12,i_bu:12,i_bus_hard_max:12,i_bus_hard_min:12,i_bus_out_of_rang:12,i_gain:12,i_leak_max:12,i_measured_report_filter_k:12,i_motor:[13,16],ialpha_measur:12,ibeta_measur:12,ibu:12,ibus_report_filter_k:12,icmp:9,id_measur:12,id_setpoint:12,idea:26,ident:4,identifi:[4,13,16],idl:[4,12,15],idproduct:[9,13,16,25],idq:12,idvendor:[9,13,16,25],ignor:[1,9,12,19,21],ignore_illegal_hall_st:12,illegal_hall_st:12,illustr:14,imagin:7,immedi:[4,10,11,12,13,16],immens:7,impact:10,implement:[2,3,9,21],importantli:10,improv:[8,13,16],imput:1,inact:12,inch:15,includ:[1,2,6,9,10,12,13,16,27],inclus:3,incom:[4,8],incomplet:12,incorrect:[9,25,27],incorrectli:21,increas:[7,10,12,13,16,19],increment:[4,8,9,10,12,15,17,23,25],indefinit:8,indent:9,independ:[4,12,27],index:[9,12,13,16,20,25,27],index_found:12,index_not_found_yet:12,index_offset:12,index_search:10,indic:[4,12,13,16,21,26],induct:[13,14,15,16,27],inertia:[8,12,17,27],inevitebl:[13,16],inexpens:2,inf:12,infin:12,info:[1,9,24],inform:[2,9,11,12,15,22,26,27],init:[9,19],initi:[13,16,21,27],inject:14,input:[2,7,8,9,10,11,12,13,16,17,20,21,23,24,25,26],input_filter_bandwidth:[8,12],input_mod:[4,8,12],input_mode_passthrough:4,input_mode_pos_filt:8,input_mode_trap_traj:[8,11],input_mode_vel_ramp:[8,11],input_po:[1,4,8,12,13,16,17,22,24],input_torqu:[4,8,12,17],input_vel:[4,8,10,12,15,17],inputmod:12,inrush:[13,16],inspect:[13,16],instal:[2,6,9,18,25,27],instanc:[13,16,21],instead:[2,4,7,8,9,10,13,15,16,17,19,28],instruct:[0,5,9,10,13,15,16,19,25],instrument:14,insul:27,int32:21,int32properti:12,int64properti:12,integ:[1,24],integr:[6,7,12],intel:3,intend:[2,9,13,16],interact:[3,13,16],intercept:4,interf:10,interfac:[2,9,10,12,13,16,17,19,21,24,25,27,29],intermedi:29,intern:[4,12,19],interoper:24,interpol:12,interpret:1,interrupt:[2,9,12,13,14,16,25],intf:19,introduc:[9,27],invalid_brake_resist:12,invalid_circular_rang:12,invalid_estim:12,invalid_input_mod:12,invalid_load_encod:12,invalid_mirror_axi:12,invalid_st:12,inverter_temp_limit_low:12,inverter_temp_limit_upp:12,investig:12,invok:[9,13,16],involv:2,ioc:9,ipdb:[9,25],ipython3:[9,25],iq_measur:[4,12],iq_setpoint:[4,12,19],irqn:12,is_active_high:12,is_active_low:12,is_arm:12,is_calibr:12,is_extend:12,is_hom:12,is_readi:12,iseri:19,isn:[2,13,15,16],iso7762f:14,iso:3,ispect:9,issu:[10,13,14,16,17,19],item:[9,27],iter:[7,12],its:[2,3,11,13,16,19,21,27,29],itself:[19,26,27],j1939:3,jack:27,jcr:12,jerki:8,jinja2:[9,25],jinja:9,jitter:2,jonathonf:9,json:[19,21],jsonschema:9,jtag:9,juli:[13,16],just:[9,10,13,15,16,18,19,21,27],kbp:[2,12],keep:[8,9,12,14,15,19,24,25],kei:[9,20,25],keyboard:[9,25],khz:9,kib:19,kill:6,kind:[7,21],know:[1,9,26],known:[4,7,11,12,15,26],l3333:[9,25],label:[9,10,11,13,16,20],lambda:[7,19],languag:1,larg:[8,10,13,16,24,27],larger:[4,8,12,13,16],largest:[13,16],last:[2,12,15,22,27],last_drv_fault:12,last_error_tim:12,latenc:2,later:[3,9,15,20],latest:[9,13,16,19,27],launch:[13,16,19],layer:2,layout:[9,25],lead:10,learn:[13,16],least:[1,3],leav:[11,13,16,19],left:[5,19,20],length:[12,14,21],less:2,lesser:23,let:[3,8,9,10,13,15,16,19,24,26],level:[7,9,13,14,16,28,29],lfs:9,lib:[19,27],libfontconfig:[9,25],librari:[1,2,9,18],libtool:9,libusb0:19,libusb:[9,13,16,19,25,27],libusb_open:9,libxft2:[9,25],licens:9,life:[9,10,11],light:6,lightweight:9,like:[1,2,3,8,9,10,12,13,14,16,19,28,29],limit:[1,7,8,9,11,12,19,23,25,26,27],line:[1,9,10,12,13,16,19,24,25],linear:[2,12],linearli:7,link:[2,9,13,15,16,19,27],linux:[1,6,9,13,16,19,27],linuxarm:[9,25],list:[1,4,12,19,27],lite:[9,25],littl:[3,8,11,21],liveplott:7,load:[2,7,8,9,10,13,16,29],load_configur:12,load_encoder_axi:12,loader:[9,25],local:[9,13,16,25,27],localhost:[9,25],locat:[5,6,9,10,11,19,27],lockin_spin:12,lockinconfig:12,log:27,logic:[10,12,15,28],login:19,longer:10,look:[3,9,10,13,14,15,16,25,27,29],loop:[4,9,10,11,12,13,15,16,24,25],loos:[13,16],lose:[8,27],loss:[15,22],lost:[15,22],lot:[3,10,13,16],low:[2,9,10,11,13,14,15,16,27,29],lower:[3,4,12,13,14,16,26,27],lowest:2,lsusb:[19,27],m4f:9,mac:[6,9,19],machin:[10,11,13,16,19],maco:[1,19],macport:19,made:[7,9,19,26],magnet:[10,11,13,15,16],magnitud:[12,13,16],mai:[2,3,4,6,8,9,10,11,13,15,16,19,21,22,25,26],main:[5,9,13,14,16,19,25,27],major:9,make:[2,5,6,7,8,9,10,12,13,15,16,19,22,25,26],makefil:5,manag:[13,16,19,24,27],mani:[2,3,10,13,16,24],manner:11,manual:[1,2,10,13,16,19,26],map:[0,15,22],margin:[13,16],mark:[9,20,25],marus25:6,mass:[8,10,13,16],master:[3,9,13,16,19],match:[5,9,15,19],mathemat:7,matplotlib:[9,13,16,19,25],max:[3,10,12,13,15,16,22,23],max_allowed_curr:12,max_dc_calib:12,max_endstop:[11,12],max_endstop_press:12,max_length:12,max_regen_curr:12,max_stack_usage_analog:12,max_stack_usage_axi:12,max_stack_usage_can:12,max_stack_usage_startup:12,max_stack_usage_uart:12,max_stack_usage_usb:12,maxim:[13,16],maximum:[0,4,8,12,13,16,24],mbp:[2,12],mcp2515:[2,9,25],mcu:9,mean:[1,7,10,11,12,13,15,16],meaning:[9,25],meant:7,measur:[0,4,7,10,12,13,15,16,26],mecahnc:12,mech_brak:12,mechan:[7,9,10,11,12,13,16,17,20,25],mechanical_brak:12,mechanical_pow:12,mechanical_power_bandwidth:12,mechanicalbrak:12,meed:12,memori:[2,4,12,13,15,16,19],mention:27,menu:[13,16,19,27],mere:9,merg:9,messag:[2,3,19,21],metal:[13,16],meter:7,method:[2,13,16,19],mhz:[2,12],micro:[9,25],microcontrol:[2,27],microsecond:7,might:[9,10,13,14,16,18],migrat:9,milisecond:11,min:[12,15,22],min_endstop:[11,12],min_endstop_press:12,min_heap_spac:12,mind:[8,14],minim:11,minimum:[4,11,12],minut:23,mirror:12,mirror_ratio:12,misconfigur:12,mismatch:27,miso:10,mitig:10,mix_channel:12,mkdir:[9,25],mod:15,mode:[6,7,9,10,11,12,15,19,20,24,25],model:7,modest:[8,15],modif:9,modifi:19,modul:[8,13,16,27],modulation_is_nan:12,modulation_magnitud:12,module_nam:[13,16],modulo:12,moment:27,more:[1,2,3,6,7,8,9,10,11,12,13,14,15,16,17,18,19,21,22,23,24,25,26,27],mosfet:[13,16],mosi:10,most:[2,3,4,7,11,12,13,14,16,24,27],motion:[4,7,8,10],motor:[7,8,9,10,11,12,17,19,23,24,25],motor_calibr:12,motor_error_:27,motor_kv:[13,16],motor_mod:[13,16],motor_rpm:[13,16],motor_thermistor:[12,26,27],motor_thermistor_over_temp:12,motor_typ:[10,12,13,16],motor_type_gimb:[13,16],motor_type_high_curr:[10,13,16],motor_upd:12,motorconfig:9,motortyp:12,mount:[10,27],mous:[9,25],move:[1,2,8,9,11,12,13,15,16,22],move_increment:[8,12],movement:8,msb:21,much:[10,12,14,22],multi:[12,19],multibyt:3,multimet:26,multipl:[7,9,14,25],multiplex:2,must:[0,3,4,8,9,10,11,12,13,14,16,17,20,21,24,25,27],mutual:12,my_config:19,my_odr:19,nCS:[10,20],n_evt_control_loop:12,n_evt_current_measur:12,n_evt_pwm_upd:12,n_evt_sampl:12,name:[1,2,5,6,9,11,13,16,19,21,25,27],nano:[13,16],nativ:[6,27,28,29],navig:[9,19,25],necessari:[4,19,26],need:[2,6,8,9,10,11,12,13,15,16,17,19,21,22,24,25,26,27,28],neg:[10,11,12,14,26],neglig:9,net:9,network:2,never:[2,10,13,16],newer:[9,13,16,19],newest:[6,19,27],newton:7,next:[2,5,7,9,10,15,19,25],nice:2,no_respons:12,node:3,node_id:[2,3,12],nodeid:3,nois:[2,9,12,13,16,20,26],noisi:4,non:[4,7,9,12,13,16,19,21],none:[6,9,19,22,25],noob:[9,25],normal:[1,9,13,14,16,19,27],note:[4,6,7,8,12,13,15,16,19,22,24,27],noth:9,notic:10,nov:9,now:[2,4,6,8,9,10,11,13,15,16,17,19,22,24,25],npn:11,ntc:26,nuke:9,num_sampl:19,number:[1,2,4,10,11,12,13,15,16,19,21],numer:[0,8,22],nvic:12,nvm_test:[9,25],objcopi:19,object:[4,15,27],observer_gain:12,obtain:21,obviou:3,occur:[2,17,27],occurr:9,ocd_bounc:9,oct:[13,16],oddli:7,odriv:[0,1,4,6,7,8,10,11,14,15,17,18,22,24,25,26,27,28,29],odrive_demo:18,odrive_test_rig_nam:[9,25],odrive_workspac:[6,9],odrivearduino:1,odriveenum:9,odrivefirmar:5,odrivefirmwar:[5,9,19,25],odrivefirmware_v3:19,odriverobot:[13,16],odrivetest:[9,25],odrivetool:[0,2,4,6,9,10,11,12,18,25,26,27],odrv0:[0,2,3,4,7,8,10,11,13,15,16,17,18,19,20,22,26,27,28,29],odrv:[0,2,3,4,9,10,11,20,24,25,27],off:[2,9,15,19,22],offboardthermistorcurrentlimit:12,offer:14,offici:[9,12,18,19],offset:[9,10,12,13,15,16,19,25],ohm:[2,10,13,16,26,27],okai:[10,13,16],old:[4,9,21],omit:1,onboard:[26,27],onboardthermistorcurrentlimit:12,onc:[1,9,10,11,13,16,25,26],one:[1,2,3,7,8,9,10,11,12,13,14,16,19,21,22,24,25,26],onli:[1,2,6,7,8,9,10,11,12,13,15,16,19,20,21,26,27],onlin:[19,21],onto:[9,25,27],onward:20,open:[1,2,4,6,9,11,13,16,19,25,27],open_loop_controller_upd:12,openocd:[5,6,19,25],openocd_script:9,oper:[9,10,12,15,20,21,22,24,26],opt:[9,25],option:[1,5,8,9,11,12,13,14,16,25,27],optocoupl:14,orang:[8,19],order:[4,8,10,11,13,15,16,21],org:9,orient:1,origin:[2,19],oscil:[2,9,13,16,25],oscilloscop:[10,12],osx:[13,16],osxfus:9,other:[2,3,4,10,11,12,14,15,17,19,20,21,24,28],otherwis:[2,12,19],otp_valid:12,our:[2,3,10,11,13,16],out:[1,3,6,9,10,11,12,13,15,16,24,26,27,29],output:[2,7,12,13,15,16,19,21,22,26,27],outsid:8,over:[2,3,6,7,9,11,12,13,15,16,19,21,27],over_temp:12,overal:7,overcom:10,overheat:[13,16],overkil:23,overlai:[9,25],overlap:10,overrid:9,overshoot:7,overspe:12,overvoltag:[12,13,16],own:[13,16,26,29],p_brake:[13,16],p_gain:12,pack:[6,13,16],packag:[9,13,16,19,27],packet:29,pacman:9,page:[13,16,27,29],pai:[13,16],pain:2,pair:[2,4,9,10,15,23,25,27,29],panel:9,parallel:27,paramet:[2,3,7,9,11,12,19,22,24],parameter:4,part:[2,4,7,17,18,19],parti:21,partial:[9,25],particular:[2,10,19,29],passthrough:12,passwordless:[9,25],past:[9,13,16,25],path:[6,9,13,14,16,19,25,27],paulstoffregen:[9,25],paus:6,payload:[3,21],pdate:1,pdb:[9,25],pdf:[12,23],pdo:3,peak:[13,16,19,23],pend:24,peopl:[10,13,16,19],per:[3,5,7,13,15,16,17,19,23,24],perfect:7,perform:[7,10,13,15,16],period:14,peripher:9,perman:[13,15,16],permiss:[13,16],perpect:7,persist:[4,13,15,16],pertain:15,phase:[12,13,15,16],phase_current_rev_gain:12,phase_induct:[12,15],phase_inductance_out_of_rang:12,phase_offset:[10,12],phase_offset_float:[12,15],phase_resist:[12,15],phase_resistance_out_of_rang:12,phase_vel:12,phy:2,phyiscal:11,physic:[2,7,9,11,12,25,26],pick:9,pictur:19,pid:7,piec:[13,16],pile:9,pin:[2,10,11,12,14,15,19,20,22,24,26,27,28],pinout:[9,15,22,28],pip3:[2,9,13,16],pip:[9,13,16,18,27],pjrc:[9,25],place:[9,11,14],placehold:4,plain:9,plan:[8,9],planner:8,plastic:27,platform:19,pleas:[9,13,16,24],pll_bandwidth:12,pll_po:12,plot:[7,12,19],plot_rat:19,plug:[9,15,19],plugin:5,pm_flux_linkag:[4,12],pnp:11,pod:21,point:[8,9,11,12,13,14,16,17,19,24,25],polar:[10,13,15,16],pole:[4,10,15,23],pole_pair:[4,12,13,15,16],poly_coefficient_0:[12,26],poly_coefficient_1:12,poly_coefficient_2:12,poly_coefficient_3:[12,26],polynomi:21,poorli:15,popd:[9,25],popular:15,port:[1,9,10,12,13,16,19,28,29],portion:7,pos:1,pos_ab:12,pos_amplitud:12,pos_circular:[8,12],pos_cpr_count:12,pos_error:7,pos_est_count:4,pos_estim:[4,7,8,12,17,19],pos_estimate_circular:17,pos_estimate_count:12,pos_feedback:7,pos_filt:12,pos_gain:[7,12,15,17,27],pos_incr:8,pos_setpoint:[4,7,12,17],posit:[2,9,10,11,12,14,15,17,19,24,25,26],position_control:12,possibl:[4,7,8,9,10,11,12,13,14,16,19,24,25,26],post:[2,27],potenti:[7,13,16],power:[4,9,10,12,15,17,19,22,23,24,26,27],ppa:9,ppr:[13,16],pr2:9,practic:[11,14,21],pre_calibr:[10,12,15],precis:[8,9],predict:7,prefer:11,preflash:[13,16],prepar:[9,25],prerequisit:[6,25],present:[10,12,13,16,19,27,29],presist:15,press:[5,6,9,11,13,16,19,25],pretti:2,prevent:[10,11,12,27],previou:[8,13,16,27],primarili:12,primit:24,principl:26,print:[2,4,10,13,15,16,18],printer:11,printf:9,printout:19,prio_analog:12,prio_axi:12,prio_can:12,prio_startup:12,prio_uart:12,prio_usb:12,prior:27,prioriti:2,privileg:19,probabl:[10,13,14,15,16,19],problem:[9,10,13,16],problemat:12,procedur:[7,9,10,13,16,19,24,27],process:[2,6,9,10,11,27],processor:[6,9,12],produc:[13,16],product:[9,12,13,16],profil:[7,12],program:[1,5,6,9,13,16,18,19,27],program_error:9,programm:[5,9,19],progress:9,project:[6,9],prompt:[9,13,16,19],properli:[2,10,13,16,19],properti:[0,1,3,11,12,13,16],proport:[7,8,12,14],protect:[12,13,14,16],protocol:[2,4,9,10,12,13,16,25,28,29],provid:[1,9,13,16,19,21,29],proxi:6,psu:27,ptc:26,pull:[13,14,16],puls:[10,13,16,23],pump:[13,16],purchas:[13,16],purpos:[10,12,19,20],push:[9,13,16],pushd:[9,25],put:[2,3,11,19,24,26],putti:1,pwd:9,pwm0:20,pwm:[0,9,12,13,14,16,25],pwm_input_test:[9,25],pwm_updat:12,pypi:9,python36:[13,16],python3:[2,9,13,16,25,27],python:[2,9,13,16,19],pyusb_debug:27,pyyaml:9,quadratur:10,qualifi:9,quarter:11,question:[13,16],quickli:[2,4,7,8,10,19],quit:[9,11,13,15,16,19,25],r_25:26,r_wl_ff_enabl:12,radian:[4,17],rais:[4,13,16,27],ramp:[4,7,11,12],ramp_dist:[10,12],ramp_tim:12,random:15,rang:[3,8,10,12,13,14,15,16,17,22,23,24,26],rare:11,raspbian:[9,25],rate:[2,3,8,13,15,16,19,24,26],rather:[3,10,13,16],ratio:[12,13,16],raw:[8,12,13,16],reach:[4,10,12,19],read:[0,2,6,9,10,11,12,13,15,16,19,21,26],readabl:1,readi:[2,6,10],readm:9,readout:2,real:[11,13,16,19],realist:27,realiz:[9,11],realli:[13,16],realtim:1,reason:[13,15,16,19],rebas:9,reboot:[1,3,4,9,10,11,12,13,15,16,17,20,22,25,28],receiv:[2,3,9,10,15,19,21,22],recent:[2,6,9,12,27],reciev:[10,13,14,16],recommend:[1,2,6,8,9,10,13,15,16,18,26],red:15,reduc:[8,13,15,16,26,27],reenabl:28,ref:[9,13,15,16],refactor:9,refer:[0,7,8,11,13,16,18,19],referenc:21,reference_manu:12,reflect:[9,10,21],regardless:11,regen:[9,25],regener:9,regex:9,regular:[8,13,16,19],reject:8,rel:[2,8,12],relat:[7,9,13,16,26],relationship:4,relaunch:6,relax:10,releas:[6,12,13,16,19,27],relev:[4,11],reliabl:[11,13,15,16],reload:[13,16],remain:24,remark:12,rememb:[13,16],remot:[9,22,25],remote_object:27,remoteobject:27,remount:27,remov:[2,4,7,9,17,19,25],renam:[5,9,19],reopen:[13,16],repeat:[10,15,27],repeatedli:11,replac:[9,13,16,19],repo:[9,25],report:[9,10,12],repositori:[6,9,25],repres:[2,4,11,12],represent:[1,2,21],request:[4,9,13,15,16],requested_current_rang:[12,13,15,16],requested_st:[4,10,12,13,15,16],requir:[2,6,9,10,11,12,14,15,17,26],rerun:9,resent:21,reserv:3,reset:[4,6,9,19],resist:[2,10,12,13,15,16,26],resistance_calib_max_voltag:[10,12,15],resistor:[2,10,12,14,17,26,27],resolut:[0,10,15,27],resolv:27,resourc:12,respect:[2,9,10,13,16,23],respond:[3,7],respons:[1,3,8,12,19],rest:7,restart:[6,10,27],restor:[13,16,19],restructuredtext:9,result:[3,7,8,9,10,12,15,17,21,24],resum:5,retain:27,retri:[13,16],retriev:19,review:9,revolut:[13,15,16,23],ribbon:10,ridg:27,rig:15,right:[5,19,24,27],ring:27,rise:[13,16],risk:[13,16],rload:26,robot:[8,13,15,16],robust:2,roll:8,root:[5,6],rotat:[4,8,10,13,16,24,27],rotor:[10,12,13,16,27],rotor_flux:12,rough:7,roughli:12,round:[8,17],rout:[9,27],rpi:[9,14,25],rpm:[4,19,23],rs485:[9,25],rsync:[9,25],rtd:9,rtr:3,rubi:[13,16],rule:[9,13,16,25,27],run:[1,4,5,6,7,9,10,12,13,15,16,18,19,21,25,28,29],runawai:[13,16],runtim:[13,16],rx_cnt:12,s19:19,safe:[13,14,16],safest:[15,22],safeti:8,sai:[7,8,13,16,19,24],same:[2,3,4,8,9,10,11,12,13,14,16,21,23,26,28],sampl:[12,19],satisfi:9,save:[1,2,9,10,11,12,15,17,19,22],save_configur:[3,4,10,11,12,13,15,16,20,22],scale:19,scari:9,scenario:12,schema:7,schemat:[9,25,26],scientif:[13,16],scipi:[9,25],sck:10,scl:20,screen:[1,6,9,25],screenshot:26,screw:[13,16,27],script:[2,9,13,16,19,25,27],scroll:[13,16],sda:20,search:[9,12,25,27],sec:[8,11],second:[3,7,8,10,11,13,16],section:[2,6,8,9,12,13,16,19,25,27],secur:27,see:[1,2,4,5,7,8,9,10,11,12,13,15,16,17,18,19,21,22,23,24,25,26,27,29],seem:9,seen:[1,13,16,19],select:[2,4,5,6,9,10,19,25],self:27,sell:[13,16],semant:21,send:[2,3,8,10,13,16,21],sens:[9,15],sensit:15,sensor:[11,12,13,15,16],sensorless:[9,13,16,25,27],sensorless_estim:[4,12,17],sensorless_estimator_upd:12,sensorless_ramp:[4,12],sensorlessestim:12,sent:[3,8,10],separ:[3,9,13,16,19,29],sequenc:[4,13,16,21],sequenti:[2,3],seri:[10,14,21,26,27],serial0:[9,25],serial:[1,2,4,9,19,21,25],serial_numb:[4,12],serv:11,server:[19,21],session:[6,9,25],set:[3,6,7,8,10,11,12,15,19,20,21,22,24,26,27,28],set_linear_count:12,set_motor_thermistor_coeff:26,setpoint:[1,7,8,12,13,15,16,17,22,24],settl:7,setup:[3,4,9,10,11,13,16,22,25,26],sever:[10,19],shadow_count:[10,12],shaft:27,shake:15,shall:21,share:[9,14,25],sheet:10,shell:[6,19,27],shield:10,shift:6,ship:[13,16],shop:[13,16],shortcut:19,should:[2,3,4,5,6,7,8,9,10,12,13,14,15,16,19,21,22],shouldn:15,show:[1,2],shown:[4,9,19,26,27,28],shunt:[13,16],side:[9,10,13,14,16,29],sign:19,signal:[2,12,13,14,15,16,17,22,24,27],signific:[3,10],significantli:9,sigrok:10,silkscreen:[11,13,16],similar:[0,1,2,9,17,19],simpl:[2,3,11,12],simplest:[9,18,24,25],simpli:[1,6,7,8,9,13,16,19],simplifi:[10,13,16],simultan:[20,29],sin:[9,12,25],sinc:[9,10,12,13,15,16,19],sinco:12,sincos_gpio_pin_co:12,sincos_gpio_pin_sin:12,sine:12,singl:[2,3,7,9,12,13,14,16,21,25,29],sink:12,site:[19,27],size:[12,21],skip:[4,10,12,15],slate:9,slide:[13,16],slightli:[13,16,27],slip:[12,13,16,27],slip_vel:12,slip_veloc:12,sloppi:15,slow:[13,14,16],slowli:[13,16],small:[12,13,16],smaller:[13,16],smooth:[8,11],smoother:[12,15],smoothli:8,socket:2,socketcan:2,softwar:[9,13,16],solder:[2,10],solut:[9,10],some:[1,3,4,5,6,7,9,10,11,12,13,14,15,16,19,25,27,28],someon:18,someth:[9,12,13,16,19,25,27],sometim:[6,10,13,16],somewhat:[13,16],soon:[12,13,16],sooner:3,sorri:19,sort:[10,11],sound:9,sourc:[8,9,10,12,13,16,19,23,26],space:[3,8,9,12],spark:[13,16],spec:10,special:[9,20],specif:[6,8,9,11,12,13,16,18,19,21,26,27,29],specifi:[3,9,12],speed:[2,4,8,9,10,11,13,14,16],sphinx:9,spi0:2,spi:[2,9,11,20,25],spi_a:12,spi_abs_aeat:12,spi_abs_am:12,spi_abs_cui:12,spi_abs_ma732:12,spi_abs_rl:12,spi_error_r:12,spike:19,spin:[10,13,15,16],spinout:12,spinout_detect:12,spinout_electrical_power_threshold:12,spinout_mechanical_power_threshold:12,spring:[10,13,16],spuriou:9,squar:[10,19],st_com:9,stabl:[7,13,15,16],stack:2,stack_size_analog:12,stack_size_axi:12,stack_size_can:12,stack_size_startup:12,stack_size_uart:12,stack_size_usb:12,stage:[7,13,16],stai:24,stair:8,staircas:8,stand:1,standalon:[13,16],standard:[2,15],star:14,start:[2,4,5,6,7,8,9,10,11,12,15,17,18,19,22,24,25,26],start_anticogging_calibr:12,start_liveplott:[7,19],start_tim:12,startswith:27,startup:[9,12,13,16,19,22,24,27],startup_closed_loop_control:[4,12,15],startup_encoder_index_search:[4,10,12],startup_encoder_offset_calibr:[4,12],startup_hom:[11,12],startup_motor_calibr:[4,12],startup_sequ:12,state:[9,11,12,13,15,16],stationari:[13,16],stator:[13,16],stator_phas:12,stator_phase_vel:12,statu:[9,12,15],stdout:[9,12],step:[0,5,6,7,8,9,10,12,13,14,15,16,19,20,25],step_dir_act:12,step_dir_always_on:12,step_dir_test:[9,25],step_gpio_pin:[12,24],steps_per_circular_rang:[12,17,24],stick:[15,22],stiff:15,still:[4,7,8,9,10,12,13,14,16,19,21,23],stlink:25,stlinkv2:[9,25],stm32:19,stm32cube:9,stm32cubef2_apr2014:9,stm32cubemx_pron_pr:9,stm32cubeprogramm:19,stm32f4:6,stm32f4x:19,stm32f4x_stlink:[9,25],stm32f722:9,stm:[12,19],stock:15,stop:[2,4,6,10,11,12,13,16,26,27],storag:[9,25],store:[4,10],str:18,straight:[13,15,16],straightforward:3,strang:10,stream:[1,12],stream_num:12,stream_protocol_type_ascii_and_stdout:28,stream_protocol_type_fibr:28,streamprotocoltyp:12,strictli:[3,6,8,9,26],struct:21,structrur:9,stuck:19,studio:9,stupid:9,style:[7,9,11],subject:[9,25],submodul:12,subsequ:[9,25],substitut:[13,16],subsystem:[9,13,16,25],subtl:10,success:[10,15,19],sudo:[2,9,13,16,19,25],suffici:3,suggest:[4,11,13,16],suitabl:15,sum:12,suppli:[4,12,13,14,16,22,23,27],support:[1,2,4,6,9,11,12,13,16,19,20,26],suppos:[9,25],suppress:20,sure:[2,5,6,7,8,9,10,12,13,15,16,19,22,25,26,27],suscept:10,svm:[13,16],swap:10,swc:9,swd:9,swing:14,swtich:19,symbol:5,symptom:10,sync:[9,10,21,25],sys:19,system32:19,system:[7,8,9,10,11,13,16,24,27],system_level:12,system_stat:12,systemctl:[9,25],systemstat:12,tab:[6,19],tabl:[11,12],tag:9,take:[4,7,9,10,11,12,13,16,17,20,27],taken:26,talk:[1,2,13,16,18],tar:[9,25],target:[19,25],task:6,task_tim:12,task_timers_arm:12,tasktim:12,tcl:9,td_153:[9,25],team:9,technic:12,tee:[9,13,16,25],teensi:[9,25],teensy_loader_cli:[9,25],teensyduino:[9,25],teensyduinoinstal:[9,25],tell:[9,10,13,15,16,22],temp_limit_low:[12,26],temp_limit_upp:[12,26],temperatur:[12,26],temporari:27,tenabl:9,tend:21,tension:27,term:[1,2,7,29],termin:[2,6,13,16,19],test:[6,13,15,16,24],test_funct:12,test_properti:12,test_runn:[9,25],texa:14,text:[1,6,7,19],textbf:7,than:[2,3,4,8,10,12,13,14,16,17,21,26,28],thank:2,thankfulli:10,thei:[2,3,4,6,9,10,13,14,16,19,21,25,26],them:[4,6,9,10,13,14,16,19],theme:9,themselv:26,theoret:[7,21],therefor:[9,15,25],thermistor:12,thermistor_current_limiter_error:27,thermistor_upd:12,thermistorcurrentlimit:12,thi:[0,1,2,3,4,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29],thicker:15,thing:[9,10,26],think:[7,13,16],thinner:15,those:[3,9,14],though:[2,9,12,13,16],three:[7,10,12,13,15,16],threshold:[12,26],through:[3,7,9,11,13,14,16,21],thu:[3,15,26],tick:5,tie:10,tied:10,tight:27,time:[1,2,6,7,8,9,10,11,12,13,14,15,16,19,21,27,28],timeout:[12,13,15,16,22],timer:1,timer_update_miss:12,timestamp:2,titl:9,tmax:26,tmin:26,tmp:9,todo:25,togeth:[2,10,14,24,27],toggl:2,toler:[4,13,16,20,28],too:[3,6,8,10,14,22,27],took:9,tool:[1,6,7,9,13,16,18,22,25,27],toolbar:5,toolchain:[5,6],top:[2,6,9,13,16,19],torqu:[1,7,12,17,19],torque_amplitud:12,torque_cmd:7,torque_const:[7,12,13,15,16,17,19],torque_control:12,torque_ff:1,torque_lim:[1,12],torque_mirror_ratio:12,torque_ramp:12,torque_ramp_r:[12,17],torque_setpoint:[12,17],total:21,touch:[13,16],toward:[11,19],traceback:27,track:[1,7,8,9,10,15,27],traffic:27,trajectori:7,trajectory_don:12,transact:21,transceiv:2,transfer:[9,19,25],transit:[11,12],translat:12,transmit:2,transmitt:22,transport:9,trap_traj:[8,11,12,17],trapezoid:7,trapezoidaltrajectori:12,travel:11,travers:24,treat:29,tree:9,tri:[4,8],trigger:[9,10,11,12,13,16,25],trip:[13,16],tristat:10,troubl:[13,16],troubleshoot:[13,16],tty:1,ttyacm0:1,tune:[8,12,13,15,16,27],tunnel:[9,25],tup:[6,9,25,27],turn:[1,2,4,7,8,9,10,11,12,13,15,16,17,19,22,24,27],turns_per_step:17,tutori:2,twist:[2,10,27],two:[2,3,4,9,10,11,12,13,16,19,24,26,27,29],tx_cnt:12,tx_overrun_cnt:12,txt:[9,25],type:[1,2,9,10,11,14,19,21,25,26,27],typic:[11,15],uart0_protocol:[12,28],uart1_protocol:12,uart2_protocol:12,uart:[1,10,12,14,15,18,21],uart_a:[12,20,28],uart_a_baudr:[12,28],uart_ascii_test:[9,25],uart_b:[12,20,28],uart_b_baudr:12,uart_c:12,uart_c_baudr:12,uart_protocol:9,ubuntu:9,udev:[9,13,16,25,27],udevadm:[9,13,16,25],uint16properti:12,uint32properti:12,uint64properti:12,uint8properti:12,ultim:7,unabl:9,unaffect:17,unavail:5,unbalanced_phas:12,undefin:12,under:[9,19,25,26],undersid:[10,13,16],understand:[3,10,13,16,19],undertak:27,unfilt:[5,8,13,16],unfortun:[3,19],uniniti:20,uniqu:[2,4],unit:[9,11,12,13,16],unix:1,unknown_current_command:12,unknown_current_measur:12,unknown_gain:12,unknown_phase_estim:12,unknown_phase_vel:12,unknown_posit:12,unknown_torqu:12,unknown_vbus_voltag:12,unknown_voltage_command:12,unless:[9,10,12,13,16,24],unlik:[13,16],unlock:[7,13,16],unlucki:[13,16],unnecessarili:[9,12],unplug:[9,15,19,22],unreason:[13,16],unstabl:7,unstable_gain:12,unsupport:20,unsupported_encoder_mod:12,until:[4,7,10,11],unus:24,upcom:7,updat:[6,7,9,12,13,16,17,25],upgrad:[9,19,25,27],upper:[3,4,9,12,26],ups:[13,16],upstream:9,uptim:12,upward:7,usag:3,usb:[1,2,3,4,9,12,13,14,16,18,19,21,25],usb_cdc_protocol:12,usb_protocol:9,usbmodem:1,use:[1,3,4,6,7,8,9,10,11,12,13,14,15,16,17,18,19,21,24,25,26,27],use_index:[10,12],use_index_offset:12,used:[0,1,2,3,4,7,8,9,10,11,12,13,15,16,17,19,20,24,25,26,27,29],useful:[8,11,13,15,16,19,20],user:[3,4,9,10,12,13,16,17,25,27],user_config_load:12,uses:[0,2,7,9,17,18,26],using:[1,2,3,4,6,7,8,10,11,13,14,15,16,17,19,21,22,26],usr:[9,13,16,25],usual:[4,9,10,12,13,15,16,26],util:[2,13,16,19,27],v_1:14,v_2:14,v_current_control_integral_d:12,v_current_control_integral_q:12,v_emf:[13,16],val:[13,16],valid:[1,10,12,13,16],valu:[1,2,3,4,6,7,8,9,10,11,12,14,15,17,19,21,24,25,26],vari:10,variabl:[4,6,9,10,11,12,13,16,19,27],variant:[13,16,21,29],variat:7,varieti:[7,13,16],vbus_voltag:[1,12,13,16,18],vcc:9,vcca:26,vd_setpoint:12,vdd:10,vdq:12,vehicl:2,vel:[1,4,10,12],vel_amplitud:12,vel_cmd:7,vel_error:7,vel_est_count:4,vel_estim:[4,12,17,19],vel_estimate_count:12,vel_feedback:7,vel_feedforward:7,vel_gain:[4,7,12,15,17,27],vel_gain_old:17,vel_integr:7,vel_integrator_gain:[4,7,12,15,17],vel_integrator_gain_old:17,vel_integrator_limit:12,vel_integrator_torqu:12,vel_limit:[4,8,10,11,12,13,15,16,17],vel_limit_toler:12,vel_ramp:12,vel_ramp_r:[8,11,12,17],vel_setpoint:[4,12,17],veloc:[9,12,15,17,19,22,25],velocity_control:12,velocity_ff:1,velocity_lim:1,vendor:[12,14,29],ver:19,verbos:27,veri:[2,3,8,9,13,15,16,19],verifi:[9,10,19,21,27],verion:[13,16],version:[2,4,9,12,13,16,17,19,21,23,25,27],via:[1,2,3,8,9,25],vibrat:[7,10,13,16],video:[10,13,16],view:[4,6,9],viewer:2,violent:27,virtual:[12,29],visibl:0,visual:9,volatil:[4,12],volt:[13,16],voltag:[0,2,4,7,9,10,12,13,14,15,16,23],voltage_cmd:7,voltage_control:12,voltage_feedforward:7,voltage_integr:7,vq_setpoint:12,vscode:[6,9,25],wai:[2,3,4,7,8,10,13,14,16,21,24,26],wait:[2,13,16,19],walkthrough:[13,16],want:[2,3,4,9,10,11,12,13,15,16,18,19,22],warmli:[13,16],warn:[9,19],watchdog:12,watchdog_fe:[12,13,16],watchdog_timeout:[12,13,16],watchdog_timer_expir:12,wave:[10,12],weak:[10,13,16],weight:6,welcom:[9,13,16,18],well:[4,10,11,19,26,27],went:10,were:[9,12,17,27],wget:[9,25],what:[1,4,9,14,15,18,19,26,29],whatev:[15,22],wheel:15,when:[1,3,4,6,7,10,11,12,13,15,16,17,19,20,24,26],whenev:9,where:[1,2,4,7,8,10,13,14,15,16,19,24,29],wherea:11,wherev:[13,16],whether:[9,12,14,26],which:[2,3,4,6,7,8,9,10,11,12,13,15,16,19,21,22,25,26,27,29],whitespac:9,who:9,whole:7,whose:[6,9],why:10,wide:[2,7],wider:26,wikipeda:2,win32:[19,27],win:2,wind:27,window:[1,6,9,13,16,19,27],winusb:27,wire:[10,11,19,22,27],wireshark:27,wise:[13,16],wish:[8,9,10,13,16],within:[2,8,9,10,12,13,16,22],without:[1,2,4,7,8,9,12,15,22],won:[1,6,13,16],word:4,work:[2,3,4,6,9,10,11,12,14,15,17,18,19,26,27],workaround:10,workflow:[9,25],workspac:[6,9],wors:10,would:[0,3,7,8,9,10,11,13,14,16,19],wrap:[7,8,24],wrapper:21,writabl:22,write:[2,4,15,18,21,22],write_imag:19,written:[1,13,16],wrong:[8,11,14,27],wrote:19,www2:9,www:[9,12,25],x86:[6,9],xct:2,xor:[1,21],yaml:[9,25],yellow:15,yes:19,yet:[9,18,29],you:[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,24,25,27,28,29],youhav:9,your:[1,2,4,5,6,7,8,9,10,11,12,13,14,15,16,19,20,21,22,23,25,26,27],your_usernam:[13,16],yourself:[2,13,16,18,21],zadig:[19,27],zero:[7,11,12],zip:[9,25]},titles:["Analog Input","ASCII Protocol","CAN Bus Guide for ODrive","CAN Protocol","Parameters & Commands","Setting up Eclipse development environment","Configuring Visual Studio Code","Control Structure and Tuning","Control Modes","ODrive Firmware Developer Guide","Encoders","Endstops and Homing","ODrive Reference","Getting Started","Ground Loops","Hoverboard motor and remote control setup guide","Getting Started","Migration Guide","Native Protocol","odrivetool","ODrive v3.x Pinout","ODrive Communication Protocol","RC PWM input","Specifications","Step/Direction","Automated Testing","Thermistors","Troubleshooting","UART Interface","USB"],titleterms:{"24v":23,"5000":27,"56v":23,"import":5,"while":27,Bus:2,IDE:9,The:[9,11,14,25],With:[10,13,16],addit:11,affect:17,algorithm:21,altern:19,analog:0,api:17,arduino:1,ascii:1,autom:[9,25],automat:15,axi:4,back:27,backup:19,behavior:17,board:23,brake:[13,16,17],build:[6,9,27],bus:2,calibr:[10,13,16],can:[2,3],cannot:9,canopen:3,chang:[17,27],circuit:26,circular:8,clean:6,code:[6,9,27],coeffici:26,command:[1,2,4],commun:[2,21],communc:2,configur:[3,4,5,6,9,11,13,15,16,19,24],connect:[26,27],constant:[13,16,27],contributor:9,control:[4,7,8,13,15,16,17],crc16:21,crc8:21,crc:21,current:[1,4,7,13,14,16],custom:19,cut:27,dbc:2,debounce_m:11,debug:[6,9],detail:7,develop:[5,9],devic:[11,19],dfu:19,diagnost:4,dimens:23,dir:27,direct:[17,24,27],divid:26,document:9,download:[13,16],dump_error:27,earlier:[13,16],eclips:5,effect:10,electr:23,enabl:[11,13,16],encod:[4,10,13,16],endstop:11,environ:5,error:27,estim:4,exampl:[2,3,11],fail:10,failur:27,fals:27,famili:9,feedback:[1,10],feel:27,fet:26,file:[2,23],filter:8,firmwar:[6,9,13,16,19,27],flash:[6,9,19,27],flow:[9,25],format:[1,21],forth:27,frame:3,from:[13,16,19],gener:[4,9],get:[13,16,27],give:27,gpio3:28,gpio:[11,17],gpio_num:11,ground:14,guid:[2,9,15,17],hall:10,happen:10,hardwar:[2,13,16],has:27,high:27,hole:23,home:11,host:[9,25],hot:27,hoverboard:[13,15,16],how:[9,24,25,28],identifi:9,increas:27,index:10,input:[0,4,15,22],instal:[5,13,16,19],instanc:19,interact:19,interfac:28,interoper:3,introduct:26,is_active_high:11,isol:14,issu:27,known:10,languag:18,later:[13,16],launch:5,less:27,libfibr:9,libusb_error_io:9,like:27,limit:[13,14,16],liveplott:19,load:[5,27],loop:[7,14],machin:4,mai:27,maintain:9,mainten:9,make:27,mechan:23,migrat:17,mode:[4,8,13,16,17],modifi:9,monitor:4,motor:[1,4,13,15,16,26,27],mount:23,multipl:19,name:17,nativ:18,neg:[13,16],next:[13,16],nois:[10,27],note:[9,10,20],odriv:[2,3,9,12,13,16,19,20,21,23],odriveool:[13,16],odrivetool:[13,16,19],off:27,offset:11,openocd:9,oscil:27,other:[13,16,18,27],our:[9,25],outlin:23,overshoot:27,packet:21,pair:[13,16],paramet:[1,4,8,13,16,17],per:4,perform:11,pinout:[10,20,24],pole:[13,16],posit:[1,4,7,8,13,16,27],power:[13,14,16],prerequisit:9,previous:17,problem:14,procedur:[4,6],produc:27,project:5,protocol:[1,3,18,21],pwm:[15,22],python:[18,27],ramp:8,raspberri:[2,9,25],raspbian:[13,16],read:1,reduc:14,refer:[1,12],releas:9,remot:15,request:[1,21],requir:[13,16],resistor:[13,16],respons:21,revers:10,rig:[9,25],rpm:27,run:27,safeti:[13,15,16],save:[4,13,16],schemat:23,search:10,send:1,sensorless:4,sequenc:[10,11],set:[2,4,5,9,13,16,17,25],setup:[2,6,15],should:27,signal:10,sit:27,slowli:27,solut:14,specif:23,speed:27,spi:10,spin:27,ssh:[9,25],start:[13,16,27],startup:[4,10,11,15],state:4,stationari:27,step:[17,23,24,27],still:27,stlink:[9,19],stm32:9,stm32cubemx:9,stream:21,structur:7,studio:6,support:10,system:[1,4],tabl:20,target:[9,27],test:[9,10,11,25],than:27,thermistor:[13,16,26],timer:[13,16],titl:20,todo:9,tool:19,torqu:[4,8,13,16,27],trajectori:[1,8],transport:3,troubleshoot:[9,19,27],tune:7,type:[13,16],uart:28,ubuntu:[13,16],uncontrol:27,under:27,unit:17,updat:[1,19],upgrad:[13,16],usag:8,usb:[27,29],use:[2,28],using:[9,27],valu:[13,16],variabl:17,veloc:[1,4,7,8,13,16],verifi:2,vibrat:27,visual:6,voltag:26,watchdog:[1,13,16],what:[2,10,13,16,27],when:[9,27],why:2,wire:[2,13,14,15,16],without:[10,13,16],work:[13,16],write:1}}) \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/specifications.html b/docs/reStructuredText/_build/html/specifications.html new file mode 100644 index 00000000..030d1c77 --- /dev/null +++ b/docs/reStructuredText/_build/html/specifications.html @@ -0,0 +1,214 @@ + + + + + + Specifications — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Specifications

+ +
+

Electrical Specifications

+

Besides the input voltage range, (12V to 24V for ODrive v3.6 24V, 12V to 56V for ODrive v3.6 56V), the electrical specifications of both version of ODrive v3.6 are the same.

+
+

Note

+

ODrive versions after v3.5 are closed-source with respect to board files and schematics.

+
+
+

ODrive v3.6 24V and 56V

+
    +
  • Peak current per motor: 120 Amps

  • +
  • +
    Max continuous current depends on cooling. See this for more details.
      +
    • Heatsink in still air: 40A per channel

    • +
    • Heatsink with basic fan cooling: 75A per channel

    • +
    • Heatsink with overkill fan cooling: 90A per channel

    • +
    +
    +
    +
  • +
  • +
    Max motor RPM: This depends on your power supply voltage, motor, and encoder. It is the lesser of:
      +
    • motor RPM limit

    • +
    • encoder RPM limit

    • +
    • motor KV * 0.7 * Supply voltage

    • +
    • 35000 eRPM / # of motor pole pairs

    • +
    • (840M counts/minute) / encoder counts per revolution (for incremental encoders - 4 x pulses per revolution).

    • +
    +
    +
    +
  • +
+
+
+

Schematic

+

The electrical schematic for ODrive v3.5 is available here in PDF format.

+
+
+
+

Mechanical Specifications

+
+

STEP File

+

A step file for ODrive v3.5 is available here.

+
+
+

Board Outline and Mounting Hole Dimensions

+board dimensions +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/step-direction.html b/docs/reStructuredText/_build/html/step-direction.html new file mode 100644 index 00000000..17a773fa --- /dev/null +++ b/docs/reStructuredText/_build/html/step-direction.html @@ -0,0 +1,215 @@ + + + + + + Step/Direction — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Step/Direction

+ +

This is the simplest possible way of controlling the ODrive. +It is also the most primitive and fragile one. So don’t use it unless you must interoperate with other hardware that you don’t control.

+
+

Pinout

+
    +
  • Step/dir signals: Any GPIOs can be used. Also see Pinout for more info.

  • +
  • GND: you must connect the grounds of the devices together. Use any GND pin on J3 of the ODrive.

  • +
+
+
+

How to Configure

+
+
    +
  1. Choose any two of the unused GPIOs for step/dir input. +Let’s say you chose GPIO7 for the step signal and GPIO8 for the dir signal.

  2. +
  3. Configure the GPIO modes:

    +
    +
    <odrv>.config.gpio7_mode = GPIO_MODE_DIGITAL_PULL_DOWN
    +<odrv>.config.gpio8_mode = GPIO_MODE_DIGITAL
    +
    +
    +
    +
  4. +
  5. Configure the axis:

    +
    +
    <axis>.config.step_gpio_pin = 7
    +<axis>.config.dir_gpio_pin = 8
    +<axis>.config.enable_step_dir = True
    +
    +
    +
    +
  6. +
  7. Enable circular setpoints

    +
    +
    <axis>.controller.config.circular_setpoints = True
    +
    +
    +
    +
  8. +
+
+

After this, step and direction will be enabled when you put the axis into closed loop control. +Note that to change out of step/dir, you need to set <axis>.config.enable_step_dir = False, go to AXIS_STATE_IDLE, and then back into closed loop control.

+

Circular setpoints are used to keep floating point error at a manageable error for systems where the motor can rotate large amounts. +If the motor is commanded out of the circular range, the position setpoint automatically wraps around to stay in the range. +Two parameters are used to control this behavior: <odrv>.<axis>.controller.config.circular_setpoint_range and <odrv>.<axis>.controller.config.steps_per_circular_range. +The circular setpoint range sets the operating range of input_pos, starting at 0.0. The steps per circular range setting controls how many steps are needed to traverse the entire range.

+

For example, to use 1024 steps per 1 full motor turn, set

+
<odrv>.<axis>.controller.config.circular_setpoint_range = 1.0   #[turns]
+<odrv>.<axis>.controller.config.steps_per_circular_range = 1024 #[steps]
+
+
+

The circular range is a floating point value and the steps per circular range parameter is an integer. +For best results, set both parameters to powers of 2.

+

The maximum step rate is pending tests, but 250kHz step rates with both axes in closed loop has been achieved.

+

Please be aware that there is no enable line right now, and the step/direction interface is enabled by default, and remains active as long as the ODrive is in position control mode. +To get the ODrive to go into position control mode at bootup, see how to configure the startup procedure.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/testing.html b/docs/reStructuredText/_build/html/testing.html new file mode 100644 index 00000000..f4fb8656 --- /dev/null +++ b/docs/reStructuredText/_build/html/testing.html @@ -0,0 +1,346 @@ + + + + + + Automated Testing — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Automated Testing

+ +

This section describes how to use the automated testing facilities. +You don’t have to do this as an end user.

+

The testing facility consists of the following components:

+
+
    +
  • Test rig: In the simplest case this can be a single ODrive optionally with a single motor and encoder pair. Can also be multiple ODrives with multiple axes, some of which may be mechanically coupled.

  • +
  • Test host: The PC on which the test script runs. All ODrives must be connected to the test host via USB.

  • +
  • test-rig.yaml: Describes your test rig. Make sure all values are correct. Incorrect values may physically break or fry your test setup.

  • +
  • test_runner.py: This is the main script that runs all the tests.

  • +
  • …_test.py The actual tests

  • +
+
+
+

The Tests

+
+
    +
  • analog_input_test.py: Analog Input

  • +
  • calibration_test.py: Motor calibration, encoder offset calibration, encoder direction find, encoder index search

  • +
  • can_test.py: Partial coverage of the commands described in CAN Protocol.

  • +
  • closed_loop_test.py: Velocity control, position control (TODO: sensorless control), brake regen current hard limit, current control with velocity limiting

  • +
  • encoder_test.py: Incremental encoder, hall effect encoder, sin/cos encoder, SPI encoders (AMS, CUI)

  • +
  • fibre_test.py: General USB protocol tests

  • +
  • nvm_test.py: Configuration storage

  • +
  • pwm_input_test.py: PWM input

  • +
  • step_dir_test.py: Step/dir input

  • +
  • uart_ascii_test.py: Partial coverage of the commands described in ASCII Protocol

  • +
+
+

All tests in a file can be run with e.g.:

+
python3 uart_ascii_test.py --test-rig-yaml ../../test-rig-rpi.yaml
+
+
+

See the following sections for a more detailed test flow description.

+
+
+

Our Test Rig

+

Our test rig essentially consists of the following components:

+
+
    +
  • an ODrive as the test subject

  • +
  • a Teensy 4.0 to emulate external hardware such as encoders

  • +
  • a Motor + Encoder pair for closed loop control tests

  • +
  • a Raspberry Pi 4.0 as test host

  • +
  • a CAN hat for the Raspberry Pi for CAN tests

  • +
+
+

This document is therefore centered around this test rig layout. +If your test rig differs, you may be able to run some but not all of the tests.

+
+
+

How to set up a Raspberry Pi as testing host

+

#. Install Raspbian Lite on a Raspberry Pi 4.0. This is easiest if you have a keyboard, mouse and screen (micro-HDMI!). +I used the NOOBS Lite installer for this. Paste the ZIP-file’s contents onto a FAT32 formatted SD card (fs type 0b in fdisk) and boot it. Then follow the on-screen instructions.

+
    +
  1. Prepare the installation:

    +
    +
    sudo systemctl enable ssh
    +sudo systemctl start ssh
    +# Transfer your public key for passwordless SSH. All subsequent steps can be done via SSH.
    +sudo apt-get update
    +sudo apt-get upgrade
    +# Change /etc/hostname to something meaningful
    +
    +
    +
    +
  2. +
  3. Add the following lines to /boot/config.txt:

    +
    +
      +
    • enable_uart=1

    • +
    • dtparam=spi=on

    • +
    • dtoverlay=spi-bcm2835-overlay

    • +
    • +
      dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25
      +

      Note

      +

      These oscillator and interrupt GPIO settings here are for the “RS485 CAN HAT” I have. There appear to be multiple versions, so they may be different from yours. Check the marking on the oscillator and the schematics.

      +
      +
      +
      +
    • +
    +
    +
  4. +
  5. +
    Remove the following arguments from /boot/cmdline.txt:
      +
    • console=serial0,115200

    • +
    +
    +
    +
  6. +
  7. Append ODRIVE_TEST_RIG_NAME=[test-rig-name] to /etc/environment. The HWIL tests use this to look up the the file [test-rig-name].yaml which is supposed to describe your test rig.

  8. +
  9. Reboot.

  10. +
  11. Install the prerequisites:

    +
    +
    sudo apt-get install ipython3 python3-appdirs python3-yaml python3-jinja2 python3-usb python3-serial python3-can python3-scipy python3-matplotlib python3-ipdb git openocd
    +# Optionally, to be able to compile the firmware:
    +sudo apt-get install gcc-arm-none-eabi
    +
    +
    +
    +
  12. +
  13. Install Teensyduino and teensy-loader-cli:

    +
    +
    sudo apt-get install libfontconfig libxft2 libusb-dev
    +
    +
    +
    wget https://downloads.arduino.cc/arduino-1.8.13-linuxarm.tar.xz
    +
    +
    +
    tar -xf arduino-1.8.13-linuxarm.tar.xz
    +
    +
    +
    wget https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linuxarm
    +
    +
    +
    chmod +x TeensyduinoInstall.linuxarm
    +
    +
    +
    ./TeensyduinoInstall.linuxarm --dir=arduino-1.8.13
    +
    +
    +
    sudo cp -R arduino-1.8.13 /usr/share/arduino
    +
    +
    +
    sudo ln -s /usr/share/arduino/arduino /usr/bin/arduino
    +
    +
    +
    git clone https://github.com/PaulStoffregen/teensy_loader_cli
    +
    +
    +
    pushd teensy_loader_cli
    +
    +
    +
    make
    +
    +
    +
    sudo cp teensy_loader_cli /usr/bin/
    +
    +
    +
    sudo ln -s /usr/bin/teensy_loader_cli /usr/bin/teensy-loader-cli
    +
    +
    +
    popd
    +
    +
    +
    curl https://www.pjrc.com/teensy/49-teensy.rules | sudo tee /etc/udev/rules.d/49-teensy.rules
    +
    +
    +
    +
  14. +
  15. Add the following lines to /etc/udev/rules.d/49-stlinkv2.rules:

    +
    +
    SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", MODE:="0666"
    +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", MODE:="0666"
    +
    +
    +
    +
  16. +
  17. sudo mkdir /opt/odrivetest && sudo chown $USER /opt/odrivetest

  18. +
  19. At this point you need the ODrive repository. See next section to sync it from your main PC. We assume now that you navigated to tools/odrive/tests/.

  20. +
  21. sudo ../../odrivetool udev-setup

  22. +
  23. sudo udevadm trigger

  24. +
  25. Run once after every reboot: sudo -E ipython3 --pdb test_runner.py -- --setup-host

  26. +
+
+
+

SSH Testing Flow

+

Here’s one possible workflow for developing on the local host and testing on a remote SSH host.

+

We assume that the ODrive repo is at /path/to/ODriveFirmware and your testing host is configured under the SSH name odrv.

+

To flash and start remote debugging:

+
    +
  1. Start OpenOCD remotely, along with a tunnel to localhost:

    +
    +
    ssh -t odrv -L3333:localhost:3333 bash -c "\"openocd '-f' 'interface/stlink-v2.cfg' '-f' 'target/stm32f4x_stlink.cfg'\""
    +
    +
    +

    You can keep this open for multiple debug sessions. Press Ctrl + C to quit.

    +
    +
  2. +
  3. Compile the firmware.

  4. +
  5. In VSCode, select the run configuration “Debug ODrive v3.x/v4.x - Remote” and press Run. This will flash the new firmware before dropping you into the debugger.

  6. +
+

To run a test:

+
+
rsync -avh -e ssh /path/to/ODriveFirmware/ odrv:/opt/odrivetest --exclude="Firmware/build" --exclude="Firmware/.tup" --exclude=".git" --exclude="GUI" --delete
+
+
+
ssh odrv
+
+
+
cd /opt/odrivetest/tools/odrive/tests/
+ipython3 --pdb uart_ascii_test.py
+
+
+
+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2021, ODrive Robotics.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/thermistors.html b/docs/reStructuredText/_build/html/thermistors.html new file mode 100644 index 00000000..bd3eb16a --- /dev/null +++ b/docs/reStructuredText/_build/html/thermistors.html @@ -0,0 +1,218 @@ + + + + + + Thermistors — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Thermistors

+ +
+

Introduction

+

Thermistors are elements that change their resistance based on the temperature. +They can be used to electrically measure temperature. +The ODrive itself has thermistors on board near the FETs to ensure that they don’t burn themselves out. +In addition to this it’s possible to connect your own thermistor to measure the temperature of the connected motors. +There are two types of thermistors, Negative Temperature Coefficient (NTC) and Positive Temperature Coefficient (PTC). +This indicates whether the resistance goes up or down when the temperature goes up or down. +The ODrive only supports the NTC type thermistor.

+
+
+

FET Thermistor

+

The temperature of the onboard FET thermistors can be read out by using the odrivetool under <axis>.motor.fet_thermistor.temperature. +The odrive will automatically start current limiting the motor when the <axis>.motor.fet_thermistor.config.temp_limit_lower threshold is exceeded and once <axis>.motor.fet_thermistor.config.temp_limit_upper is exceeded the ODrive will stop controlling the motor and set an error. +The lower and upper threshold can be changed, but this is not recommended.

+
+
+

Connecting Motor Thermistors

+

To use your own thermistors with the ODrive a few things have to be clarified first. +The use of your own thermistor requires one analog input pin. Under <axis>.motor.motor_thermistor.config the configuration of your own thermistor is available with the following fields:

+
    +
  • gpio_pin: The GPIO input in used for this thermistor.

  • +
  • poly_coefficient_0 to poly_coefficient_3: Coefficient that needs to be set for your specific setup more on that in Thermistor coefficients.

  • +
  • temp_limit_lower and temp_limit_upper: Same principle as the FET temperature limits.

  • +
  • enabled: Whether this thermistor is enabled or not.

  • +
+
+
+

Voltage Divider Circuit

+

To measure a temperature with a thermistor a voltage divider circuit is used in addition with an ADC. +The screenshot below is taken directly from the ODrive schematic.

+
+Thermistor voltage divider +
+

The way this works is that the thermistor is connected in series with a known resistance value. +By connecting an ADC directly after the thermistor the resistance value can be determined. +For further information see Voltage divider. +While not strictly necessary, it is a good idea to add a capacitor as shown as well. This will help reduce the effect of electrical noise. +A value between 470nF and 4.7uF is recommended, and any voltage rating 4V or higher. Put the capacitor physically close to the ODrive. +To use a thermistor with the ODrive a voltage divider circuit has to be made that uses VCCA as the power source with GNDA as the ground. +The voltage divider output can be connected to a GPIO pin that supports analog input.

+
+
+

Thermistor Coefficients

+

Every thermistor and voltage divider circuit is different and thus it’s necessary to let the ODrive know how to relate a voltage it measures at the GPIO pin to a temperature. +The poly_coefficient_0 to poly_coefficient_3 under <axis>.motor.motor_thermistor.config are used for this. +The odrivetool has a convenience function set_motor_thermistor_coeffs(axis, Rload, R_25, Beta, Tmin, Tmax) which can be used to calculate and set these coefficients.

+
    +
  • axis: Which axis do set the motor thermistor coefficients for (odrv0.axis0 or odrv0.axis1).

  • +
  • Rload: The Ohm value of the resistor used in the voltage divider circuit.

  • +
  • R_25: The resistance of the thermistor when the temperature is 25 degrees celsius. Can usually be found in the datasheet of your thermistor. Can also be measured manually with a multimeter.

  • +
  • Beta: A constant specific to your thermistor. Can be found in the datasheet of your thermistor.

  • +
  • Tmin and Tmax: The temperature range that is used to create the coefficients. Make sure to set this range to be wider than what is expected during operation. A good example may be -10 to 150.

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/troubleshooting.html b/docs/reStructuredText/_build/html/troubleshooting.html new file mode 100644 index 00000000..9878bda8 --- /dev/null +++ b/docs/reStructuredText/_build/html/troubleshooting.html @@ -0,0 +1,338 @@ + + + + + + Troubleshooting — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Troubleshooting

+ +
+

Error Codes

+

If your ODrive is not working as expected, run odrivetool and type dump_errors(odrv0) Enter. +This will dump a list of all the errors that are present. To clear all the errors, you can run odrv0.clear_errors().

+

With this information you can look up the API documentation for your error(s):

+
    +
  • System error flags documented here.

  • +
  • Axis error flags documented here.

  • +
  • Motor error flags documented here.

  • +
  • Encoder error flags documented here.

  • +
  • Controller error flags documented here.

  • +
  • Sensorless estimator error flags documented here.

  • +
+
+

What if dump_errors() gives me python errors?

+

If you get output like this:

+

when you call dump_errors(), you have a version mismatch between odrivetool and the firmware on your ODrive. +To get the newest version of odrivetool, you can run pip install odrive --upgrade. +To get the newest ODrive firmware, run odrivetool dfu. +See the odrivetool page for more details.

+
+
+
+

USB Connectivity Issues

+
+
    +
  • Try turning it off and on again (the ODrive, the script, the PC)

  • +
  • Make sure you’re using the latest firmware and python tools release

  • +
  • Linux: Type lsusb to list all USB devices. Verify that your ODrive is listed.

  • +
  • Linux: Make sure you set up your udev rules correctly.

  • +
  • Windows: Right-click on the start menu and open “Device Manager”. Verify that your ODrive is listed.

  • +
  • Windows: Use the Zadig utility to verify the driver is set to WinUSB or libusb-win32.

    +
    +

    Note

    +

    There are two options listed in Zadig for ODrive: ODrive 3.x Native Interface (Interface 2) and ODrive 3.x CDC Interface (Interface 0).

    +
    +

    Only the driver setting of the native interface is important to odrivetool.

    +
  • +
  • Ensure that no other ODrive program is running

  • +
  • Run odrivetool with the –verbose option.

  • +
  • Run PYUSB_DEBUG=debug odrivetool to get even more log output.

  • +
  • If you’re a developer you can use Wireshark to capture USB traffic.

  • +
  • Try a different USB cable

  • +
  • Try routing your USB cable so that it is far away from the motor and PSU cables to reduce EMI

  • +
+
+
+
+

Firmware Issues

+
+

Failure to Build the Firmware when Running make

+
    +
  • Clear out temporary files from previous compiles by first running make clean to prevent conflicts.

  • +
  • Windows users: Confirm that tup has been correctly added to path by running env|grep PATH in Git Bash. +If you see no mention of tup then you must add its location to your PATH environment variable.. +Note that you may need to restart for the added path to take effect.

  • +
+
+
+

Failure to Flash the Firmware When Running make flash

+
    +
  • If using an ST-link, confirm that the ST-link is connected the correct pins and that you have power supplied to the board. +This can be by the 5V pin on the ST link or the main DC power jack. No power is supplied over the USB connection.

  • +
+
+
+
+

Other Issues that may not produce an error code

+
+

Motor cuts off or spins uncontrollably at high speeds (ie: > 5000 RPM)

+
    +
  • You may be approaching the limit of your encoder. +The 2400 count/rotation encoders that were initially included with odrive are realistically limited to around 5000 RPM. +Exceeding this speed causes the odrive to lose track of position. +This can only be fixed by using an alternative encoder or gearing down the output of your motor onto your encoder so that it still sees < 5000RPM at full speed. +If using the gearing options be sure to change your counts/rotation accordingly.

  • +
+
+
+

Motor vibrates when stationary or makes constant noise

+
    +
  • Likely due to incorrect gains, specifically vel_gain may be set too high. +Try following the tuning procedure.

  • +
  • Check encoder shaft connection. Grub screws may vibrate lose with time. +If using a CUI shaft encoder try remounting the plastic retaining ring and confirm that it is not coming into contact with the encoder housing. +Also confirm that the encoder is securely mounted.

  • +
  • If you are using a high resolution encoder (>4000 counts/rotation) then increasing encoder_pll_bandwidth may help reduce vibration.

  • +
  • If you connect your motor to an object with a large moment of inertia (such as a flywheel) this will help reduce vibrations at high gians. +However, make sure that all connections are ridged. Cheap shaft couplers or belts under low tension can introduce enough flex into a system that the motor may still vibrate independently.

  • +
+
+
+

Motor overshoots target position or oscillates back and forth

+
    +
  • Likely due to incorrect gains for a given motor current limit. +Specifically pos_gain is set too high. +Try following the tuning procedure.

  • +
  • Increase the current limit of your motor for more torque.

  • +
+
+
+

Motor slowly starts to increase in speed

+
    +
  • Encoder has likely slipped. This may occur when your motor makes a hard stop or violently vibrates causing something to come lose. +Power the board off and on again so that it undertakes a new calibration. +If you are using an index search on startup then you will need to repeat the index calibration process.

  • +
+
+
+

Motor feels like it has less torque than it should and/or gets hot sitting still while under no load

+
    +
  • Encoder has likely slipped causing the motor controller to commutate the wrong windings slightly which reduces output torque and produces excess heat as the motor ‘fights itself’.

  • +
  • This can also be caused if the rotor bell slips on the motor shaft. +On some motors the rotor bell is secured against the shaft with a grub screw. +Confirm that this screw is tight enough. +For further details on how to resolve this issue see this forum post.

  • +
+
+
+

False steps or direction changes when using step/dir

+
    +
  • Prior to Odrive board V3.5 no filtering is present on the GPIO pins used for step/dir interface and so inductively coupled noise may causes false steps to be detected. +ODrive V3.5 and has onboard filtering to resolve this issue.

  • +
  • If you experience this issue use a twisted pair cable between your microcontroller that’s generating the step/dir signals and your odrive board. +A section cut from cat-5 cable works well as does just twisting some normal insulated wire together.

  • +
  • Ensure that the step/dir signal cables are not draped over the odrive board, are not running in parallel to the motor or power supply cables.

  • +
  • If the above does not resolve your issue on V3.4 boards and lower try adding a ~22 Ohm resistor in series with the step and direction pins along with a ~ 4.7 nF capacitor between the ground pin and the step and dir pins such as shown here.

  • +
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/uart.html b/docs/reStructuredText/_build/html/uart.html new file mode 100644 index 00000000..3fc616f2 --- /dev/null +++ b/docs/reStructuredText/_build/html/uart.html @@ -0,0 +1,174 @@ + + + + + + UART Interface — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

UART Interface

+

The ODrive’s UART_A interface is enabled by default with a baudrate of 115200 on the pins as shown in Pinout.

+

To use UART connect it like this:

+
    +
  • Tx of the ODrive <=> Rx of other device

  • +
  • Rx of the ODrive <=> Tx of other device

  • +
  • GND of the ODrive (use any GND pin on J3 of the ODrive) <=> GND of the other device

  • +
+

The logic level of the ODrive is 3.3V. The GPIOs are 5V tolerant.

+

You can use odrv0.config.uart_a_baudrate to change the baudrate and odrv0.config.enable_uart_a to disable/reenable UART_A. +The UART_A port can run the Native Protocol or the ASCII Protocol, but not both at the same time. +You can configure this by setting odrv0.config.uart0_protocol to either STREAM_PROTOCOL_TYPE_ASCII_AND_STDOUT for the ASCII protocol or STREAM_PROTOCOL_TYPE_FIBRE for the native protocol.

+
+

How to use UART on GPIO3/4

+

If you need GPIO1/2 for some function other than UART you can disable UART_A and instead use UART_B on GPIO3/4. Here’s how you do it:

+
odrv0.config.enable_uart_a = False
+odrv0.config.gpio1_mode = GPIO_MODE_DIGITAL
+odrv0.config.gpio2_mode = GPIO_MODE_DIGITAL
+odrv0.config.enable_uart_b = True
+odrv0.config.gpio3_mode = GPIO_MODE_UART_B
+odrv0.config.gpio4_mode = GPIO_MODE_UART_B
+odrv0.reboot()
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reStructuredText/_build/html/usb.html b/docs/reStructuredText/_build/html/usb.html new file mode 100644 index 00000000..1c7397e7 --- /dev/null +++ b/docs/reStructuredText/_build/html/usb.html @@ -0,0 +1,204 @@ + + + + + + USB — ODrive Documentation 0.0 documentation + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

USB

+

This page documents the low level USB configuration. +If you’re looking for a higher level protocol documentation see Native Protocol and ASCII Protocol.

+

This page assumes that you are familiar with the general USB architecture, in particular with terms like “configuration”, “interface” and “endpoint”.

+

On USB the ODrive provides a single configuration which is a composite device consisting of a CDC device (virtual COM port) and a vendor specific device.

+

The following interface groups are present:

+
+
    +
  • +
    Interface Association: Communication Device Class (CDC)
      +
    • +
      Interface 0:
        +
      • Endpoint 0x82: CDC commands

      • +
      +
      +
      +
    • +
    • +
      Interface 1:
        +
      • Endpoint 0x01: CDC data OUT

      • +
      • Endpoint 0x81: CDC data IN

      • +
      +
      +
      +
    • +
    +
    +
    +
  • +
  • +
    Interface Association: Vendor Specific Device Class
      +
    • +
      Interface 2:
        +
      • Endpoint 0x03: data OUT

      • +
      • Endpoint 0x83: data IN

      • +
      +
      +
      +
    • +
    +
    +
    +
  • +
+
+

The CDC interface (endpoint pair 0x01, 0x81) runs the ASCII Protocol by default (see odrv0.config.enable_ascii_protocol_on_usb). +The vendor specific interface (endpoint pair 0x03, 0x83) runs the Native Protocol (the packet based variant).

+

The two interfaces can not (yet) be used simultaneously.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file