saving work
parent
56c5400a1f
commit
b8903bbf16
@ -1,3 +1,17 @@
|
|||||||
# learn_moteus
|
|
||||||
|
|
||||||
# Learning how to use the Moteus BLDC controller
|
# Learning how to use the Moteus BLDC controller
|
||||||
|
|
||||||
|
## The EIL5 of communication with the Moteus
|
||||||
|
|
||||||
|
There are two protocol's at play with the Moteus you need to be aware of:
|
||||||
|
|
||||||
|
1. **Diagnostic**: This is used by the `tview` and `moteus_tool` applications and is of the text form `d pos`
|
||||||
|
- Calibration of the motor used with the Moteus as of 05/04/2023 still requires the `moteus_tool` you cannot calibrate the motor from within your application.
|
||||||
|
- `conf write` will persist the configuration you have set in tview to the motor controller. It's the easiest way to change the gains you or whatever.
|
||||||
|
- [Getting Started with the DevKit](https://youtu.be/HHCBohdrCH8) Has a great explaination of a lot of the tools.
|
||||||
|
2. **Register**: This is the protocol used under the hood with the Python library. It read/writes to registers on the Micro over CANBus FD.
|
||||||
|
- __BE AWARE__: There is a watchdog timer set per servo that defaults to `servo.default_timeout_s=0.1`. This means that you you need to send a valid command at least every 100ms or you will trip the fault state. TODO add how to get out of fault state. END TODO. YOu can change this value in tview and persist with `conf write` or you can can use `watchdog_timeout` kwarg in the `set_postion` command. ALSO! You can use diagnotic protocol from python programs to programtically set it #TODO add an example python file showing how to do this END TODO#.
|
||||||
|
|
||||||
|
|
||||||
|
## Firmware updates
|
||||||
|
|
||||||
|
Firmware updates will unlock newer features of the boards. Follow these instructions https://github.com/mjbots/moteus/blob/main/docs/reference.md#flashing-over-can when you need to upgrade the controllers.
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
# Invoke tab-completion script to be sourced with Bash shell.
|
||||||
|
# Known to work on Bash 3.x, untested on 4.x.
|
||||||
|
|
||||||
|
_complete_invoke() {
|
||||||
|
local candidates
|
||||||
|
|
||||||
|
# COMP_WORDS contains the entire command string up til now (including
|
||||||
|
# program name).
|
||||||
|
# We hand it to Invoke so it can figure out the current context: spit back
|
||||||
|
# core options, task names, the current task's options, or some combo.
|
||||||
|
candidates=`invoke --complete -- ${COMP_WORDS[*]}`
|
||||||
|
|
||||||
|
# `compgen -W` takes list of valid options & a partial word & spits back
|
||||||
|
# possible matches. Necessary for any partial word completions (vs
|
||||||
|
# completions performed when no partial words are present).
|
||||||
|
#
|
||||||
|
# $2 is the current word or token being tabbed on, either empty string or a
|
||||||
|
# partial word, and thus wants to be compgen'd to arrive at some subset of
|
||||||
|
# our candidate list which actually matches.
|
||||||
|
#
|
||||||
|
# COMPREPLY is the list of valid completions handed back to `complete`.
|
||||||
|
COMPREPLY=( $(compgen -W "${candidates}" -- $2) )
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Tell shell builtin to use the above for completing our invocations.
|
||||||
|
# * -F: use given function name to generate completions.
|
||||||
|
# * -o default: when function generates no results, use filenames.
|
||||||
|
# * positional args: program names to complete for.
|
||||||
|
complete -F _complete_invoke -o default invoke inv
|
||||||
|
|
||||||
|
# vim: set ft=sh :
|
||||||
|
|
@ -0,0 +1,6 @@
|
|||||||
|
black
|
||||||
|
pip-tools
|
||||||
|
pre-commit
|
||||||
|
invoke
|
||||||
|
isort
|
||||||
|
ruff
|
@ -0,0 +1,62 @@
|
|||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile with Python 3.10
|
||||||
|
# by the following command:
|
||||||
|
#
|
||||||
|
# pip-compile dev_requirements.in
|
||||||
|
#
|
||||||
|
black==23.3.0
|
||||||
|
# via -r dev_requirements.in
|
||||||
|
build==0.10.0
|
||||||
|
# via pip-tools
|
||||||
|
cfgv==3.3.1
|
||||||
|
# via pre-commit
|
||||||
|
click==8.1.3
|
||||||
|
# via
|
||||||
|
# black
|
||||||
|
# pip-tools
|
||||||
|
distlib==0.3.6
|
||||||
|
# via virtualenv
|
||||||
|
filelock==3.12.0
|
||||||
|
# via virtualenv
|
||||||
|
identify==2.5.24
|
||||||
|
# via pre-commit
|
||||||
|
invoke==2.1.1
|
||||||
|
# via -r dev_requirements.in
|
||||||
|
isort==5.12.0
|
||||||
|
# via -r dev_requirements.in
|
||||||
|
mypy-extensions==1.0.0
|
||||||
|
# via black
|
||||||
|
nodeenv==1.7.0
|
||||||
|
# via pre-commit
|
||||||
|
packaging==23.1
|
||||||
|
# via
|
||||||
|
# black
|
||||||
|
# build
|
||||||
|
pathspec==0.11.1
|
||||||
|
# via black
|
||||||
|
pip-tools==6.13.0
|
||||||
|
# via -r dev_requirements.in
|
||||||
|
platformdirs==3.5.0
|
||||||
|
# via
|
||||||
|
# black
|
||||||
|
# virtualenv
|
||||||
|
pre-commit==3.3.1
|
||||||
|
# via -r dev_requirements.in
|
||||||
|
pyproject-hooks==1.0.0
|
||||||
|
# via build
|
||||||
|
pyyaml==6.0
|
||||||
|
# via pre-commit
|
||||||
|
ruff==0.0.264
|
||||||
|
# via -r dev_requirements.in
|
||||||
|
tomli==2.0.1
|
||||||
|
# via
|
||||||
|
# black
|
||||||
|
# build
|
||||||
|
virtualenv==20.23.0
|
||||||
|
# via pre-commit
|
||||||
|
wheel==0.40.0
|
||||||
|
# via pip-tools
|
||||||
|
|
||||||
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
# pip
|
||||||
|
# setuptools
|
@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/python3 -B
|
||||||
|
|
||||||
|
# Copyright 2021 Josh Pieper, jjp@pobox.com.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
This example commands a single servo at ID #1 using the default
|
||||||
|
transport to hold the current position indefinitely, and prints the
|
||||||
|
state of the servo to the console.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import math
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
import moteus
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BLDCMotor:
|
||||||
|
"""A BLDC Motor"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.controller = moteus.Controller()
|
||||||
|
|
||||||
|
async def set_stop(self, *args, **kwargs):
|
||||||
|
"""Stops the BLDC Motor"""
|
||||||
|
await self.controller.set_stop(*args, **kwargs)
|
||||||
|
|
||||||
|
async def set_position(self, *args, **kwargs):
|
||||||
|
state = await self.controller.set_position(*args, **kwargs)
|
||||||
|
return state
|
||||||
|
|
||||||
|
|
||||||
|
def create_async_signal_handler(coro_func: Callable[[], None]) -> Callable[[signal.Signals], None]:
|
||||||
|
async def _async_signal_handler(signal_type: signal.Signals) -> None:
|
||||||
|
logging.info(f"Received exit signal {signal.name}...")
|
||||||
|
|
||||||
|
await coro_func()
|
||||||
|
|
||||||
|
tasks = [t for t in asyncio.all_tasks() if t is not
|
||||||
|
asyncio.current_task()]
|
||||||
|
|
||||||
|
[task.cancel() for task in tasks]
|
||||||
|
|
||||||
|
logging.info(f"Cancelling {len(tasks)} outstanding tasks")
|
||||||
|
await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.stop()
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
|
||||||
|
def _signal_handler(signal_type: signal.Signals, loop: asyncio.AbstractEventLoop) -> None:
|
||||||
|
print(f"Signal {signal_type.name} received.")
|
||||||
|
asyncio.create_task(_async_signal_handler(signal_type))
|
||||||
|
|
||||||
|
return _signal_handler
|
||||||
|
|
||||||
|
|
||||||
|
async def main(motor: BLDCMotor):
|
||||||
|
# In case the controller had faulted previously, at the start of
|
||||||
|
# this script we send the stop command in order to clear it.
|
||||||
|
await motor.set_stop()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# `set_position` accepts an optional keyword argument for each
|
||||||
|
# possible position mode register as described in the moteus
|
||||||
|
# reference manual. If a given register is omitted, then that
|
||||||
|
# register is omitted from the command itself, with semantics
|
||||||
|
# as described in the reference manual.
|
||||||
|
#
|
||||||
|
# The return type of 'set_position' is a moteus.Result type.
|
||||||
|
# It has a __repr__ method, and has a 'values' field which can
|
||||||
|
# be used to examine individual result registers.
|
||||||
|
state = await motor.set_position(position=math.nan, query=True)
|
||||||
|
|
||||||
|
# Print out everything.
|
||||||
|
print(state)
|
||||||
|
|
||||||
|
# Print out just the position register.
|
||||||
|
print("Position:", state.values[moteus.Register.POSITION])
|
||||||
|
|
||||||
|
# And a blank line so we can separate one iteration from the
|
||||||
|
# next.
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Wait 20ms between iterations. By default, when commanded
|
||||||
|
# over CAN, there is a watchdog which requires commands to be
|
||||||
|
# sent at least every 100ms or the controller will enter a
|
||||||
|
# latched fault state.
|
||||||
|
await asyncio.sleep(0.02)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
motor = BLDCMotor()
|
||||||
|
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
signal_handler = create_async_signal_handler(motor.set_stop)
|
||||||
|
|
||||||
|
# Register signal handlers for SIGNINT(Keyboard interrupt) and SIGTERM (Termination)
|
||||||
|
# for sig in (signal.SIGINT, signal.SIGTERM):
|
||||||
|
for sig in (signal.SIGINT):
|
||||||
|
loop.add_signal_handler(sig, signal_handler, sig, loop)
|
||||||
|
|
||||||
|
try:
|
||||||
|
loop.run_until_complete(main(motor))
|
||||||
|
finally:
|
||||||
|
loop.close()
|
@ -0,0 +1,8 @@
|
|||||||
|
import moteus
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("Stopping motor...")
|
||||||
|
c = moteus.Controller()
|
||||||
|
asyncio.run(c.set_stop())
|
||||||
|
print("Motor Stopped")
|
@ -0,0 +1,2 @@
|
|||||||
|
moteus
|
||||||
|
moteus-gui
|
@ -0,0 +1,152 @@
|
|||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile with Python 3.10
|
||||||
|
# by the following command:
|
||||||
|
#
|
||||||
|
# pip-compile requirement.in
|
||||||
|
#
|
||||||
|
asttokens==2.2.1
|
||||||
|
# via stack-data
|
||||||
|
asyncqt==0.8.0
|
||||||
|
# via moteus-gui
|
||||||
|
backcall==0.2.0
|
||||||
|
# via ipython
|
||||||
|
comm==0.1.3
|
||||||
|
# via ipykernel
|
||||||
|
contourpy==1.0.7
|
||||||
|
# via matplotlib
|
||||||
|
cycler==0.11.0
|
||||||
|
# via matplotlib
|
||||||
|
debugpy==1.6.7
|
||||||
|
# via ipykernel
|
||||||
|
decorator==5.1.1
|
||||||
|
# via ipython
|
||||||
|
executing==1.2.0
|
||||||
|
# via stack-data
|
||||||
|
fonttools==4.39.3
|
||||||
|
# via matplotlib
|
||||||
|
importlib-metadata==6.6.0
|
||||||
|
# via moteus
|
||||||
|
ipykernel==6.22.0
|
||||||
|
# via qtconsole
|
||||||
|
ipython==8.13.2
|
||||||
|
# via ipykernel
|
||||||
|
ipython-genutils==0.2.0
|
||||||
|
# via qtconsole
|
||||||
|
jedi==0.18.2
|
||||||
|
# via ipython
|
||||||
|
jupyter-client==8.2.0
|
||||||
|
# via
|
||||||
|
# ipykernel
|
||||||
|
# qtconsole
|
||||||
|
jupyter-core==5.3.0
|
||||||
|
# via
|
||||||
|
# ipykernel
|
||||||
|
# jupyter-client
|
||||||
|
# qtconsole
|
||||||
|
kiwisolver==1.4.4
|
||||||
|
# via matplotlib
|
||||||
|
matplotlib==3.7.1
|
||||||
|
# via moteus-gui
|
||||||
|
matplotlib-inline==0.1.6
|
||||||
|
# via
|
||||||
|
# ipykernel
|
||||||
|
# ipython
|
||||||
|
moteus==0.3.54
|
||||||
|
# via
|
||||||
|
# -r requirement.in
|
||||||
|
# moteus-gui
|
||||||
|
moteus-gui==0.3.54
|
||||||
|
# via -r requirement.in
|
||||||
|
msgpack==1.0.5
|
||||||
|
# via python-can
|
||||||
|
nest-asyncio==1.5.6
|
||||||
|
# via ipykernel
|
||||||
|
numpy==1.24.3
|
||||||
|
# via
|
||||||
|
# contourpy
|
||||||
|
# matplotlib
|
||||||
|
# moteus-gui
|
||||||
|
packaging==23.1
|
||||||
|
# via
|
||||||
|
# ipykernel
|
||||||
|
# matplotlib
|
||||||
|
# python-can
|
||||||
|
# qtconsole
|
||||||
|
# qtpy
|
||||||
|
parso==0.8.3
|
||||||
|
# via jedi
|
||||||
|
pexpect==4.8.0
|
||||||
|
# via ipython
|
||||||
|
pickleshare==0.7.5
|
||||||
|
# via ipython
|
||||||
|
pillow==9.5.0
|
||||||
|
# via matplotlib
|
||||||
|
platformdirs==3.5.0
|
||||||
|
# via jupyter-core
|
||||||
|
prompt-toolkit==3.0.38
|
||||||
|
# via ipython
|
||||||
|
psutil==5.9.5
|
||||||
|
# via ipykernel
|
||||||
|
ptyprocess==0.7.0
|
||||||
|
# via pexpect
|
||||||
|
pure-eval==0.2.2
|
||||||
|
# via stack-data
|
||||||
|
pyelftools==0.29
|
||||||
|
# via moteus
|
||||||
|
pygments==2.15.1
|
||||||
|
# via
|
||||||
|
# ipython
|
||||||
|
# qtconsole
|
||||||
|
pyparsing==3.0.9
|
||||||
|
# via matplotlib
|
||||||
|
pyserial==3.5
|
||||||
|
# via moteus
|
||||||
|
pyside2==5.15.2.1
|
||||||
|
# via moteus-gui
|
||||||
|
python-can==4.2.0
|
||||||
|
# via moteus
|
||||||
|
python-dateutil==2.8.2
|
||||||
|
# via
|
||||||
|
# jupyter-client
|
||||||
|
# matplotlib
|
||||||
|
pyzmq==25.0.2
|
||||||
|
# via
|
||||||
|
# ipykernel
|
||||||
|
# jupyter-client
|
||||||
|
# qtconsole
|
||||||
|
qtconsole==5.4.2
|
||||||
|
# via moteus-gui
|
||||||
|
qtpy==2.3.1
|
||||||
|
# via
|
||||||
|
# moteus-gui
|
||||||
|
# qtconsole
|
||||||
|
shiboken2==5.15.2.1
|
||||||
|
# via pyside2
|
||||||
|
six==1.16.0
|
||||||
|
# via python-dateutil
|
||||||
|
stack-data==0.6.2
|
||||||
|
# via ipython
|
||||||
|
tornado==6.3.1
|
||||||
|
# via
|
||||||
|
# ipykernel
|
||||||
|
# jupyter-client
|
||||||
|
traitlets==5.9.0
|
||||||
|
# via
|
||||||
|
# comm
|
||||||
|
# ipykernel
|
||||||
|
# ipython
|
||||||
|
# jupyter-client
|
||||||
|
# jupyter-core
|
||||||
|
# matplotlib-inline
|
||||||
|
# qtconsole
|
||||||
|
typing-extensions==4.5.0
|
||||||
|
# via python-can
|
||||||
|
wcwidth==0.2.6
|
||||||
|
# via prompt-toolkit
|
||||||
|
wrapt==1.15.0
|
||||||
|
# via python-can
|
||||||
|
zipp==3.15.0
|
||||||
|
# via importlib-metadata
|
||||||
|
|
||||||
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
# setuptools
|
@ -0,0 +1,13 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from invoke import task
|
||||||
|
|
||||||
|
@task
|
||||||
|
def sync_env(c):
|
||||||
|
"""Update the local virtualenv with pip-sync"""
|
||||||
|
if not os.environ.get('VIRTUAL_ENV'):
|
||||||
|
print("You are operating outside of a virtualenv. Skipping sync...")
|
||||||
|
sys.exit(1)
|
||||||
|
print("Updating your virtualenv dependencies!")
|
||||||
|
c.run("pip-sync ./requirements.txt ./dev_requirements.txt")
|
@ -0,0 +1 @@
|
|||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", MODE="0660", GROUP="dialout", SYMLINK+="fdcanusb%n", TAG+="uaccess", TAG+="udev-acl", OWNER="toor"
|
Loading…
Reference in New Issue