saving work

master
Drew Bednar 2 years ago
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…
Cancel
Save