Import
This commit is contained in:
commit
584ca15338
19
.clang-format
Normal file
19
.clang-format
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
---
|
||||||
|
AccessModifierOffset: -4
|
||||||
|
BasedOnStyle: Google
|
||||||
|
ColumnLimit: 100
|
||||||
|
IndentWidth: 4
|
||||||
19
.cmake-format.py
Normal file
19
.cmake-format.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
with section("format"):
|
||||||
|
command_case = "lower"
|
||||||
|
|
||||||
|
with section("markup"):
|
||||||
|
enable_markup = False
|
||||||
25
.github/ISSUE_TEMPLATE/1-bug-report.yml
vendored
Normal file
25
.github/ISSUE_TEMPLATE/1-bug-report.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
name: Bug Report
|
||||||
|
description: File a bug report.
|
||||||
|
type: Bug
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the problem you got
|
||||||
|
description: It's better that you provide information as much as possible.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
25
.github/ISSUE_TEMPLATE/2-feature-request.yml
vendored
Normal file
25
.github/ISSUE_TEMPLATE/2-feature-request.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
name: Feature Request
|
||||||
|
description: Request a feature.
|
||||||
|
type: Feature
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the feature you want
|
||||||
|
description: It's better that you also provide your use case.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
22
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
22
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Discord
|
||||||
|
url: https://discord.gg/FsZaZ4z3We
|
||||||
|
about: Please ask and answer questions here.
|
||||||
|
- name: GitHub Discussions
|
||||||
|
url: https://github.com/enactic/openarm_can/discussions
|
||||||
|
about: If you prefer GitHub Discussions to Discord, you can use GitHub Discussions too.
|
||||||
96
.github/workflows/test.yaml
vendored
Normal file
96
.github/workflows/test.yaml
vendored
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
name: Test
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
- '!dependabot/**'
|
||||||
|
tags:
|
||||||
|
- '**'
|
||||||
|
pull_request:
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.head_ref || github.sha }}-${{ github.workflow }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
- name: Install pre-commit
|
||||||
|
run: |
|
||||||
|
python -m pip install pre-commit
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pre-commit
|
||||||
|
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||||
|
- name: Run pre-commit
|
||||||
|
run: |
|
||||||
|
pre-commit run --show-diff-on-failure --color=always --all-files
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ${{ matrix.runs-on }}
|
||||||
|
timeout-minutes: 5
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
runs-on:
|
||||||
|
- ubuntu-22.04
|
||||||
|
- ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
- name: "C++: Install dependencies"
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y -V \
|
||||||
|
cmake \
|
||||||
|
ninja-build
|
||||||
|
- name: "C++: CMake"
|
||||||
|
run: |
|
||||||
|
cmake \
|
||||||
|
-B ../build \
|
||||||
|
-S . \
|
||||||
|
-GNinja \
|
||||||
|
-DCMAKE_INSTALL_PREFIX=$PWD/install \
|
||||||
|
-DCMAKE_BUILD_TYPE=Debug
|
||||||
|
- name: "C++: Build"
|
||||||
|
run: |
|
||||||
|
ninja -C ../build
|
||||||
|
- name: Install
|
||||||
|
run: |
|
||||||
|
ninja -C ../build install
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: 3
|
||||||
|
- name: "Python: Install dependencies"
|
||||||
|
run: |
|
||||||
|
python3 -m pip install -r python/requirements.txt
|
||||||
|
- name: "Python: Build: pkg-config"
|
||||||
|
run: |
|
||||||
|
python3 -m pip install \
|
||||||
|
-Csetup-args=--build.pkg-config-path=$PWD/install/lib/pkgconfig \
|
||||||
|
./python
|
||||||
|
- name: "Python: Build: CMake"
|
||||||
|
run: |
|
||||||
|
python3 -m pip install \
|
||||||
|
-Csetup-args=--build.cmake-prefix-path=$PWD/install \
|
||||||
|
./python
|
||||||
42
.pre-commit-config.yaml
Normal file
42
.pre-commit-config.yaml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
|
rev: v20.1.7
|
||||||
|
hooks:
|
||||||
|
- id: clang-format
|
||||||
|
alias: cpp
|
||||||
|
- repo: https://github.com/cheshirekow/cmake-format-precommit
|
||||||
|
rev: v0.6.13
|
||||||
|
hooks:
|
||||||
|
- id: cmake-format
|
||||||
|
alias: cpp
|
||||||
|
- repo: https://github.com/hhatto/autopep8
|
||||||
|
rev: v2.3.2
|
||||||
|
hooks:
|
||||||
|
- id: autopep8
|
||||||
|
alias: python
|
||||||
|
name: Python Format
|
||||||
|
args:
|
||||||
|
- "--global-config"
|
||||||
|
- "python/pyproject.toml"
|
||||||
|
- "--ignore-local-config"
|
||||||
|
- "--in-place"
|
||||||
|
- repo: https://github.com/trim21/pre-commit-mirror-meson
|
||||||
|
rev: v1.8.2
|
||||||
|
hooks:
|
||||||
|
- id: meson-fmt
|
||||||
|
alias: python
|
||||||
|
args: ['--inplace']
|
||||||
127
CMakeLists.txt
Executable file
127
CMakeLists.txt
Executable file
@ -0,0 +1,127 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.22)
|
||||||
|
project(openarm_can VERSION 1.0.0)
|
||||||
|
|
||||||
|
# Set C++ standard
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
|
add_compile_options(-Wall -Wextra -Wpedantic)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Testing support
|
||||||
|
include(CTest)
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
# Create the main library
|
||||||
|
add_library(
|
||||||
|
openarm_can
|
||||||
|
src/openarm/can/socket/arm_component.cpp
|
||||||
|
src/openarm/can/socket/gripper_component.cpp
|
||||||
|
src/openarm/can/socket/openarm.cpp
|
||||||
|
src/openarm/canbus/can_device_collection.cpp
|
||||||
|
src/openarm/canbus/can_socket.cpp
|
||||||
|
src/openarm/damiao_motor/dm_motor.cpp
|
||||||
|
src/openarm/damiao_motor/dm_motor_control.cpp
|
||||||
|
src/openarm/damiao_motor/dm_motor_device.cpp
|
||||||
|
src/openarm/damiao_motor/dm_motor_device_collection.cpp)
|
||||||
|
set_target_properties(openarm_can PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||||
|
set(USE_FILE_SET_HEADERS FALSE)
|
||||||
|
# Meson doesn't support FILE_SET TYPE HEADERS...
|
||||||
|
# if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.23)
|
||||||
|
# set(USE_FILE_SET_HEADERS TRUE)
|
||||||
|
# endif()
|
||||||
|
if(USE_FILE_SET_HEADERS)
|
||||||
|
target_sources(
|
||||||
|
openarm_can
|
||||||
|
PUBLIC FILE_SET
|
||||||
|
HEADERS
|
||||||
|
TYPE
|
||||||
|
HEADERS
|
||||||
|
BASE_DIRS
|
||||||
|
include
|
||||||
|
FILES
|
||||||
|
include/openarm/can/socket/arm_component.hpp
|
||||||
|
include/openarm/can/socket/gripper_component.hpp
|
||||||
|
include/openarm/can/socket/openarm.hpp
|
||||||
|
include/openarm/canbus/can_device.hpp
|
||||||
|
include/openarm/canbus/can_device_collection.hpp
|
||||||
|
include/openarm/canbus/can_socket.hpp
|
||||||
|
include/openarm/damiao_motor/dm_motor.hpp
|
||||||
|
include/openarm/damiao_motor/dm_motor_constants.hpp
|
||||||
|
include/openarm/damiao_motor/dm_motor_control.hpp
|
||||||
|
include/openarm/damiao_motor/dm_motor_device.hpp
|
||||||
|
include/openarm/damiao_motor/dm_motor_device_collection.hpp)
|
||||||
|
install(
|
||||||
|
TARGETS openarm_can
|
||||||
|
EXPORT openarm_can_export
|
||||||
|
FILE_SET HEADERS)
|
||||||
|
else()
|
||||||
|
target_include_directories(
|
||||||
|
openarm_can PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||||
|
$<INSTALL_INTERFACE:include>)
|
||||||
|
install(TARGETS openarm_can EXPORT openarm_can_export)
|
||||||
|
install(DIRECTORY include/openarm TYPE INCLUDE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# CMake package
|
||||||
|
set(INSTALL_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/OpenArmCAN)
|
||||||
|
install(
|
||||||
|
EXPORT openarm_can_export
|
||||||
|
DESTINATION ${INSTALL_CMAKE_DIR}
|
||||||
|
NAMESPACE OpenArmCAN::
|
||||||
|
FILE OpenArmCANTargets.cmake)
|
||||||
|
include(CMakePackageConfigHelpers)
|
||||||
|
configure_package_config_file(
|
||||||
|
OpenArmCANConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/OpenArmCANConfig.cmake
|
||||||
|
INSTALL_DESTINATION ${INSTALL_CMAKE_DIR})
|
||||||
|
write_basic_package_version_file(
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/OpenArmCANVersion.cmake
|
||||||
|
VERSION ${PROJECT_VERSION}
|
||||||
|
COMPATIBILITY SameMajorVersion)
|
||||||
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpenArmCANConfig.cmake
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/OpenArmCANVersion.cmake
|
||||||
|
DESTINATION ${INSTALL_CMAKE_DIR})
|
||||||
|
|
||||||
|
# pkg-config package
|
||||||
|
if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||||
|
set(PKG_CONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||||
|
else()
|
||||||
|
set(PKG_CONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
|
||||||
|
endif()
|
||||||
|
if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
|
||||||
|
set(PKG_CONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
|
||||||
|
else()
|
||||||
|
set(PKG_CONFIG_LIBDIR "\${prefix}/${CMAKE_INSTALL_LIBDIR}")
|
||||||
|
endif()
|
||||||
|
configure_file(openarm-can.pc.in ${CMAKE_CURRENT_BINARY_DIR}/openarm-can.pc
|
||||||
|
@ONLY)
|
||||||
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/openarm-can.pc
|
||||||
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||||
|
|
||||||
|
add_executable(motor-check setup/motor_check.cpp)
|
||||||
|
target_link_libraries(motor-check openarm_can)
|
||||||
|
|
||||||
|
# Add motor control example executable
|
||||||
|
add_executable(openarm-demo examples/demo.cpp)
|
||||||
|
target_link_libraries(openarm-demo openarm_can)
|
||||||
|
|
||||||
|
# Add tests
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
# add_subdirectory(test)
|
||||||
|
endif()
|
||||||
133
CODE_OF_CONDUCT.md
Normal file
133
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||||
|
identity and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the overall
|
||||||
|
community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||||
|
any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email address,
|
||||||
|
without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official email address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
hi_public@reazon.jp.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series of
|
||||||
|
actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or permanent
|
||||||
|
ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||||
|
community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.1, available at
|
||||||
|
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by
|
||||||
|
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||||
|
[https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
19
CONTRIBUTING.md
Normal file
19
CONTRIBUTING.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# How to contribute
|
||||||
|
|
||||||
|
## Did you find a bug?
|
||||||
|
|
||||||
|
Please report it to [GitHub Issues](https://github.com/enactic/openarm_can/issues/new?template=1-bug-report.yml)!
|
||||||
|
|
||||||
|
## Did you have a feature request?
|
||||||
|
|
||||||
|
Please share it to [GitHub Issues](https://github.com/enactic/openarm_can/issues/new?template=2-feature-request.yml)!
|
||||||
|
|
||||||
|
## Did you write a patch?
|
||||||
|
|
||||||
|
Please open a pull request with it!
|
||||||
|
|
||||||
|
Please make sure to review [our license](https://github.com/enactic/openarm_can/blob/main/LICENSE.txt) before you open a pull request.
|
||||||
|
|
||||||
|
## Others?
|
||||||
|
|
||||||
|
Please share it on [Discord](https://discord.gg/FsZaZ4z3We) or [GitHub Discussions](https://github.com/enactic/openarm_can/discussions)!
|
||||||
202
LICENSE.txt
Normal file
202
LICENSE.txt
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
||||||
19
OpenArmCANConfig.cmake.in
Normal file
19
OpenArmCANConfig.cmake.in
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
@PACKAGE_INIT@
|
||||||
|
|
||||||
|
include("${CMAKE_CURRENT_LIST_DIR}/OpenArmCANTargets.cmake")
|
||||||
|
|
||||||
|
check_required_components(OpenArmCAN)
|
||||||
96
README.md
Normal file
96
README.md
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# OpenArm CAN Library
|
||||||
|
|
||||||
|
A C++ library for CAN communication with OpenArm robotic hardware, supporting Damiao motors over CAN/CAN-FD interfaces.
|
||||||
|
This library is a part of [OpenArm SDK](https://github.com/enactic/openarm_sdk).
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Linux with SocketCAN support
|
||||||
|
- CAN interface hardware
|
||||||
|
- CMake 3.22+
|
||||||
|
- C++17 compiler
|
||||||
|
|
||||||
|
### 1. Setup CAN Interface
|
||||||
|
|
||||||
|
Configure your CAN interface using the provided script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# CAN 2.0 (default)
|
||||||
|
setup/configure_socketcan.sh can0
|
||||||
|
|
||||||
|
# CAN-FD with 5Mbps data rate
|
||||||
|
setup/configure_socketcan.sh can0 -fd
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. C++ Library
|
||||||
|
|
||||||
|
**Build & Install:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd openarm_can
|
||||||
|
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build
|
||||||
|
sudo cmake --install build
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <openarm/can/socket/openarm.hpp>
|
||||||
|
|
||||||
|
openarm::can::socket::OpenArm arm("can0", true); // CAN-FD enabled
|
||||||
|
arm.init_arm_motors({MotorType::DM4310}, {0x01}, {0x11});
|
||||||
|
arm.enable_all();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Python (🚧 EXPERIMENTAL - TEMPORARY 🚧)
|
||||||
|
|
||||||
|
> ⚠️ **WARNING: UNSTABLE API** ⚠️
|
||||||
|
> Python bindings are currently a direct low level **temporary port**, and will change **DRASTICALLY**.
|
||||||
|
> The interface is may break between versions.Use at your own risk! Discussions on the interface are welcomed.
|
||||||
|
|
||||||
|
**Build & Install:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd python
|
||||||
|
|
||||||
|
# Create and activate virtual environment (recommended)
|
||||||
|
python -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
./build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
# WARNING: This API is unstable and will change!
|
||||||
|
import openarm_can as oa
|
||||||
|
|
||||||
|
arm = oa.OpenArm("can0", True) # CAN-FD enabled
|
||||||
|
arm.init_arm_motors([oa.MotorType.DM4310], [0x01], [0x11])
|
||||||
|
arm.enable_all()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
- **C++**: `examples/demo.cpp` - Complete arm control demo
|
||||||
|
- **Python**: `python/examples/example.py` - Basic Python usage
|
||||||
|
|
||||||
|
## Related links
|
||||||
|
|
||||||
|
- 📚 Read the [documentation](https://docs.openarm.dev/software/sdk/openarm-can/)
|
||||||
|
- 💬 Join the community on [Discord](https://discord.gg/FsZaZ4z3We)
|
||||||
|
- 📬 Contact us through <openarm@enactic.ai>
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Licensed under the Apache License 2.0. See `LICENSE.txt` for details.
|
||||||
|
|
||||||
|
Copyright 2025 Enactic, Inc.
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
All participation in the OpenArm project is governed by our [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||||
118
examples/demo.cpp
Normal file
118
examples/demo.cpp
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <csignal>
|
||||||
|
#include <iostream>
|
||||||
|
#include <openarm/can/socket/openarm.hpp>
|
||||||
|
#include <openarm/damiao_motor/dm_motor_constants.hpp>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
try {
|
||||||
|
std::cout << "=== OpenArm CAN Example ===" << std::endl;
|
||||||
|
std::cout << "This example demonstrates the OpenArm API functionality" << std::endl;
|
||||||
|
|
||||||
|
// Initialize OpenArm with CAN interface and enable CAN-FD
|
||||||
|
std::cout << "Initializing OpenArm CAN..." << std::endl;
|
||||||
|
openarm::can::socket::OpenArm openarm("can0", true); // Use CAN-FD on can0 interface
|
||||||
|
|
||||||
|
// Initialize arm motors
|
||||||
|
std::vector<openarm::damiao_motor::MotorType> motor_types = {
|
||||||
|
openarm::damiao_motor::MotorType::DM4310, openarm::damiao_motor::MotorType::DM4310};
|
||||||
|
std::vector<uint32_t> send_can_ids = {0x01, 0x02};
|
||||||
|
std::vector<uint32_t> recv_can_ids = {0x11, 0x12};
|
||||||
|
openarm.init_arm_motors(motor_types, send_can_ids, recv_can_ids);
|
||||||
|
|
||||||
|
// Initialize gripper
|
||||||
|
std::cout << "Initializing gripper..." << std::endl;
|
||||||
|
openarm.init_gripper_motor(openarm::damiao_motor::MotorType::DM4310, 0x08, 0x18);
|
||||||
|
|
||||||
|
// Set callback mode to ignore and enable all motors
|
||||||
|
openarm.set_callback_mode_all(openarm::damiao_motor::CallbackMode::IGNORE);
|
||||||
|
|
||||||
|
// Enable all motors
|
||||||
|
std::cout << "\n=== Enabling Motors ===" << std::endl;
|
||||||
|
openarm.enable_all();
|
||||||
|
// Allow time (2ms) for the motors to respond for slow operations like enabling
|
||||||
|
openarm.recv_all(2000);
|
||||||
|
|
||||||
|
// Set device mode to param and query motor id
|
||||||
|
std::cout << "\n=== Querying Motor Recv IDs ===" << std::endl;
|
||||||
|
openarm.set_callback_mode_all(openarm::damiao_motor::CallbackMode::PARAM);
|
||||||
|
openarm.query_param_all(static_cast<int>(openarm::damiao_motor::RID::MST_ID));
|
||||||
|
// Allow time (2ms) for the motors to respond for slow operations like querying
|
||||||
|
// parameter from register
|
||||||
|
openarm.recv_all(2000);
|
||||||
|
|
||||||
|
// Access motors through components
|
||||||
|
for (const auto& motor : openarm.get_arm().get_motors()) {
|
||||||
|
std::cout << "Arm Motor: " << motor.get_send_can_id() << " ID: "
|
||||||
|
<< motor.get_param(static_cast<int>(openarm::damiao_motor::RID::MST_ID))
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
for (const auto& motor : openarm.get_gripper().get_motors()) {
|
||||||
|
std::cout << "Gripper Motor: " << motor.get_send_can_id() << " ID: "
|
||||||
|
<< motor.get_param(static_cast<int>(openarm::damiao_motor::RID::MST_ID))
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set device mode to state and control motor
|
||||||
|
std::cout << "\n=== Controlling Motors ===" << std::endl;
|
||||||
|
openarm.set_callback_mode_all(openarm::damiao_motor::CallbackMode::STATE);
|
||||||
|
|
||||||
|
// Control arm motors with position control
|
||||||
|
openarm.get_arm().mit_control_all({openarm::damiao_motor::MITParam{2, 1, 0, 0, 0},
|
||||||
|
openarm::damiao_motor::MITParam{2, 1, 0, 0, 0}});
|
||||||
|
openarm.recv_all(500);
|
||||||
|
|
||||||
|
// Control arm motors with torque control
|
||||||
|
openarm.get_arm().mit_control_all({openarm::damiao_motor::MITParam{0, 0, 0, 0, 0.1},
|
||||||
|
openarm::damiao_motor::MITParam{0, 0, 0, 0, 0.1}});
|
||||||
|
openarm.recv_all(500);
|
||||||
|
|
||||||
|
// Control gripper
|
||||||
|
std::cout << "Closing gripper..." << std::endl;
|
||||||
|
openarm.get_gripper().close();
|
||||||
|
openarm.recv_all(1000);
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
|
openarm.refresh_all();
|
||||||
|
openarm.recv_all(300);
|
||||||
|
|
||||||
|
// Display arm motor states
|
||||||
|
for (const auto& motor : openarm.get_arm().get_motors()) {
|
||||||
|
std::cout << "Arm Motor: " << motor.get_send_can_id()
|
||||||
|
<< " position: " << motor.get_position() << std::endl;
|
||||||
|
}
|
||||||
|
// Display gripper state
|
||||||
|
for (const auto& motor : openarm.get_gripper().get_motors()) {
|
||||||
|
std::cout << "Gripper Motor: " << motor.get_send_can_id()
|
||||||
|
<< " position: " << motor.get_position() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openarm.disable_all();
|
||||||
|
openarm.recv_all(1000);
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Error: " << e.what() << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
38
include/openarm/can/socket/arm_component.hpp
Normal file
38
include/openarm/can/socket/arm_component.hpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../../canbus/can_socket.hpp"
|
||||||
|
#include "../../damiao_motor/dm_motor.hpp"
|
||||||
|
#include "../../damiao_motor/dm_motor_device_collection.hpp"
|
||||||
|
|
||||||
|
namespace openarm::can::socket {
|
||||||
|
|
||||||
|
class ArmComponent : public damiao_motor::DMDeviceCollection {
|
||||||
|
public:
|
||||||
|
ArmComponent(canbus::CANSocket& can_socket);
|
||||||
|
~ArmComponent() = default;
|
||||||
|
|
||||||
|
void init_motor_devices(std::vector<damiao_motor::MotorType> motor_types,
|
||||||
|
std::vector<uint32_t> send_can_ids, std::vector<uint32_t> recv_can_ids,
|
||||||
|
bool use_fd);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<damiao_motor::Motor> motors_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace openarm::can::socket
|
||||||
70
include/openarm/can/socket/gripper_component.hpp
Normal file
70
include/openarm/can/socket/gripper_component.hpp
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
#include <cmath>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "../../damiao_motor/dm_motor.hpp"
|
||||||
|
#include "../../damiao_motor/dm_motor_device_collection.hpp"
|
||||||
|
|
||||||
|
namespace openarm::can::socket {
|
||||||
|
|
||||||
|
class GripperComponent : public damiao_motor::DMDeviceCollection {
|
||||||
|
public:
|
||||||
|
GripperComponent(canbus::CANSocket& can_socket);
|
||||||
|
~GripperComponent() = default;
|
||||||
|
|
||||||
|
void init_motor_device(damiao_motor::MotorType motor_type, uint32_t send_can_id,
|
||||||
|
uint32_t recv_can_id, bool use_fd);
|
||||||
|
|
||||||
|
// Gripper-specific controls
|
||||||
|
void open(double kp = 50.0, double kd = 1.0);
|
||||||
|
void close(double kp = 50.0, double kd = 1.0);
|
||||||
|
damiao_motor::Motor* get_motor() const { return motor_.get(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<damiao_motor::Motor> motor_;
|
||||||
|
std::shared_ptr<damiao_motor::DMCANDevice> motor_device_;
|
||||||
|
|
||||||
|
void set_position(double position, double kp = 50.0, double kd = 1.0);
|
||||||
|
|
||||||
|
// The actual physical gripper uses a slider cranker-like mechanism, this mapping is an
|
||||||
|
// approximation.
|
||||||
|
double gripper_to_motor_position(double gripper_position) {
|
||||||
|
// Map gripper position (0.0=closed, 1.0=open) to motor position
|
||||||
|
return (gripper_position - gripper_open_position_) /
|
||||||
|
(gripper_closed_position_ - gripper_open_position_) *
|
||||||
|
(motor_closed_position_ - motor_open_position_) +
|
||||||
|
motor_open_position_;
|
||||||
|
}
|
||||||
|
|
||||||
|
double motor_to_gripper_position(double motor_position) {
|
||||||
|
// Map motor position back to gripper position (0.0=closed, 1.0=open)
|
||||||
|
return (motor_position - motor_open_position_) /
|
||||||
|
(motor_closed_position_ - motor_open_position_) *
|
||||||
|
(gripper_closed_position_ - gripper_open_position_) +
|
||||||
|
gripper_open_position_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gripper configuration
|
||||||
|
double gripper_open_position_ = 1.0;
|
||||||
|
double gripper_closed_position_ = 0.0;
|
||||||
|
double motor_open_position_ = -1.0472; // 60 degrees
|
||||||
|
double motor_closed_position_ = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace openarm::can::socket
|
||||||
68
include/openarm/can/socket/openarm.hpp
Normal file
68
include/openarm/can/socket/openarm.hpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "../../canbus/can_device_collection.hpp"
|
||||||
|
#include "../../canbus/can_socket.hpp"
|
||||||
|
#include "arm_component.hpp"
|
||||||
|
#include "gripper_component.hpp"
|
||||||
|
|
||||||
|
namespace openarm::can::socket {
|
||||||
|
class OpenArm {
|
||||||
|
public:
|
||||||
|
OpenArm(const std::string& can_interface, bool enable_fd = false);
|
||||||
|
~OpenArm() = default;
|
||||||
|
|
||||||
|
// Component initialization
|
||||||
|
void init_arm_motors(const std::vector<damiao_motor::MotorType>& motor_types,
|
||||||
|
const std::vector<uint32_t>& send_can_ids,
|
||||||
|
const std::vector<uint32_t>& recv_can_ids);
|
||||||
|
|
||||||
|
void init_gripper_motor(damiao_motor::MotorType motor_type, uint32_t send_can_id,
|
||||||
|
uint32_t recv_can_id);
|
||||||
|
|
||||||
|
// Component access
|
||||||
|
ArmComponent& get_arm() { return *arm_; }
|
||||||
|
GripperComponent& get_gripper() { return *gripper_; }
|
||||||
|
canbus::CANDeviceCollection& get_master_can_device_collection() {
|
||||||
|
return *master_can_device_collection_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Damiao Motor operations (works only on sub_dm_device_collections_)
|
||||||
|
void enable_all();
|
||||||
|
void disable_all();
|
||||||
|
void set_zero_all();
|
||||||
|
void refresh_all();
|
||||||
|
// The timeout for reading from socket, set to timeout_us.
|
||||||
|
// Tuning this value may improve the performance but should be done with caution.
|
||||||
|
void recv_all(int timeout_us = 500);
|
||||||
|
void set_callback_mode_all(damiao_motor::CallbackMode callback_mode);
|
||||||
|
void query_param_all(int RID);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string can_interface_;
|
||||||
|
bool enable_fd_;
|
||||||
|
std::unique_ptr<canbus::CANSocket> can_socket_;
|
||||||
|
std::unique_ptr<ArmComponent> arm_;
|
||||||
|
std::unique_ptr<GripperComponent> gripper_;
|
||||||
|
std::unique_ptr<canbus::CANDeviceCollection> master_can_device_collection_;
|
||||||
|
std::vector<damiao_motor::DMDeviceCollection*> sub_dm_device_collections_;
|
||||||
|
void register_dm_device_collection(damiao_motor::DMDeviceCollection& device_collection);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace openarm::can::socket
|
||||||
50
include/openarm/canbus/can_device.hpp
Normal file
50
include/openarm/canbus/can_device.hpp
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <linux/can.h>
|
||||||
|
#include <linux/can/raw.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace openarm::canbus {
|
||||||
|
// Abstract base class for CAN devices
|
||||||
|
class CANDevice {
|
||||||
|
public:
|
||||||
|
explicit CANDevice(canid_t send_can_id, canid_t recv_can_id, canid_t recv_can_mask,
|
||||||
|
bool is_fd_enabled = false)
|
||||||
|
: send_can_id_(send_can_id),
|
||||||
|
recv_can_id_(recv_can_id),
|
||||||
|
recv_can_mask_(recv_can_mask),
|
||||||
|
is_fd_enabled_(is_fd_enabled) {}
|
||||||
|
virtual ~CANDevice() = default;
|
||||||
|
|
||||||
|
virtual void callback(const can_frame& frame) = 0;
|
||||||
|
virtual void callback(const canfd_frame& frame) = 0;
|
||||||
|
|
||||||
|
canid_t get_send_can_id() const { return send_can_id_; }
|
||||||
|
canid_t get_recv_can_id() const { return recv_can_id_; }
|
||||||
|
canid_t get_recv_can_mask() const { return recv_can_mask_; }
|
||||||
|
bool is_fd_enabled() const { return is_fd_enabled_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
canid_t send_can_id_;
|
||||||
|
canid_t recv_can_id_;
|
||||||
|
// mask for receiving
|
||||||
|
canid_t recv_can_mask_ = CAN_SFF_MASK;
|
||||||
|
bool is_fd_enabled_ = false;
|
||||||
|
};
|
||||||
|
} // namespace openarm::canbus
|
||||||
43
include/openarm/canbus/can_device_collection.hpp
Normal file
43
include/openarm/canbus/can_device_collection.hpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "can_device.hpp"
|
||||||
|
#include "can_socket.hpp"
|
||||||
|
|
||||||
|
namespace openarm::canbus {
|
||||||
|
class CANDeviceCollection {
|
||||||
|
public:
|
||||||
|
CANDeviceCollection(canbus::CANSocket& can_socket);
|
||||||
|
~CANDeviceCollection();
|
||||||
|
|
||||||
|
void add_device(const std::shared_ptr<CANDevice>& device);
|
||||||
|
void remove_device(const std::shared_ptr<CANDevice>& device);
|
||||||
|
void dispatch_frame_callback(can_frame& frame);
|
||||||
|
void dispatch_frame_callback(canfd_frame& frame);
|
||||||
|
const std::map<canid_t, std::shared_ptr<CANDevice>>& get_devices() const { return devices_; }
|
||||||
|
canbus::CANSocket& get_can_socket() const { return can_socket_; }
|
||||||
|
int get_socket_fd() const { return can_socket_.get_socket_fd(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
canbus::CANSocket& can_socket_;
|
||||||
|
std::map<canid_t, std::shared_ptr<CANDevice>> devices_;
|
||||||
|
};
|
||||||
|
} // namespace openarm::canbus
|
||||||
75
include/openarm/canbus/can_socket.hpp
Normal file
75
include/openarm/canbus/can_socket.hpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <linux/can.h>
|
||||||
|
#include <linux/can/raw.h>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace openarm::canbus {
|
||||||
|
|
||||||
|
// Exception classes for socket operations
|
||||||
|
class CANSocketException : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
explicit CANSocketException(const std::string& message)
|
||||||
|
: std::runtime_error("Socket error: " + message) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Base socket management class
|
||||||
|
class CANSocket {
|
||||||
|
public:
|
||||||
|
explicit CANSocket(const std::string& interface, bool enable_fd = false);
|
||||||
|
~CANSocket();
|
||||||
|
|
||||||
|
// Disable copy, enable move
|
||||||
|
CANSocket(const CANSocket&) = delete;
|
||||||
|
CANSocket& operator=(const CANSocket&) = delete;
|
||||||
|
CANSocket(CANSocket&&) = default;
|
||||||
|
CANSocket& operator=(CANSocket&&) = default;
|
||||||
|
|
||||||
|
// File descriptor access for Python bindings
|
||||||
|
int get_socket_fd() const { return socket_fd_; }
|
||||||
|
const std::string& get_interface() const { return interface_; }
|
||||||
|
bool is_canfd_enabled() const { return fd_enabled_; }
|
||||||
|
bool is_initialized() const { return initialized_; }
|
||||||
|
|
||||||
|
// Direct frame operations for Python bindings
|
||||||
|
ssize_t read_raw_frame(void* buffer, size_t buffer_size);
|
||||||
|
ssize_t write_raw_frame(const void* buffer, size_t frame_size);
|
||||||
|
|
||||||
|
// write can_frame or canfd_frame
|
||||||
|
bool write_can_frame(const can_frame& frame);
|
||||||
|
bool write_canfd_frame(const canfd_frame& frame);
|
||||||
|
|
||||||
|
// read can_frame or canfd_frame
|
||||||
|
bool read_can_frame(can_frame& frame);
|
||||||
|
bool read_canfd_frame(canfd_frame& frame);
|
||||||
|
|
||||||
|
// check if data is available for reading (non-blocking)
|
||||||
|
bool is_data_available(int timeout_us = 100);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool initialize_socket(const std::string& interface);
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
|
int socket_fd_;
|
||||||
|
std::string interface_;
|
||||||
|
bool fd_enabled_;
|
||||||
|
bool initialized_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace openarm::canbus
|
||||||
77
include/openarm/damiao_motor/dm_motor.hpp
Normal file
77
include/openarm/damiao_motor/dm_motor.hpp
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "dm_motor_constants.hpp"
|
||||||
|
|
||||||
|
namespace openarm::damiao_motor {
|
||||||
|
class Motor {
|
||||||
|
friend class DMCANDevice; // Allow MotorDeviceCan to access protected
|
||||||
|
// members
|
||||||
|
friend class DMControl;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Constructor
|
||||||
|
Motor(MotorType motor_type, uint32_t send_can_id, uint32_t recv_can_id);
|
||||||
|
|
||||||
|
// State getters
|
||||||
|
double get_position() const { return state_q_; }
|
||||||
|
double get_velocity() const { return state_dq_; }
|
||||||
|
double get_torque() const { return state_tau_; }
|
||||||
|
int get_state_tmos() const { return state_tmos_; }
|
||||||
|
int get_state_trotor() const { return state_trotor_; }
|
||||||
|
|
||||||
|
// Motor property getters
|
||||||
|
uint32_t get_send_can_id() const { return send_can_id_; }
|
||||||
|
uint32_t get_recv_can_id() const { return recv_can_id_; }
|
||||||
|
MotorType get_motor_type() const { return motor_type_; }
|
||||||
|
|
||||||
|
// Enable status getters
|
||||||
|
bool is_enabled() const { return enabled_; }
|
||||||
|
|
||||||
|
// Parameter methods
|
||||||
|
double get_param(int RID) const;
|
||||||
|
|
||||||
|
// Static methods for motor properties
|
||||||
|
static LimitParam get_limit_param(MotorType motor_type);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// State update methods
|
||||||
|
void update_state(double q, double dq, double tau, int tmos, int trotor);
|
||||||
|
void set_state_tmos(int tmos);
|
||||||
|
void set_state_trotor(int trotor);
|
||||||
|
void set_enabled(bool enabled);
|
||||||
|
void set_temp_param(int RID, int val);
|
||||||
|
|
||||||
|
// Motor identifiers
|
||||||
|
uint32_t send_can_id_;
|
||||||
|
uint32_t recv_can_id_;
|
||||||
|
MotorType motor_type_;
|
||||||
|
|
||||||
|
// Enable status
|
||||||
|
bool enabled_;
|
||||||
|
|
||||||
|
// Current state
|
||||||
|
double state_q_, state_dq_, state_tau_;
|
||||||
|
int state_tmos_, state_trotor_;
|
||||||
|
|
||||||
|
// Parameter storage
|
||||||
|
std::map<int, double> temp_param_dict_;
|
||||||
|
};
|
||||||
|
} // namespace openarm::damiao_motor
|
||||||
111
include/openarm/damiao_motor/dm_motor_constants.hpp
Normal file
111
include/openarm/damiao_motor/dm_motor_constants.hpp
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace openarm::damiao_motor {
|
||||||
|
enum class MotorType : uint8_t {
|
||||||
|
DM3507 = 0,
|
||||||
|
DM4310 = 1,
|
||||||
|
DM4310_48V = 2,
|
||||||
|
DM4340 = 3,
|
||||||
|
DM4340_48V = 4,
|
||||||
|
DM6006 = 5,
|
||||||
|
DM8006 = 6,
|
||||||
|
DM8009 = 7,
|
||||||
|
DM10010L = 8,
|
||||||
|
DM10010 = 9,
|
||||||
|
DMH3510 = 10,
|
||||||
|
DMH6215 = 11,
|
||||||
|
DMG6220 = 12,
|
||||||
|
COUNT = 13
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class RID : uint8_t {
|
||||||
|
UV_Value = 0,
|
||||||
|
KT_Value = 1,
|
||||||
|
OT_Value = 2,
|
||||||
|
OC_Value = 3,
|
||||||
|
ACC = 4,
|
||||||
|
DEC = 5,
|
||||||
|
MAX_SPD = 6,
|
||||||
|
MST_ID = 7,
|
||||||
|
ESC_ID = 8,
|
||||||
|
TIMEOUT = 9,
|
||||||
|
CTRL_MODE = 10,
|
||||||
|
Damp = 11,
|
||||||
|
Inertia = 12,
|
||||||
|
hw_ver = 13,
|
||||||
|
sw_ver = 14,
|
||||||
|
SN = 15,
|
||||||
|
NPP = 16,
|
||||||
|
Rs = 17,
|
||||||
|
LS = 18,
|
||||||
|
Flux = 19,
|
||||||
|
Gr = 20,
|
||||||
|
PMAX = 21,
|
||||||
|
VMAX = 22,
|
||||||
|
TMAX = 23,
|
||||||
|
I_BW = 24,
|
||||||
|
KP_ASR = 25,
|
||||||
|
KI_ASR = 26,
|
||||||
|
KP_APR = 27,
|
||||||
|
KI_APR = 28,
|
||||||
|
OV_Value = 29,
|
||||||
|
GREF = 30,
|
||||||
|
Deta = 31,
|
||||||
|
V_BW = 32,
|
||||||
|
IQ_c1 = 33,
|
||||||
|
VL_c1 = 34,
|
||||||
|
can_br = 35,
|
||||||
|
sub_ver = 36,
|
||||||
|
u_off = 50,
|
||||||
|
v_off = 51,
|
||||||
|
k1 = 52,
|
||||||
|
k2 = 53,
|
||||||
|
m_off = 54,
|
||||||
|
dir = 55,
|
||||||
|
p_m = 80,
|
||||||
|
xout = 81,
|
||||||
|
COUNT = 82
|
||||||
|
};
|
||||||
|
|
||||||
|
// Limit parameters structure for different motor types
|
||||||
|
struct LimitParam {
|
||||||
|
double pMax; // Position limit (rad)
|
||||||
|
double vMax; // Velocity limit (rad/s)
|
||||||
|
double tMax; // Torque limit (Nm)
|
||||||
|
};
|
||||||
|
// Limit parameters for each motor type [pMax, vMax, tMax]
|
||||||
|
inline constexpr std::array<LimitParam, static_cast<std::size_t>(MotorType::COUNT)>
|
||||||
|
MOTOR_LIMIT_PARAMS = {{
|
||||||
|
{12.5, 50, 5}, // DM3507
|
||||||
|
{12.5, 30, 10}, // DM4310
|
||||||
|
{12.5, 50, 10}, // DM4310_48V
|
||||||
|
{12.5, 8, 28}, // DM4340
|
||||||
|
{12.5, 10, 28}, // DM4340_48V
|
||||||
|
{12.5, 45, 20}, // DM6006
|
||||||
|
{12.5, 45, 40}, // DM8006
|
||||||
|
{12.5, 45, 54}, // DM8009
|
||||||
|
{12.5, 25, 200}, // DM10010L
|
||||||
|
{12.5, 20, 200}, // DM10010
|
||||||
|
{12.5, 280, 1}, // DMH3510
|
||||||
|
{12.5, 45, 10}, // DMH6215
|
||||||
|
{12.5, 45, 10} // DMG6220
|
||||||
|
}};
|
||||||
|
} // namespace openarm::damiao_motor
|
||||||
91
include/openarm/damiao_motor/dm_motor_control.hpp
Normal file
91
include/openarm/damiao_motor/dm_motor_control.hpp
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <linux/can.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring> // for memcpy
|
||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "dm_motor.hpp"
|
||||||
|
#include "dm_motor_constants.hpp"
|
||||||
|
|
||||||
|
namespace openarm::damiao_motor {
|
||||||
|
// Forward declarations
|
||||||
|
class Motor;
|
||||||
|
|
||||||
|
struct ParamResult {
|
||||||
|
int rid;
|
||||||
|
double value;
|
||||||
|
bool valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StateResult {
|
||||||
|
double position;
|
||||||
|
double velocity;
|
||||||
|
double torque;
|
||||||
|
int t_mos;
|
||||||
|
int t_rotor;
|
||||||
|
bool valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CANPacket {
|
||||||
|
uint32_t send_can_id;
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MITParam {
|
||||||
|
double kp;
|
||||||
|
double kd;
|
||||||
|
double q;
|
||||||
|
double dq;
|
||||||
|
double tau;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CanPacketEncoder {
|
||||||
|
public:
|
||||||
|
static CANPacket create_enable_command(const Motor& motor);
|
||||||
|
static CANPacket create_disable_command(const Motor& motor);
|
||||||
|
static CANPacket create_set_zero_command(const Motor& motor);
|
||||||
|
static CANPacket create_mit_control_command(const Motor& motor, const MITParam& mit_param);
|
||||||
|
static CANPacket create_query_param_command(const Motor& motor, int RID);
|
||||||
|
static CANPacket create_refresh_command(const Motor& motor);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::vector<uint8_t> pack_mit_control_data(MotorType motor_type,
|
||||||
|
const MITParam& mit_param);
|
||||||
|
static std::vector<uint8_t> pack_query_param_data(uint32_t send_can_id, int RID);
|
||||||
|
static std::vector<uint8_t> pack_command_data(uint8_t cmd);
|
||||||
|
|
||||||
|
static double limit_min_max(double x, double min, double max);
|
||||||
|
static uint16_t double_to_uint(double x, double x_min, double x_max, int bits);
|
||||||
|
};
|
||||||
|
|
||||||
|
class CanPacketDecoder {
|
||||||
|
public:
|
||||||
|
static StateResult parse_motor_state_data(const Motor& motor, const std::vector<uint8_t>& data);
|
||||||
|
static ParamResult parse_motor_param_data(const std::vector<uint8_t>& data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static double uint_to_double(uint16_t x, double min, double max, int bits);
|
||||||
|
static float uint8s_to_float(const std::array<uint8_t, 4>& bytes);
|
||||||
|
static uint32_t uint8s_to_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4);
|
||||||
|
static bool is_in_ranges(int number);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace openarm::damiao_motor
|
||||||
50
include/openarm/damiao_motor/dm_motor_device.hpp
Normal file
50
include/openarm/damiao_motor/dm_motor_device.hpp
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../canbus/can_device.hpp"
|
||||||
|
#include "../canbus/can_socket.hpp"
|
||||||
|
#include "dm_motor.hpp"
|
||||||
|
#include "dm_motor_control.hpp"
|
||||||
|
|
||||||
|
namespace openarm::damiao_motor {
|
||||||
|
enum CallbackMode {
|
||||||
|
STATE,
|
||||||
|
PARAM,
|
||||||
|
// discard
|
||||||
|
IGNORE
|
||||||
|
};
|
||||||
|
|
||||||
|
class DMCANDevice : public canbus::CANDevice {
|
||||||
|
public:
|
||||||
|
explicit DMCANDevice(Motor& motor, canid_t recv_can_mask, bool use_fd);
|
||||||
|
void callback(const can_frame& frame);
|
||||||
|
void callback(const canfd_frame& frame);
|
||||||
|
|
||||||
|
// Create frame from data array
|
||||||
|
can_frame create_can_frame(canid_t send_can_id, std::vector<uint8_t> data);
|
||||||
|
canfd_frame create_canfd_frame(canid_t send_can_id, std::vector<uint8_t> data);
|
||||||
|
// Getter method to access motor state
|
||||||
|
Motor& get_motor() { return motor_; }
|
||||||
|
void set_callback_mode(CallbackMode callback_mode) { callback_mode_ = callback_mode; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<uint8_t> get_data_from_frame(const can_frame& frame);
|
||||||
|
std::vector<uint8_t> get_data_from_frame(const canfd_frame& frame);
|
||||||
|
Motor& motor_;
|
||||||
|
CallbackMode callback_mode_;
|
||||||
|
bool use_fd_; // Track if using CAN-FD
|
||||||
|
};
|
||||||
|
} // namespace openarm::damiao_motor
|
||||||
64
include/openarm/damiao_motor/dm_motor_device_collection.hpp
Normal file
64
include/openarm/damiao_motor/dm_motor_device_collection.hpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../canbus/can_device_collection.hpp"
|
||||||
|
#include "dm_motor_constants.hpp"
|
||||||
|
#include "dm_motor_control.hpp"
|
||||||
|
#include "dm_motor_device.hpp"
|
||||||
|
|
||||||
|
namespace openarm::damiao_motor {
|
||||||
|
|
||||||
|
class DMDeviceCollection {
|
||||||
|
public:
|
||||||
|
DMDeviceCollection(canbus::CANSocket& can_socket);
|
||||||
|
virtual ~DMDeviceCollection() = default;
|
||||||
|
|
||||||
|
// Common motor operations
|
||||||
|
void enable_all();
|
||||||
|
void disable_all();
|
||||||
|
void set_zero_all();
|
||||||
|
void set_callback_mode_all(CallbackMode callback_mode);
|
||||||
|
|
||||||
|
// Refresh operations (for individual motors)
|
||||||
|
void refresh_one(int i);
|
||||||
|
void refresh_all();
|
||||||
|
|
||||||
|
// Query parameter operations
|
||||||
|
void query_param_one(int i, int RID);
|
||||||
|
void query_param_all(int RID);
|
||||||
|
|
||||||
|
// MIT control operations
|
||||||
|
void mit_control_one(int i, const MITParam& mit_param);
|
||||||
|
void mit_control_all(const std::vector<MITParam>& mit_params);
|
||||||
|
|
||||||
|
// Device collection access
|
||||||
|
std::vector<Motor> get_motors() const;
|
||||||
|
canbus::CANDeviceCollection& get_device_collection() { return *device_collection_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
canbus::CANSocket& can_socket_;
|
||||||
|
std::unique_ptr<CanPacketEncoder> can_packet_encoder_;
|
||||||
|
std::unique_ptr<CanPacketDecoder> can_packet_decoder_;
|
||||||
|
std::unique_ptr<canbus::CANDeviceCollection> device_collection_;
|
||||||
|
|
||||||
|
// Helper methods for subclasses
|
||||||
|
void send_command_to_device(std::shared_ptr<DMCANDevice> dm_device, const CANPacket& packet);
|
||||||
|
std::vector<std::shared_ptr<DMCANDevice>> get_dm_devices() const;
|
||||||
|
};
|
||||||
|
} // namespace openarm::damiao_motor
|
||||||
23
openarm-can.pc.in
Normal file
23
openarm-can.pc.in
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
prefix=@CMAKE_INSTALL_PREFIX@
|
||||||
|
includedir=@PKG_CONFIG_INCLUDEDIR@
|
||||||
|
libdir=@PKG_CONFIG_LIBDIR@
|
||||||
|
|
||||||
|
Name: OpenArm CAN
|
||||||
|
Description: OpenArm CAN library
|
||||||
|
Version: @PROJECT_VERSION@
|
||||||
|
Libs: -L${libdir} -lopenarm_can
|
||||||
|
Cflags: -I${includedir}
|
||||||
3
python/README.md
Normal file
3
python/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# OpenArm
|
||||||
|
|
||||||
|
...TODO...
|
||||||
55
python/build.sh
Executable file
55
python/build.sh
Executable file
@ -0,0 +1,55 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# Build script for OpenArm Python bindings
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Get script directory
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||||
|
|
||||||
|
echo "Building OpenArm Python bindings..."
|
||||||
|
echo "Script directory: $SCRIPT_DIR"
|
||||||
|
echo "Project root: $PROJECT_ROOT"
|
||||||
|
|
||||||
|
# Check if we're in a virtual environment
|
||||||
|
if [[ "$VIRTUAL_ENV" != "" ]]; then
|
||||||
|
echo "Using virtual environment: $VIRTUAL_ENV"
|
||||||
|
else
|
||||||
|
echo "Warning: Not in a virtual environment. Consider using 'python -m venv venv && source venv/bin/activate'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build the C++ library first if needed
|
||||||
|
if [ ! -d "$PROJECT_ROOT/build" ]; then
|
||||||
|
echo "Building C++ library..."
|
||||||
|
mkdir -p "$PROJECT_ROOT/build"
|
||||||
|
cd "$PROJECT_ROOT/build"
|
||||||
|
cmake ..
|
||||||
|
make -j$(nproc)
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install in development mode
|
||||||
|
echo "Installing in development mode..."
|
||||||
|
pip install .
|
||||||
|
|
||||||
|
echo "Build completed successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "To test the installation, run:"
|
||||||
|
echo " python -c 'import openarm; print(openarm.__version__)'"
|
||||||
|
echo ""
|
||||||
|
echo "See examples/ directory for usage examples."
|
||||||
56
python/examples/example.py
Normal file
56
python/examples/example.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import openarm_can as oa
|
||||||
|
import time
|
||||||
|
# Create OpenArm instance
|
||||||
|
|
||||||
|
arm = oa.OpenArm("can0", True)
|
||||||
|
|
||||||
|
# Initialize arm motors
|
||||||
|
motor_types = [oa.MotorType.DM4310, oa.MotorType.DM4310]
|
||||||
|
send_ids = [0x01, 0x02]
|
||||||
|
recv_ids = [0x11, 0x12]
|
||||||
|
arm.init_arm_motors(motor_types, send_ids, recv_ids)
|
||||||
|
|
||||||
|
# Initialize gripper
|
||||||
|
arm.init_gripper_motor(oa.MotorType.DM4310, 0x7, 0x17)
|
||||||
|
arm.set_callback_mode_all(oa.CallbackMode.IGNORE)
|
||||||
|
# Use high-level operations
|
||||||
|
arm.enable_all()
|
||||||
|
arm.recv_all()
|
||||||
|
|
||||||
|
|
||||||
|
# return to zero position
|
||||||
|
arm.set_callback_mode_all(oa.CallbackMode.STATE)
|
||||||
|
arm.get_arm().mit_control_all([oa.MITParam(2, 0.5, 0, 0, 0),
|
||||||
|
oa.MITParam(2, 0.5, 0, 0, 0)])
|
||||||
|
|
||||||
|
arm.recv_all()
|
||||||
|
|
||||||
|
# torque control test
|
||||||
|
|
||||||
|
arm.get_gripper().mit_control_all([oa.MITParam(0, 0, 0, 0, 0.15)])
|
||||||
|
arm.get_arm().mit_control_all(
|
||||||
|
[oa.MITParam(0, 0, 0, 0, 0.15), oa.MITParam(0, 0, 0, 0, 0.15)])
|
||||||
|
arm.recv_all()
|
||||||
|
|
||||||
|
# read motor position
|
||||||
|
while True:
|
||||||
|
arm.refresh_all()
|
||||||
|
arm.recv_all()
|
||||||
|
for motor in arm.get_arm().get_motors():
|
||||||
|
print(motor.get_position())
|
||||||
|
for motor in arm.get_gripper().get_motors():
|
||||||
|
print(motor.get_position())
|
||||||
41
python/meson.build
Normal file
41
python/meson.build
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
project(
|
||||||
|
'openarm_can',
|
||||||
|
'cpp',
|
||||||
|
version: '1.0.0',
|
||||||
|
default_options: ['cpp_std=c++17'],
|
||||||
|
)
|
||||||
|
|
||||||
|
py = import('python').find_installation(pure: false)
|
||||||
|
nanobind_dep = dependency('nanobind')
|
||||||
|
|
||||||
|
openarm_cpp_dep = dependency(
|
||||||
|
'openarm-can',
|
||||||
|
'OpenArmCAN',
|
||||||
|
modules: ['OpenArmCAN::openarm_can'],
|
||||||
|
)
|
||||||
|
|
||||||
|
py.install_sources(
|
||||||
|
'openarm/can/__init__.py',
|
||||||
|
'openarm/can/core.py',
|
||||||
|
subdir: 'openarm/can',
|
||||||
|
)
|
||||||
|
py.extension_module(
|
||||||
|
'openarm_can',
|
||||||
|
'src/openarm_can.cpp',
|
||||||
|
install: true,
|
||||||
|
dependencies: [openarm_cpp_dep, nanobind_dep],
|
||||||
|
)
|
||||||
53
python/openarm/can/__init__.py
Normal file
53
python/openarm/can/__init__.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
OpenArm CAN Python bindings for motor control via SocketCAN.
|
||||||
|
|
||||||
|
This package provides Python bindings for the OpenArm motor control system,
|
||||||
|
allowing you to control DAMIAO motors through SocketCAN.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Import all C++ bindings directly - 1:1 mapping with C++ API
|
||||||
|
from .core import *
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Enactic, Inc."
|
||||||
|
|
||||||
|
# Direct export of C++ classes - no wrappers
|
||||||
|
__all__ = [
|
||||||
|
# Enums
|
||||||
|
"MotorType",
|
||||||
|
"MotorVariable",
|
||||||
|
"CallbackMode",
|
||||||
|
|
||||||
|
# Data structures
|
||||||
|
"LimitParam",
|
||||||
|
"ParamResult",
|
||||||
|
"MotorStateResult",
|
||||||
|
"CanFrame",
|
||||||
|
"CanFdFrame",
|
||||||
|
"MITParam",
|
||||||
|
|
||||||
|
# Main C++ classes (1:1 mapping)
|
||||||
|
"Motor",
|
||||||
|
"MotorControl",
|
||||||
|
"CANSocket", # Low-level socket with file descriptor access
|
||||||
|
"CANDevice", # Base CAN device class
|
||||||
|
"MotorDeviceCan", # Motor device management
|
||||||
|
"CANDeviceCollection", # Device collection management
|
||||||
|
|
||||||
|
# Exceptions
|
||||||
|
"CANSocketException",
|
||||||
|
]
|
||||||
53
python/openarm/can/core.py
Normal file
53
python/openarm/can/core.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Core bindings module that imports the C++ extension.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
from openarm_can import *
|
||||||
|
except ImportError as e:
|
||||||
|
raise ImportError(
|
||||||
|
"Failed to import openarm_can C++ extension. "
|
||||||
|
"Make sure the package is properly installed. "
|
||||||
|
f"Original error: {e}"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
# Re-export main classes with better names if needed
|
||||||
|
__all__ = [
|
||||||
|
# Enums
|
||||||
|
"MotorType",
|
||||||
|
"MotorVariable",
|
||||||
|
"CallbackMode",
|
||||||
|
|
||||||
|
# Data structures
|
||||||
|
"LimitParam",
|
||||||
|
"ParamResult",
|
||||||
|
"MotorStateResult",
|
||||||
|
"CanFrame",
|
||||||
|
"CanFdFrame",
|
||||||
|
"MITParam",
|
||||||
|
|
||||||
|
# Main classes (1:1 C++ mapping)
|
||||||
|
"Motor",
|
||||||
|
"MotorControl",
|
||||||
|
"CANSocket",
|
||||||
|
"CANDevice",
|
||||||
|
"MotorDeviceCan",
|
||||||
|
"CANDeviceCollection",
|
||||||
|
|
||||||
|
# Exceptions
|
||||||
|
"CANSocketException",
|
||||||
|
]
|
||||||
36
python/pyproject.toml
Normal file
36
python/pyproject.toml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
build-backend = "mesonpy"
|
||||||
|
requires = ["meson-python"]
|
||||||
|
|
||||||
|
[project]
|
||||||
|
authors = [{name = "Enactic, Inc."}]
|
||||||
|
license = "Apache-2.0"
|
||||||
|
# license-files = ["../LICENSE.txt"]
|
||||||
|
maintainers = [{name = "Enactic, Inc."}]
|
||||||
|
name = "openarm_can"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">= 3.10"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
# TODO: changelog = "https://github.com/reazon-research/openarm_socketcan/releases"
|
||||||
|
# TODO: documentation = "https://..."
|
||||||
|
# TODO: homepage = "https://open-arm.org/"
|
||||||
|
# TODO: issues = "https://github.com/reazon-research/openarm_socketcan/issues"
|
||||||
|
# TODO: repository = "https://github.com/reazon-research/openarm_socketcan.git"
|
||||||
|
|
||||||
|
[tool.autopep8]
|
||||||
13
python/requirements.txt
Executable file
13
python/requirements.txt
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
390
python/src/openarm_can.cpp
Normal file
390
python/src/openarm_can.cpp
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include <nanobind/nanobind.h>
|
||||||
|
#include <nanobind/stl/string.h>
|
||||||
|
|
||||||
|
// Include the C++ headers
|
||||||
|
#include <linux/can.h>
|
||||||
|
#include <linux/can/raw.h>
|
||||||
|
|
||||||
|
#include <openarm/can/socket/arm_component.hpp>
|
||||||
|
#include <openarm/can/socket/gripper_component.hpp>
|
||||||
|
#include <openarm/can/socket/openarm.hpp>
|
||||||
|
#include <openarm/canbus/can_device.hpp>
|
||||||
|
#include <openarm/canbus/can_device_collection.hpp>
|
||||||
|
#include <openarm/canbus/can_socket.hpp>
|
||||||
|
#include <openarm/damiao_motor/dm_motor.hpp>
|
||||||
|
#include <openarm/damiao_motor/dm_motor_constants.hpp>
|
||||||
|
#include <openarm/damiao_motor/dm_motor_control.hpp>
|
||||||
|
#include <openarm/damiao_motor/dm_motor_device.hpp>
|
||||||
|
#include <openarm/damiao_motor/dm_motor_device_collection.hpp>
|
||||||
|
|
||||||
|
using namespace openarm::canbus;
|
||||||
|
using namespace openarm::damiao_motor;
|
||||||
|
using namespace openarm::can::socket;
|
||||||
|
|
||||||
|
namespace nb = nanobind;
|
||||||
|
|
||||||
|
NB_MODULE(openarm_can, m) {
|
||||||
|
m.doc() = "OpenArm CAN Python bindings for motor control via SocketCAN";
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DAMIAO MOTOR NAMESPACE - ENUMS AND CONSTANTS
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Motor Type enum
|
||||||
|
nb::enum_<MotorType>(m, "MotorType")
|
||||||
|
.value("DM3507", MotorType::DM3507)
|
||||||
|
.value("DM4310", MotorType::DM4310)
|
||||||
|
.value("DM4310_48V", MotorType::DM4310_48V)
|
||||||
|
.value("DM4340", MotorType::DM4340)
|
||||||
|
.value("DM4340_48V", MotorType::DM4340_48V)
|
||||||
|
.value("DM6006", MotorType::DM6006)
|
||||||
|
.value("DM8006", MotorType::DM8006)
|
||||||
|
.value("DM8009", MotorType::DM8009)
|
||||||
|
.value("DM10010L", MotorType::DM10010L)
|
||||||
|
.value("DM10010", MotorType::DM10010)
|
||||||
|
.value("DMH3510", MotorType::DMH3510)
|
||||||
|
.value("DMH6215", MotorType::DMH6215)
|
||||||
|
.value("DMG6220", MotorType::DMG6220)
|
||||||
|
.value("COUNT", MotorType::COUNT)
|
||||||
|
.export_values();
|
||||||
|
|
||||||
|
// Motor Variable enum
|
||||||
|
nb::enum_<RID>(m, "MotorVariable")
|
||||||
|
.value("UV_Value", RID::UV_Value)
|
||||||
|
.value("KT_Value", RID::KT_Value)
|
||||||
|
.value("OT_Value", RID::OT_Value)
|
||||||
|
.value("OC_Value", RID::OC_Value)
|
||||||
|
.value("ACC", RID::ACC)
|
||||||
|
.value("DEC", RID::DEC)
|
||||||
|
.value("MAX_SPD", RID::MAX_SPD)
|
||||||
|
.value("MST_ID", RID::MST_ID)
|
||||||
|
.value("ESC_ID", RID::ESC_ID)
|
||||||
|
.value("TIMEOUT", RID::TIMEOUT)
|
||||||
|
.value("CTRL_MODE", RID::CTRL_MODE)
|
||||||
|
.value("Damp", RID::Damp)
|
||||||
|
.value("Inertia", RID::Inertia)
|
||||||
|
.value("hw_ver", RID::hw_ver)
|
||||||
|
.value("sw_ver", RID::sw_ver)
|
||||||
|
.value("SN", RID::SN)
|
||||||
|
.value("NPP", RID::NPP)
|
||||||
|
.value("Rs", RID::Rs)
|
||||||
|
.value("LS", RID::LS)
|
||||||
|
.value("Flux", RID::Flux)
|
||||||
|
.value("Gr", RID::Gr)
|
||||||
|
.value("PMAX", RID::PMAX)
|
||||||
|
.value("VMAX", RID::VMAX)
|
||||||
|
.value("TMAX", RID::TMAX)
|
||||||
|
.value("I_BW", RID::I_BW)
|
||||||
|
.value("KP_ASR", RID::KP_ASR)
|
||||||
|
.value("KI_ASR", RID::KI_ASR)
|
||||||
|
.value("KP_APR", RID::KP_APR)
|
||||||
|
.value("KI_APR", RID::KI_APR)
|
||||||
|
.value("OV_Value", RID::OV_Value)
|
||||||
|
.value("GREF", RID::GREF)
|
||||||
|
.value("Deta", RID::Deta)
|
||||||
|
.value("V_BW", RID::V_BW)
|
||||||
|
.value("IQ_c1", RID::IQ_c1)
|
||||||
|
.value("VL_c1", RID::VL_c1)
|
||||||
|
.value("can_br", RID::can_br)
|
||||||
|
.value("sub_ver", RID::sub_ver)
|
||||||
|
.value("u_off", RID::u_off)
|
||||||
|
.value("v_off", RID::v_off)
|
||||||
|
.value("k1", RID::k1)
|
||||||
|
.value("k2", RID::k2)
|
||||||
|
.value("m_off", RID::m_off)
|
||||||
|
.value("dir", RID::dir)
|
||||||
|
.value("p_m", RID::p_m)
|
||||||
|
.value("xout", RID::xout)
|
||||||
|
.value("COUNT", RID::COUNT)
|
||||||
|
.export_values();
|
||||||
|
|
||||||
|
// Callback Mode enum
|
||||||
|
nb::enum_<CallbackMode>(m, "CallbackMode")
|
||||||
|
.value("STATE", CallbackMode::STATE)
|
||||||
|
.value("PARAM", CallbackMode::PARAM)
|
||||||
|
.value("IGNORE", CallbackMode::IGNORE)
|
||||||
|
.export_values();
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DAMIAO MOTOR NAMESPACE - STRUCTS
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// LimitParam struct
|
||||||
|
nb::class_<LimitParam>(m, "LimitParam")
|
||||||
|
.def(nb::init<>())
|
||||||
|
.def_rw("pMax", &LimitParam::pMax)
|
||||||
|
.def_rw("vMax", &LimitParam::vMax)
|
||||||
|
.def_rw("tMax", &LimitParam::tMax);
|
||||||
|
|
||||||
|
// ParamResult struct
|
||||||
|
nb::class_<ParamResult>(m, "ParamResult")
|
||||||
|
.def(nb::init<>())
|
||||||
|
.def_rw("rid", &ParamResult::rid)
|
||||||
|
.def_rw("value", &ParamResult::value)
|
||||||
|
.def_rw("valid", &ParamResult::valid);
|
||||||
|
|
||||||
|
// MotorStateResult struct
|
||||||
|
nb::class_<StateResult>(m, "MotorStateResult")
|
||||||
|
.def(nb::init<>())
|
||||||
|
.def_rw("position", &StateResult::position)
|
||||||
|
.def_rw("velocity", &StateResult::velocity)
|
||||||
|
.def_rw("torque", &StateResult::torque)
|
||||||
|
.def_rw("t_mos", &StateResult::t_mos)
|
||||||
|
.def_rw("t_rotor", &StateResult::t_rotor)
|
||||||
|
.def_rw("valid", &StateResult::valid);
|
||||||
|
|
||||||
|
// CANPacket struct
|
||||||
|
nb::class_<CANPacket>(m, "CANPacket")
|
||||||
|
.def(nb::init<>())
|
||||||
|
.def_rw("send_can_id", &CANPacket::send_can_id)
|
||||||
|
.def_rw("data", &CANPacket::data);
|
||||||
|
|
||||||
|
// MITParam struct
|
||||||
|
nb::class_<MITParam>(m, "MITParam")
|
||||||
|
.def(nb::init<>())
|
||||||
|
.def(
|
||||||
|
"__init__",
|
||||||
|
[](MITParam* param, double kp, double kd, double q, double dq, double tau) {
|
||||||
|
new (param) MITParam(MITParam{kp, kd, q, dq, tau});
|
||||||
|
},
|
||||||
|
nb::arg("kp"), nb::arg("kd"), nb::arg("q"), nb::arg("dq"), nb::arg("tau"))
|
||||||
|
.def_rw("kp", &MITParam::kp)
|
||||||
|
.def_rw("kd", &MITParam::kd)
|
||||||
|
.def_rw("q", &MITParam::q)
|
||||||
|
.def_rw("dq", &MITParam::dq)
|
||||||
|
.def_rw("tau", &MITParam::tau);
|
||||||
|
// ============================================================================
|
||||||
|
// DAMIAO MOTOR NAMESPACE - MAIN CLASSES
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Motor class
|
||||||
|
nb::class_<Motor>(m, "Motor")
|
||||||
|
.def(nb::init<MotorType, uint32_t, uint32_t>(), nb::arg("motor_type"),
|
||||||
|
nb::arg("send_can_id"), nb::arg("recv_can_id"))
|
||||||
|
.def("get_position", &Motor::get_position)
|
||||||
|
.def("get_velocity", &Motor::get_velocity)
|
||||||
|
.def("get_torque", &Motor::get_torque)
|
||||||
|
.def("get_state_tmos", &Motor::get_state_tmos)
|
||||||
|
.def("get_state_trotor", &Motor::get_state_trotor)
|
||||||
|
.def("get_send_can_id", &Motor::get_send_can_id)
|
||||||
|
.def("get_recv_can_id", &Motor::get_recv_can_id)
|
||||||
|
.def("get_motor_type", &Motor::get_motor_type)
|
||||||
|
.def("is_enabled", &Motor::is_enabled)
|
||||||
|
.def("get_param", &Motor::get_param, nb::arg("rid"))
|
||||||
|
.def_static("get_limit_param", &Motor::get_limit_param, nb::arg("motor_type"));
|
||||||
|
|
||||||
|
// MotorControl class
|
||||||
|
nb::class_<CanPacketEncoder>(m, "CanPacketEncoder")
|
||||||
|
.def_static("create_refresh_command", &CanPacketEncoder::create_refresh_command,
|
||||||
|
nb::arg("motor"))
|
||||||
|
.def_static("create_enable_command", &CanPacketEncoder::create_enable_command,
|
||||||
|
nb::arg("motor"))
|
||||||
|
.def_static("create_disable_command", &CanPacketEncoder::create_disable_command,
|
||||||
|
nb::arg("motor"))
|
||||||
|
.def_static("create_set_zero_command", &CanPacketEncoder::create_set_zero_command,
|
||||||
|
nb::arg("motor"))
|
||||||
|
.def_static("create_mit_control_command", &CanPacketEncoder::create_mit_control_command,
|
||||||
|
nb::arg("motor"), nb::arg("mit_param"))
|
||||||
|
.def_static("create_query_param_command", &CanPacketEncoder::create_query_param_command,
|
||||||
|
nb::arg("motor"), nb::arg("rid"));
|
||||||
|
|
||||||
|
nb::class_<CanPacketDecoder>(m, "CanPacketDecoder")
|
||||||
|
.def_static("parse_motor_state_data", &CanPacketDecoder::parse_motor_state_data,
|
||||||
|
nb::arg("motor"), nb::arg("data"))
|
||||||
|
.def_static("parse_motor_param_data", &CanPacketDecoder::parse_motor_param_data,
|
||||||
|
nb::arg("data"));
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// CANBUS NAMESPACE - EXCEPTIONS AND BASE CLASSES
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// CAN Socket Exception
|
||||||
|
nb::exception<CANSocketException>(m, "CANSocketException");
|
||||||
|
|
||||||
|
// CANDevice base class (MUST be defined before derived classes)
|
||||||
|
nb::class_<CANDevice>(m, "CANDevice")
|
||||||
|
.def("get_send_can_id", &CANDevice::get_send_can_id)
|
||||||
|
.def("get_recv_can_id", &CANDevice::get_recv_can_id)
|
||||||
|
.def("get_recv_can_mask", &CANDevice::get_recv_can_mask)
|
||||||
|
.def("is_fd_enabled", &CANDevice::is_fd_enabled);
|
||||||
|
|
||||||
|
// MotorDeviceCan class (NOW can inherit from CANDevice)
|
||||||
|
nb::class_<DMCANDevice, CANDevice>(m, "MotorDeviceCan")
|
||||||
|
.def(nb::init<Motor&, canid_t, bool>(), nb::arg("motor"), nb::arg("recv_can_mask"),
|
||||||
|
nb::arg("use_fd"))
|
||||||
|
.def("get_motor", &DMCANDevice::get_motor, nb::rv_policy::reference)
|
||||||
|
.def("callback",
|
||||||
|
static_cast<void (DMCANDevice::*)(const can_frame&)>(&DMCANDevice::callback),
|
||||||
|
nb::arg("frame"))
|
||||||
|
.def("callback",
|
||||||
|
static_cast<void (DMCANDevice::*)(const canfd_frame&)>(&DMCANDevice::callback),
|
||||||
|
nb::arg("frame"))
|
||||||
|
.def("create_can_frame", &DMCANDevice::create_can_frame, nb::arg("send_can_id"),
|
||||||
|
nb::arg("data"))
|
||||||
|
.def("create_canfd_frame", &DMCANDevice::create_canfd_frame, nb::arg("send_can_id"),
|
||||||
|
nb::arg("data"))
|
||||||
|
.def("set_callback_mode", &DMCANDevice::set_callback_mode, nb::arg("callback_mode"));
|
||||||
|
|
||||||
|
// CANDeviceCollection class
|
||||||
|
nb::class_<CANDeviceCollection>(m, "CANDeviceCollection")
|
||||||
|
.def(nb::init<CANSocket&>(), nb::arg("can_socket"))
|
||||||
|
.def(
|
||||||
|
"add_device",
|
||||||
|
[](CANDeviceCollection& self, std::shared_ptr<CANDevice> device) {
|
||||||
|
self.add_device(device);
|
||||||
|
},
|
||||||
|
nb::arg("device"))
|
||||||
|
.def(
|
||||||
|
"remove_device",
|
||||||
|
[](CANDeviceCollection& self, std::shared_ptr<CANDevice> device) {
|
||||||
|
self.remove_device(device);
|
||||||
|
},
|
||||||
|
nb::arg("device"))
|
||||||
|
.def("dispatch_frame_callback",
|
||||||
|
static_cast<void (CANDeviceCollection::*)(can_frame&)>(
|
||||||
|
&CANDeviceCollection::dispatch_frame_callback),
|
||||||
|
nb::arg("frame"))
|
||||||
|
.def("dispatch_frame_callback",
|
||||||
|
static_cast<void (CANDeviceCollection::*)(canfd_frame&)>(
|
||||||
|
&CANDeviceCollection::dispatch_frame_callback),
|
||||||
|
nb::arg("frame"))
|
||||||
|
.def("get_devices", &CANDeviceCollection::get_devices);
|
||||||
|
|
||||||
|
// CAN Socket class
|
||||||
|
nb::class_<CANSocket>(m, "CANSocket")
|
||||||
|
.def(nb::init<const std::string&, bool>(), nb::arg("interface"),
|
||||||
|
nb::arg("enable_fd") = false)
|
||||||
|
.def("get_socket_fd", &CANSocket::get_socket_fd)
|
||||||
|
.def("get_interface", &CANSocket::get_interface)
|
||||||
|
.def("is_canfd_enabled", &CANSocket::is_canfd_enabled)
|
||||||
|
.def("is_initialized", &CANSocket::is_initialized)
|
||||||
|
.def(
|
||||||
|
"read_raw_frame",
|
||||||
|
[](CANSocket& self, size_t buffer_size) {
|
||||||
|
std::vector<uint8_t> buffer(buffer_size);
|
||||||
|
ssize_t bytes_read = self.read_raw_frame(buffer.data(), buffer_size);
|
||||||
|
if (bytes_read > 0) {
|
||||||
|
buffer.resize(bytes_read);
|
||||||
|
return nb::bytes(reinterpret_cast<const char*>(buffer.data()), bytes_read);
|
||||||
|
}
|
||||||
|
return nb::bytes();
|
||||||
|
},
|
||||||
|
nb::arg("buffer_size"))
|
||||||
|
.def(
|
||||||
|
"write_raw_frame",
|
||||||
|
[](CANSocket& self, nb::bytes data) {
|
||||||
|
return self.write_raw_frame(data.data(), data.size());
|
||||||
|
},
|
||||||
|
nb::arg("data"))
|
||||||
|
.def("write_can_frame", &CANSocket::write_can_frame, nb::arg("frame"))
|
||||||
|
.def("read_can_frame", &CANSocket::read_can_frame, nb::arg("frame"))
|
||||||
|
.def("write_canfd_frame", &CANSocket::write_canfd_frame, nb::arg("frame"))
|
||||||
|
.def("read_canfd_frame", &CANSocket::read_canfd_frame, nb::arg("frame"));
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// LINUX CAN FRAME STRUCTURES
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// CAN frame structures
|
||||||
|
nb::class_<can_frame>(m, "CanFrame")
|
||||||
|
.def(nb::init<>())
|
||||||
|
.def_rw("can_id", &can_frame::can_id)
|
||||||
|
.def_rw("can_dlc", &can_frame::can_dlc)
|
||||||
|
.def_prop_rw(
|
||||||
|
"data",
|
||||||
|
[](const can_frame& frame) {
|
||||||
|
return nb::bytes(reinterpret_cast<const char*>(frame.data), frame.can_dlc);
|
||||||
|
},
|
||||||
|
[](can_frame& frame, nb::bytes data) {
|
||||||
|
size_t len = std::min(data.size(), sizeof(frame.data));
|
||||||
|
frame.can_dlc = len;
|
||||||
|
std::memcpy(frame.data, data.data(), len);
|
||||||
|
});
|
||||||
|
|
||||||
|
nb::class_<canfd_frame>(m, "CanFdFrame")
|
||||||
|
.def(nb::init<>())
|
||||||
|
.def_rw("can_id", &canfd_frame::can_id)
|
||||||
|
.def_rw("len", &canfd_frame::len)
|
||||||
|
.def_rw("flags", &canfd_frame::flags)
|
||||||
|
.def_prop_rw(
|
||||||
|
"data",
|
||||||
|
[](const canfd_frame& frame) {
|
||||||
|
return nb::bytes(reinterpret_cast<const char*>(frame.data), frame.len);
|
||||||
|
},
|
||||||
|
[](canfd_frame& frame, nb::bytes data) {
|
||||||
|
size_t len = std::min(data.size(), sizeof(frame.data));
|
||||||
|
frame.len = len;
|
||||||
|
std::memcpy(frame.data, data.data(), len);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// TOP-LEVEL COMPONENT CLASSES
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// DMDeviceCollection class (base class for ArmComponent and
|
||||||
|
// GripperComponent)
|
||||||
|
nb::class_<DMDeviceCollection>(m, "DMDeviceCollection")
|
||||||
|
.def(nb::init<CANSocket&>(), nb::arg("can_socket"))
|
||||||
|
.def("enable_all", &DMDeviceCollection::enable_all)
|
||||||
|
.def("disable_all", &DMDeviceCollection::disable_all)
|
||||||
|
.def("set_zero_all", &DMDeviceCollection::set_zero_all)
|
||||||
|
.def("refresh_all", &DMDeviceCollection::refresh_all)
|
||||||
|
.def("set_callback_mode_all", &DMDeviceCollection::set_callback_mode_all,
|
||||||
|
nb::arg("callback_mode"))
|
||||||
|
.def("query_param_all", &DMDeviceCollection::query_param_all, nb::arg("rid"))
|
||||||
|
.def("mit_control_one", &DMDeviceCollection::mit_control_one, nb::arg("index"),
|
||||||
|
nb::arg("mit_param"))
|
||||||
|
.def("mit_control_all", &DMDeviceCollection::mit_control_all, nb::arg("mit_params"))
|
||||||
|
.def("get_motors", &DMDeviceCollection::get_motors)
|
||||||
|
.def("get_device_collection", &DMDeviceCollection::get_device_collection,
|
||||||
|
nb::rv_policy::reference);
|
||||||
|
|
||||||
|
// ArmComponent class
|
||||||
|
nb::class_<ArmComponent, DMDeviceCollection>(m, "ArmComponent")
|
||||||
|
.def(nb::init<CANSocket&>(), nb::arg("can_socket"))
|
||||||
|
.def("init_motor_devices", &ArmComponent::init_motor_devices, nb::arg("motor_types"),
|
||||||
|
nb::arg("send_can_ids"), nb::arg("recv_can_ids"), nb::arg("use_fd"));
|
||||||
|
|
||||||
|
// GripperComponent class
|
||||||
|
nb::class_<GripperComponent, DMDeviceCollection>(m, "GripperComponent")
|
||||||
|
.def(nb::init<CANSocket&>(), nb::arg("can_socket"))
|
||||||
|
.def("init_motor_device", &GripperComponent::init_motor_device, nb::arg("motor_type"),
|
||||||
|
nb::arg("send_can_id"), nb::arg("recv_can_id"), nb::arg("use_fd"))
|
||||||
|
.def("open", &GripperComponent::open, nb::arg("kp") = 50.0, nb::arg("kd") = 1.0)
|
||||||
|
.def("close", &GripperComponent::close, nb::arg("kp") = 50.0, nb::arg("kd") = 1.0)
|
||||||
|
.def("get_motor", &GripperComponent::get_motor, nb::rv_policy::reference_internal);
|
||||||
|
|
||||||
|
// OpenArm class (main high-level interface)
|
||||||
|
nb::class_<OpenArm>(m, "OpenArm")
|
||||||
|
.def(nb::init<const std::string&, bool>(), nb::arg("can_interface"),
|
||||||
|
nb::arg("enable_fd") = false)
|
||||||
|
.def("init_arm_motors", &OpenArm::init_arm_motors, nb::arg("motor_types"),
|
||||||
|
nb::arg("send_can_ids"), nb::arg("recv_can_ids"))
|
||||||
|
.def("init_gripper_motor", &OpenArm::init_gripper_motor, nb::arg("motor_type"),
|
||||||
|
nb::arg("send_can_id"), nb::arg("recv_can_id"))
|
||||||
|
.def("get_arm", &OpenArm::get_arm, nb::rv_policy::reference)
|
||||||
|
.def("get_gripper", &OpenArm::get_gripper, nb::rv_policy::reference)
|
||||||
|
.def("get_master_can_device_collection", &OpenArm::get_master_can_device_collection,
|
||||||
|
nb::rv_policy::reference)
|
||||||
|
.def("enable_all", &OpenArm::enable_all)
|
||||||
|
.def("disable_all", &OpenArm::disable_all)
|
||||||
|
.def("set_zero_all", &OpenArm::set_zero_all)
|
||||||
|
.def("refresh_all", &OpenArm::refresh_all)
|
||||||
|
.def("recv_all", &OpenArm::recv_all, nb::arg("timeout_us") = 500)
|
||||||
|
.def("set_callback_mode_all", &OpenArm::set_callback_mode_all, nb::arg("callback_mode"))
|
||||||
|
.def("query_param_all", &OpenArm::query_param_all, nb::arg("rid"));
|
||||||
|
}
|
||||||
17
python/subprojects/.gitignore
vendored
Normal file
17
python/subprojects/.gitignore
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
/nanobind-*
|
||||||
|
/packagecache/
|
||||||
|
/robin-map-*
|
||||||
27
python/subprojects/nanobind.wrap
Normal file
27
python/subprojects/nanobind.wrap
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
[wrap-file]
|
||||||
|
directory = nanobind-2.7.0
|
||||||
|
source_url = https://github.com/wjakob/nanobind/archive/refs/tags/v2.7.0.tar.gz
|
||||||
|
source_filename = nanobind-2.7.0.tar.gz
|
||||||
|
source_hash = 6c8c6bf0435b9d8da9312801686affcf34b6dbba142db60feec8d8e220830499
|
||||||
|
patch_filename = nanobind_2.7.0-4_patch.zip
|
||||||
|
patch_url = https://wrapdb.mesonbuild.com/v2/nanobind_2.7.0-4/get_patch
|
||||||
|
patch_hash = 53e1f5ef483fb4a7cf45a0fdf1825a27d1ca45c41cc331ac3e80763560d8a2cb
|
||||||
|
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/nanobind_2.7.0-4/nanobind-2.7.0.tar.gz
|
||||||
|
wrapdb_version = 2.7.0-4
|
||||||
|
|
||||||
|
[provide]
|
||||||
|
nanobind = nanobind_dep
|
||||||
27
python/subprojects/robin-map.wrap
Normal file
27
python/subprojects/robin-map.wrap
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
[wrap-file]
|
||||||
|
directory = robin-map-1.4.0
|
||||||
|
source_url = https://github.com/Tessil/robin-map/archive/refs/tags/v1.4.0.tar.gz
|
||||||
|
source_filename = robin-map-1.4.0.tar.gz
|
||||||
|
source_hash = 7930dbf9634acfc02686d87f615c0f4f33135948130b8922331c16d90a03250c
|
||||||
|
patch_filename = robin-map_1.4.0-1_patch.zip
|
||||||
|
patch_url = https://wrapdb.mesonbuild.com/v2/robin-map_1.4.0-1/get_patch
|
||||||
|
patch_hash = feb14b6752b7d439fb2f3ee968e595a9a3de00ef8cb029488af2ceb4f504b95d
|
||||||
|
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/robin-map_1.4.0-1/robin-map-1.4.0.tar.gz
|
||||||
|
wrapdb_version = 1.4.0-1
|
||||||
|
|
||||||
|
[provide]
|
||||||
|
robin-map = robin_map_dep
|
||||||
261
setup/change_baudrate.py
Executable file
261
setup/change_baudrate.py
Executable file
@ -0,0 +1,261 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
DM Motor ID and Baudrate Writer script for writing baudrate and ID parameters to DM motors
|
||||||
|
Work with python-can library
|
||||||
|
Original author: @necobit (Co-Authored-By: Claude)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import can
|
||||||
|
import time
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class DMMotorWriter:
|
||||||
|
"""DM Motor parameter writer"""
|
||||||
|
|
||||||
|
# Supported baudrates and their codes
|
||||||
|
BAUDRATE_MAP = {
|
||||||
|
125000: 0, # 125K
|
||||||
|
200000: 1, # 200K
|
||||||
|
250000: 2, # 250K
|
||||||
|
500000: 3, # 500K
|
||||||
|
1000000: 4, # 1M
|
||||||
|
2000000: 5, # 2M
|
||||||
|
2500000: 6, # 2.5M
|
||||||
|
3200000: 7, # 3.2M
|
||||||
|
4000000: 8, # 4M
|
||||||
|
5000000: 9 # 5M
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, socketcan_port: str = "can0"):
|
||||||
|
self.socketcan_port = socketcan_port
|
||||||
|
self.can_bus: Optional[can.BusABC] = None
|
||||||
|
|
||||||
|
def connect(self) -> bool:
|
||||||
|
"""Connect to CAN bus"""
|
||||||
|
try:
|
||||||
|
print(f"Connecting to CAN interface: {self.socketcan_port}")
|
||||||
|
self.can_bus = can.interface.Bus(
|
||||||
|
channel=self.socketcan_port,
|
||||||
|
interface='socketcan'
|
||||||
|
)
|
||||||
|
print(f"✓ Connected to {self.socketcan_port}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Connection failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
"""Disconnect from CAN bus"""
|
||||||
|
if self.can_bus:
|
||||||
|
self.can_bus.shutdown()
|
||||||
|
self.can_bus = None
|
||||||
|
print("Disconnected from CAN bus")
|
||||||
|
|
||||||
|
def validate_baudrate(self, baudrate: int) -> bool:
|
||||||
|
"""Validate if baudrate is supported"""
|
||||||
|
return baudrate in self.BAUDRATE_MAP
|
||||||
|
|
||||||
|
def write_baudrate(self, motor_id: int, baudrate: int) -> bool:
|
||||||
|
"""Write baudrate to motor"""
|
||||||
|
if not self.can_bus:
|
||||||
|
print("✗ Not connected to CAN bus")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.validate_baudrate(baudrate):
|
||||||
|
print(f"✗ Unsupported baudrate: {baudrate}")
|
||||||
|
print(f"Supported baudrates: {list(self.BAUDRATE_MAP.keys())}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
baudrate_code = self.BAUDRATE_MAP[baudrate]
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"Writing baudrate {baudrate} (code: {baudrate_code}) to motor ID {motor_id}")
|
||||||
|
|
||||||
|
# Set Baudrate (can_br) - RID 35 (0x23)
|
||||||
|
baudrate_data = [
|
||||||
|
motor_id & 0xFF,
|
||||||
|
(motor_id >> 8) & 0xFF,
|
||||||
|
0x55,
|
||||||
|
0x23,
|
||||||
|
baudrate_code,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00
|
||||||
|
]
|
||||||
|
|
||||||
|
baudrate_msg = can.Message(
|
||||||
|
arbitration_id=0x7FF,
|
||||||
|
data=baudrate_data,
|
||||||
|
is_extended_id=False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.can_bus.send(baudrate_msg)
|
||||||
|
time.sleep(0.1) # Wait for processing
|
||||||
|
|
||||||
|
print(f"✓ Baudrate {baudrate} written to motor ID {motor_id}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Failed to write baudrate: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def save_to_flash(self, motor_id: int) -> bool:
|
||||||
|
"""Save parameters to flash memory"""
|
||||||
|
if not self.can_bus:
|
||||||
|
print("✗ Not connected to CAN bus")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f"Saving parameters to flash for motor ID {motor_id}")
|
||||||
|
print("⚠️ Motor has a hard limit of writing to flash of 10000 times")
|
||||||
|
print("⚠️ Motor will be disabled during save operation")
|
||||||
|
|
||||||
|
# Step 1: Disable motor first (required for save operation)
|
||||||
|
disable_data = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD]
|
||||||
|
disable_msg = can.Message(
|
||||||
|
arbitration_id=motor_id,
|
||||||
|
data=disable_data,
|
||||||
|
is_extended_id=False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.can_bus.send(disable_msg)
|
||||||
|
time.sleep(0.5) # Wait for disable to take effect
|
||||||
|
|
||||||
|
# Step 2: Send save command
|
||||||
|
save_data = [
|
||||||
|
motor_id & 0xFF,
|
||||||
|
(motor_id >> 8) & 0xFF,
|
||||||
|
0xAA,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00
|
||||||
|
]
|
||||||
|
|
||||||
|
save_msg = can.Message(
|
||||||
|
arbitration_id=0x7FF,
|
||||||
|
data=save_data,
|
||||||
|
is_extended_id=False
|
||||||
|
)
|
||||||
|
|
||||||
|
self.can_bus.send(save_msg)
|
||||||
|
time.sleep(0.5) # Wait for save operation (up to 30ms)
|
||||||
|
|
||||||
|
print(f"✓ Parameters saved to flash for motor ID {motor_id}")
|
||||||
|
print("⚠️ Motor is now in DISABLE mode")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Failed to save to flash: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="DM Motor ID and Baudrate Writer",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""
|
||||||
|
Examples:
|
||||||
|
# Write baudrate 1000000 to motor ID 2 on can0
|
||||||
|
python change_baudrate.py --baudrate 1000000 --canid 2 --socketcan can0
|
||||||
|
|
||||||
|
# Write baudrate and save to flash
|
||||||
|
python change_baudrate.py --baudrate 500000 --canid 1 --socketcan can0 --flash
|
||||||
|
|
||||||
|
# Write to motor on different CAN port
|
||||||
|
python change_baudrate.py --baudrate 2000000 --canid 3 --socketcan can1
|
||||||
|
|
||||||
|
Supported baudrates:
|
||||||
|
125000, 200000, 250000, 500000, 1000000, 2000000, 2500000, 3200000, 4000000, 5000000
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-b', '--baudrate',
|
||||||
|
type=int,
|
||||||
|
required=True,
|
||||||
|
help='Baudrate to write (125000, 200000, 250000, 500000, 1000000, 2000000, 2500000, 3200000, 4000000, 5000000)'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-c', '--canid',
|
||||||
|
type=int,
|
||||||
|
required=True,
|
||||||
|
help='Motor CAN ID (slave ID) to write to (0-255)'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-s', '--socketcan',
|
||||||
|
type=str,
|
||||||
|
default='can0',
|
||||||
|
help='SocketCAN port (default: can0)'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-f', '--flash',
|
||||||
|
action='store_true',
|
||||||
|
help='Save parameters to flash memory after writing'
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Validate inputs
|
||||||
|
if args.canid < 0 or args.canid > 255:
|
||||||
|
print("✗ Error: CAN ID must be between 0 and 255")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Create writer instance
|
||||||
|
writer = DMMotorWriter(args.socketcan)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Connect to CAN bus
|
||||||
|
if not writer.connect():
|
||||||
|
print("✗ Failed to connect to CAN bus")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Write baudrate
|
||||||
|
if not writer.write_baudrate(args.canid, args.baudrate):
|
||||||
|
print("✗ Failed to write baudrate")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Save to flash if requested
|
||||||
|
if args.flash:
|
||||||
|
if not writer.save_to_flash(args.canid):
|
||||||
|
print("✗ Failed to save to flash")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print("✓ Operation completed successfully")
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n⚠️ Operation cancelled by user")
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Unexpected error: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
finally:
|
||||||
|
writer.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
114
setup/configure_socketcan.sh
Executable file
114
setup/configure_socketcan.sh
Executable file
@ -0,0 +1,114 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# Simple CAN Interface Setup Script
|
||||||
|
# Usage: script/configure_socketcan.sh <interface> [options]
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
BITRATE=1000000
|
||||||
|
DBITRATE=5000000
|
||||||
|
FD_MODE=false
|
||||||
|
|
||||||
|
# Show usage
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 <can_interface> [options]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " -fd Enable CAN FD mode (default: CAN 2.0)"
|
||||||
|
echo " -b <bitrate> Set bitrate (default: 1000000)"
|
||||||
|
echo " -d <dbitrate> Set CAN FD data bitrate (default: 5000000)"
|
||||||
|
echo " -h Show help"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 can0 # CAN 2.0 at 1Mbps"
|
||||||
|
echo " $0 can0 -fd # CAN FD at 1Mbps/5Mbps"
|
||||||
|
echo " $0 can0 -b 500000 # CAN 2.0 at 500kbps"
|
||||||
|
echo " $0 can0 -fd -d 8000000 # CAN FD with 8Mbps data rate"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if interface provided
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Error: Specify CAN interface (e.g., can0)"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CAN_IF=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
# Parse options
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-fd)
|
||||||
|
FD_MODE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-b)
|
||||||
|
BITRATE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-d)
|
||||||
|
DBITRATE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Error: Unknown option '$1'"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if interface exists
|
||||||
|
if ! ip link show $CAN_IF &>/dev/null; then
|
||||||
|
echo "Error: CAN interface '$CAN_IF' not found"
|
||||||
|
echo "Available interfaces:"
|
||||||
|
ip link show | grep -E "can[0-9]" | cut -d: -f2 | tr -d ' ' || echo " No CAN interfaces found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Configure CAN interface
|
||||||
|
echo "Configuring $CAN_IF..."
|
||||||
|
|
||||||
|
if ! sudo ip link set $CAN_IF down; then
|
||||||
|
echo "Error: Failed to bring down $CAN_IF"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$FD_MODE" = true ]; then
|
||||||
|
if ! sudo ip link set $CAN_IF type can bitrate $BITRATE dbitrate $DBITRATE fd on; then
|
||||||
|
echo "Error: Failed to configure CAN FD mode"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "$CAN_IF is now set to CAN FD mode (${BITRATE} bps / ${DBITRATE} bps)"
|
||||||
|
else
|
||||||
|
if ! sudo ip link set $CAN_IF type can bitrate $BITRATE; then
|
||||||
|
echo "Error: Failed to configure CAN 2.0 mode"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "$CAN_IF is now set to CAN 2.0 mode (${BITRATE} bps)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! sudo ip link set $CAN_IF up; then
|
||||||
|
echo "Error: Failed to bring up $CAN_IF"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✓ $CAN_IF is active"
|
||||||
186
setup/motor_check.cpp
Normal file
186
setup/motor_check.cpp
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
#include <openarm/can/socket/openarm.hpp>
|
||||||
|
#include <openarm/damiao_motor/dm_motor_constants.hpp>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void print_usage(const char* program_name) {
|
||||||
|
std::cout << "Usage: " << program_name << " <send_can_id> <recv_can_id> [can_interface] [-fd]"
|
||||||
|
<< std::endl;
|
||||||
|
std::cout << " send_can_id: The send CAN ID of the motor (e.g., 1, 2, 7)" << std::endl;
|
||||||
|
std::cout << " recv_can_id: The receive CAN ID of the motor (e.g., 17, 18, 23)" << std::endl;
|
||||||
|
std::cout << " can_interface: CAN interface name (default: can0)" << std::endl;
|
||||||
|
std::cout << " -fd: Enable CAN-FD (default: disabled)" << std::endl;
|
||||||
|
std::cout << std::endl;
|
||||||
|
std::cout << "Example: " << program_name << " 1 17" << std::endl;
|
||||||
|
std::cout << "Example: " << program_name << " 1 17 can1" << std::endl;
|
||||||
|
std::cout << "Example: " << program_name << " 1 17 can1 -fd" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_motor_status(const openarm::damiao_motor::Motor& motor) {
|
||||||
|
std::cout << "Motor ID: " << motor.get_send_can_id() << std::endl;
|
||||||
|
std::cout << " Position: " << motor.get_position() << " rad" << std::endl;
|
||||||
|
std::cout << " Velocity: " << motor.get_velocity() << " rad/s" << std::endl;
|
||||||
|
std::cout << " Torque: " << motor.get_torque() << " Nm" << std::endl;
|
||||||
|
std::cout << " Temperature (MOS): " << motor.get_state_tmos() << " °C" << std::endl;
|
||||||
|
std::cout << " Temperature (Rotor): " << motor.get_state_trotor() << " °C" << std::endl;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
if (argc < 3 || argc > 5) {
|
||||||
|
print_usage(argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse send and receive CAN IDs from command line
|
||||||
|
uint32_t send_can_id, recv_can_id;
|
||||||
|
try {
|
||||||
|
send_can_id = std::stoul(argv[1]);
|
||||||
|
recv_can_id = std::stoul(argv[2]);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Error: Invalid CAN ID format" << std::endl;
|
||||||
|
print_usage(argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse optional CAN interface and FD flag
|
||||||
|
std::string can_interface = "can0"; // default
|
||||||
|
bool use_fd = false; // default: disabled
|
||||||
|
|
||||||
|
if (argc >= 4) {
|
||||||
|
std::string arg4 = argv[3];
|
||||||
|
if (arg4 == "-fd") {
|
||||||
|
use_fd = true;
|
||||||
|
} else {
|
||||||
|
can_interface = arg4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc >= 5) {
|
||||||
|
std::string arg5 = argv[4];
|
||||||
|
if (arg5 == "-fd") {
|
||||||
|
use_fd = true;
|
||||||
|
} else {
|
||||||
|
std::cerr << "Error: Unknown argument '" << arg5 << "'. Use -fd to enable CAN-FD"
|
||||||
|
<< std::endl;
|
||||||
|
print_usage(argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::cout << "=== OpenArm Motor Control Script ===" << std::endl;
|
||||||
|
std::cout << "Send CAN ID: " << send_can_id << std::endl;
|
||||||
|
std::cout << "Receive CAN ID: " << recv_can_id << std::endl;
|
||||||
|
std::cout << "CAN Interface: " << can_interface << std::endl;
|
||||||
|
std::cout << "CAN-FD Enabled: " << (use_fd ? "Yes" : "No") << std::endl;
|
||||||
|
std::cout << std::endl;
|
||||||
|
|
||||||
|
// Initialize OpenArm with CAN interface
|
||||||
|
std::cout << "Initializing OpenArm CAN..." << std::endl;
|
||||||
|
openarm::can::socket::OpenArm openarm(can_interface,
|
||||||
|
use_fd); // Use specified interface and FD setting
|
||||||
|
|
||||||
|
// Initialize single motor
|
||||||
|
std::cout << "Initializing motor..." << std::endl;
|
||||||
|
openarm.init_arm_motors({openarm::damiao_motor::MotorType::DM4310}, {send_can_id},
|
||||||
|
{recv_can_id});
|
||||||
|
|
||||||
|
// Set callback mode to param for initial parameter reading
|
||||||
|
openarm.set_callback_mode_all(openarm::damiao_motor::CallbackMode::PARAM);
|
||||||
|
|
||||||
|
// Query motor parameters (Master ID and Baudrate)
|
||||||
|
std::cout << "Reading motor parameters..." << std::endl;
|
||||||
|
openarm.query_param_all(static_cast<int>(openarm::damiao_motor::RID::MST_ID));
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
openarm.recv_all();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
|
openarm.query_param_all(static_cast<int>(openarm::damiao_motor::RID::can_br));
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
openarm.recv_all();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
|
// Get motor and verify parameters
|
||||||
|
const auto& motors = openarm.get_arm().get_motors();
|
||||||
|
if (!motors.empty()) {
|
||||||
|
const auto& motor = motors[0];
|
||||||
|
double queried_mst_id =
|
||||||
|
motor.get_param(static_cast<int>(openarm::damiao_motor::RID::MST_ID));
|
||||||
|
double queried_baudrate =
|
||||||
|
motor.get_param(static_cast<int>(openarm::damiao_motor::RID::can_br));
|
||||||
|
|
||||||
|
std::cout << "\n=== Motor Parameters ===" << std::endl;
|
||||||
|
std::cout << "Send CAN ID: " << motor.get_send_can_id() << std::endl;
|
||||||
|
std::cout << "Queried Master ID: " << queried_mst_id << std::endl;
|
||||||
|
std::cout << "Queried Baudrate (1-9): " << queried_baudrate << std::endl;
|
||||||
|
|
||||||
|
// Verify recv_can_id matches queried master ID
|
||||||
|
if (static_cast<uint32_t>(queried_mst_id) != recv_can_id) {
|
||||||
|
std::cerr << "Error: Queried Master ID (" << queried_mst_id
|
||||||
|
<< ") does not match provided recv_can_id (" << recv_can_id << ")"
|
||||||
|
<< std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::cout << "✓ Master ID verification passed" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch to state callback mode for motor status updates
|
||||||
|
openarm.set_callback_mode_all(openarm::damiao_motor::CallbackMode::STATE);
|
||||||
|
|
||||||
|
// Enable the motor
|
||||||
|
std::cout << "\n=== Enabling Motor ===" << std::endl;
|
||||||
|
openarm.enable_all();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
openarm.recv_all();
|
||||||
|
|
||||||
|
// Refresh 10 times at 10Hz (100ms intervals)
|
||||||
|
std::cout << "\n=== Refreshing Motor Status (10Hz for 1 second) ===" << std::endl;
|
||||||
|
for (int i = 1; i <= 10; i++) {
|
||||||
|
openarm.refresh_all();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
openarm.recv_all();
|
||||||
|
|
||||||
|
for (const auto& motor : openarm.get_arm().get_motors()) {
|
||||||
|
std::cout << "\n--- Refresh " << i << "/10 ---" << std::endl;
|
||||||
|
print_motor_status(motor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable the motor
|
||||||
|
std::cout << "\n=== Disabling Motor ===" << std::endl;
|
||||||
|
openarm.disable_all();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
openarm.recv_all();
|
||||||
|
|
||||||
|
// Print final status
|
||||||
|
if (!motors.empty()) {
|
||||||
|
std::cout << "\n=== Final Motor Status ===" << std::endl;
|
||||||
|
print_motor_status(motors[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\n=== Script Completed Successfully ===" << std::endl;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Error: " << e.what() << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
160
setup/set_zero.sh
Executable file
160
setup/set_zero.sh
Executable file
@ -0,0 +1,160 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Copyright 2025 Enactic, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
# CAN Interface Script
|
||||||
|
# Usage: scripts/set_zero.sh <CAN_IF> [CAN_ID] [-all]
|
||||||
|
|
||||||
|
# Function to display usage
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 <CAN_IF> [CAN_ID] [-all]"
|
||||||
|
echo " CAN_IF: CAN interface name (e.g., can0)"
|
||||||
|
echo " CAN_ID: CAN ID in hex format (e.g., 00x) - not needed with -all"
|
||||||
|
echo " -all: Send to all IDs from 001 to 008"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 can0 001"
|
||||||
|
echo " $0 can0 -all"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to check if CAN interface is up and get baudrate
|
||||||
|
check_can_interface() {
|
||||||
|
local interface=$1
|
||||||
|
|
||||||
|
# Check if interface exists and is up
|
||||||
|
if ! ip link show "$interface" &>/dev/null; then
|
||||||
|
echo "Error: CAN interface $interface does not exist"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if interface is up
|
||||||
|
local state=$(ip link show "$interface" | grep -o "state [A-Z]*" | cut -d' ' -f2)
|
||||||
|
if [ "$state" != "UP" ]; then
|
||||||
|
echo "Error: CAN interface $interface is not UP (current state: $state)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "CAN interface $interface is UP"
|
||||||
|
|
||||||
|
# Try to get baudrate information
|
||||||
|
if command -v ethtool &>/dev/null; then
|
||||||
|
local baudrate=$(ethtool "$interface" 2>/dev/null | grep -i speed | cut -d: -f2 | tr -d ' ')
|
||||||
|
if [ -n "$baudrate" ]; then
|
||||||
|
echo "Baudrate: $baudrate"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Alternative method using ip command
|
||||||
|
local bitrate=$(ip -details link show "$interface" 2>/dev/null | grep -o "bitrate [0-9]*" | cut -d' ' -f2)
|
||||||
|
if [ -n "$bitrate" ]; then
|
||||||
|
echo "Bitrate: ${bitrate} bps"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to send CAN messages for a single ID
|
||||||
|
send_can_messages() {
|
||||||
|
local CAN_ID=$1
|
||||||
|
local CAN_IF=$2
|
||||||
|
|
||||||
|
echo "Sending CAN messages for ID: $CAN_ID on interface: $CAN_IF"
|
||||||
|
|
||||||
|
# Send first disablemessage
|
||||||
|
echo "Sending: cansend $CAN_IF ${CAN_ID}#FFFFFFFFFFFFFFFD"
|
||||||
|
cansend "$CAN_IF" "${CAN_ID}#FFFFFFFFFFFFFFFD"
|
||||||
|
|
||||||
|
sleep 0.1
|
||||||
|
|
||||||
|
# Send second set zero message
|
||||||
|
echo "Sending: cansend $CAN_IF ${CAN_ID}#FFFFFFFFFFFFFFFE"
|
||||||
|
cansend "$CAN_IF" "${CAN_ID}#FFFFFFFFFFFFFFFE"
|
||||||
|
|
||||||
|
sleep 0.1
|
||||||
|
|
||||||
|
# Send third disable message
|
||||||
|
echo "Sending: cansend $CAN_IF ${CAN_ID}#FFFFFFFFFFFFFFFD"
|
||||||
|
cansend "$CAN_IF" "${CAN_ID}#FFFFFFFFFFFFFFFD"
|
||||||
|
|
||||||
|
sleep 0.1
|
||||||
|
|
||||||
|
echo "Messages sent for ID: $CAN_ID"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main script logic
|
||||||
|
main() {
|
||||||
|
# Check for minimum arguments
|
||||||
|
if [ $# -lt 1 ]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
local CAN_IF=$1
|
||||||
|
local CAN_ID=""
|
||||||
|
local all_flag=false
|
||||||
|
|
||||||
|
# Check for -all flag
|
||||||
|
if [ "$2" = "-all" ]; then
|
||||||
|
all_flag=true
|
||||||
|
else
|
||||||
|
CAN_ID=$2
|
||||||
|
if [ "$3" = "-all" ]; then
|
||||||
|
all_flag=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate CAN_IF
|
||||||
|
if [ -z "$CAN_IF" ]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate CAN_ID only if -all flag is not set
|
||||||
|
if [ "$all_flag" = false ] && [ -z "$CAN_ID" ]; then
|
||||||
|
echo "Error: CAN_ID is required when -all flag is not used"
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if cansend command is available
|
||||||
|
if ! command -v cansend &>/dev/null; then
|
||||||
|
echo "Error: cansend command not found. Please install can-utils package."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check CAN interface status
|
||||||
|
if ! check_can_interface "$CAN_IF"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Execute based on flags
|
||||||
|
if [ "$all_flag" = true ]; then
|
||||||
|
echo "Sending set zero messages to all motor with CAN IDs from 001 to 008"
|
||||||
|
echo "=========================================="
|
||||||
|
for i in {1..8}; do
|
||||||
|
local padded_id=$(printf "%03d" $i)
|
||||||
|
send_can_messages "$padded_id" "$CAN_IF"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
send_can_messages "$CAN_ID" "$CAN_IF"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Set zero completed."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function with all arguments
|
||||||
|
main "$@"
|
||||||
43
src/openarm/can/socket/arm_component.cpp
Normal file
43
src/openarm/can/socket/arm_component.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include <linux/can.h>
|
||||||
|
#include <linux/can/raw.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <openarm/can/socket/arm_component.hpp>
|
||||||
|
|
||||||
|
namespace openarm::can::socket {
|
||||||
|
|
||||||
|
ArmComponent::ArmComponent(canbus::CANSocket& can_socket)
|
||||||
|
: damiao_motor::DMDeviceCollection(can_socket) {}
|
||||||
|
|
||||||
|
void ArmComponent::init_motor_devices(std::vector<damiao_motor::MotorType> motor_types,
|
||||||
|
std::vector<canid_t> send_can_ids,
|
||||||
|
std::vector<canid_t> recv_can_ids, bool use_fd) {
|
||||||
|
// Reserve space to prevent vector reallocation that would invalidate motor
|
||||||
|
// references
|
||||||
|
motors_.reserve(motor_types.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < motor_types.size(); i++) {
|
||||||
|
// First, create and store the motor in the vector
|
||||||
|
motors_.emplace_back(motor_types[i], send_can_ids[i], recv_can_ids[i]);
|
||||||
|
// Then create the device with a reference to the stored motor
|
||||||
|
auto motor_device =
|
||||||
|
std::make_shared<damiao_motor::DMCANDevice>(motors_.back(), CAN_SFF_MASK, use_fd);
|
||||||
|
get_device_collection().add_device(motor_device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace openarm::can::socket
|
||||||
48
src/openarm/can/socket/gripper_component.cpp
Normal file
48
src/openarm/can/socket/gripper_component.cpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include <linux/can.h>
|
||||||
|
#include <linux/can/raw.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <openarm/can/socket/gripper_component.hpp>
|
||||||
|
|
||||||
|
namespace openarm::can::socket {
|
||||||
|
|
||||||
|
GripperComponent::GripperComponent(canbus::CANSocket& can_socket)
|
||||||
|
: DMDeviceCollection(can_socket) {}
|
||||||
|
|
||||||
|
void GripperComponent::init_motor_device(damiao_motor::MotorType motor_type, uint32_t send_can_id,
|
||||||
|
uint32_t recv_can_id, bool use_fd) {
|
||||||
|
// Create the motor
|
||||||
|
motor_ = std::make_unique<damiao_motor::Motor>(motor_type, send_can_id, recv_can_id);
|
||||||
|
// Create the device with a reference to the motor
|
||||||
|
motor_device_ = std::make_shared<damiao_motor::DMCANDevice>(*motor_, CAN_SFF_MASK, use_fd);
|
||||||
|
get_device_collection().add_device(motor_device_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GripperComponent::open(double kp, double kd) { set_position(gripper_open_position_, kp, kd); }
|
||||||
|
|
||||||
|
void GripperComponent::close(double kp, double kd) {
|
||||||
|
set_position(gripper_closed_position_, kp, kd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GripperComponent::set_position(double gripper_position, double kp, double kd) {
|
||||||
|
if (!motor_device_) return;
|
||||||
|
|
||||||
|
// MIT control to desired position (zero velocity and torque)
|
||||||
|
mit_control_one(
|
||||||
|
0, damiao_motor::MITParam{kp, kd, gripper_to_motor_position(gripper_position), 0.0, 0.0});
|
||||||
|
}
|
||||||
|
} // namespace openarm::can::socket
|
||||||
110
src/openarm/can/socket/openarm.cpp
Normal file
110
src/openarm/can/socket/openarm.cpp
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include <linux/can.h>
|
||||||
|
#include <linux/can/raw.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <openarm/can/socket/openarm.hpp>
|
||||||
|
|
||||||
|
namespace openarm::can::socket {
|
||||||
|
|
||||||
|
OpenArm::OpenArm(const std::string& can_interface, bool enable_fd)
|
||||||
|
: can_interface_(can_interface), enable_fd_(enable_fd) {
|
||||||
|
can_socket_ = std::make_unique<canbus::CANSocket>(can_interface_, enable_fd_);
|
||||||
|
master_can_device_collection_ = std::make_unique<canbus::CANDeviceCollection>(*can_socket_);
|
||||||
|
arm_ = std::make_unique<ArmComponent>(*can_socket_);
|
||||||
|
gripper_ = std::make_unique<GripperComponent>(*can_socket_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenArm::init_arm_motors(const std::vector<damiao_motor::MotorType>& motor_types,
|
||||||
|
const std::vector<uint32_t>& send_can_ids,
|
||||||
|
const std::vector<uint32_t>& recv_can_ids) {
|
||||||
|
arm_->init_motor_devices(motor_types, send_can_ids, recv_can_ids, enable_fd_);
|
||||||
|
register_dm_device_collection(*arm_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenArm::init_gripper_motor(damiao_motor::MotorType motor_type, uint32_t send_can_id,
|
||||||
|
uint32_t recv_can_id) {
|
||||||
|
gripper_->init_motor_device(motor_type, send_can_id, recv_can_id, enable_fd_);
|
||||||
|
register_dm_device_collection(*gripper_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenArm::register_dm_device_collection(damiao_motor::DMDeviceCollection& device_collection) {
|
||||||
|
for (const auto& [id, device] : device_collection.get_device_collection().get_devices()) {
|
||||||
|
master_can_device_collection_->add_device(device);
|
||||||
|
}
|
||||||
|
sub_dm_device_collections_.push_back(&device_collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenArm::enable_all() {
|
||||||
|
for (damiao_motor::DMDeviceCollection* device_collection : sub_dm_device_collections_) {
|
||||||
|
device_collection->enable_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenArm::set_zero_all() {
|
||||||
|
for (damiao_motor::DMDeviceCollection* device_collection : sub_dm_device_collections_) {
|
||||||
|
device_collection->set_zero_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenArm::refresh_all() {
|
||||||
|
for (damiao_motor::DMDeviceCollection* device_collection : sub_dm_device_collections_) {
|
||||||
|
device_collection->refresh_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenArm::disable_all() {
|
||||||
|
for (damiao_motor::DMDeviceCollection* device_collection : sub_dm_device_collections_) {
|
||||||
|
device_collection->disable_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenArm::recv_all(int timeout_us) {
|
||||||
|
// The timeout for select() is set to timeout_us (default: 500 us).
|
||||||
|
// Tuning this value may improve the performance but should be done with caution.
|
||||||
|
|
||||||
|
// CAN FD
|
||||||
|
if (enable_fd_) {
|
||||||
|
canfd_frame response_frame;
|
||||||
|
while (can_socket_->is_data_available(timeout_us) &&
|
||||||
|
can_socket_->read_canfd_frame(response_frame)) {
|
||||||
|
master_can_device_collection_->dispatch_frame_callback(response_frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// CAN 2.0
|
||||||
|
else {
|
||||||
|
can_frame response_frame;
|
||||||
|
while (can_socket_->is_data_available(timeout_us) &&
|
||||||
|
can_socket_->read_can_frame(response_frame)) {
|
||||||
|
master_can_device_collection_->dispatch_frame_callback(response_frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenArm::query_param_all(int RID) {
|
||||||
|
for (damiao_motor::DMDeviceCollection* device_collection : sub_dm_device_collections_) {
|
||||||
|
device_collection->query_param_all(RID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenArm::set_callback_mode_all(damiao_motor::CallbackMode callback_mode) {
|
||||||
|
for (damiao_motor::DMDeviceCollection* device_collection : sub_dm_device_collections_) {
|
||||||
|
device_collection->set_callback_mode_all(callback_mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace openarm::can::socket
|
||||||
62
src/openarm/canbus/can_device_collection.cpp
Normal file
62
src/openarm/canbus/can_device_collection.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <openarm/canbus/can_device_collection.hpp>
|
||||||
|
#include <openarm/canbus/can_socket.hpp>
|
||||||
|
|
||||||
|
namespace openarm::canbus {
|
||||||
|
|
||||||
|
CANDeviceCollection::CANDeviceCollection(CANSocket& can_socket) : can_socket_(can_socket) {}
|
||||||
|
|
||||||
|
CANDeviceCollection::~CANDeviceCollection() {}
|
||||||
|
|
||||||
|
void CANDeviceCollection::add_device(const std::shared_ptr<CANDevice>& device) {
|
||||||
|
if (!device) return;
|
||||||
|
|
||||||
|
// Add device to our collection
|
||||||
|
canid_t device_id = device->get_recv_can_id();
|
||||||
|
devices_[device_id] = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CANDeviceCollection::remove_device(const std::shared_ptr<CANDevice>& device) {
|
||||||
|
if (!device) return;
|
||||||
|
|
||||||
|
canid_t device_id = device->get_recv_can_id();
|
||||||
|
auto it = devices_.find(device_id);
|
||||||
|
if (it != devices_.end()) {
|
||||||
|
// Remove from our collection
|
||||||
|
devices_.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CANDeviceCollection::dispatch_frame_callback(can_frame& frame) {
|
||||||
|
auto it = devices_.find(frame.can_id);
|
||||||
|
if (it != devices_.end()) {
|
||||||
|
it->second->callback(frame);
|
||||||
|
}
|
||||||
|
// Note: Silently ignore frames for unknown devices (this is normal in CAN
|
||||||
|
// networks)
|
||||||
|
}
|
||||||
|
|
||||||
|
void CANDeviceCollection::dispatch_frame_callback(canfd_frame& frame) {
|
||||||
|
auto it = devices_.find(frame.can_id);
|
||||||
|
if (it != devices_.end()) {
|
||||||
|
it->second->callback(frame);
|
||||||
|
}
|
||||||
|
// Note: Silently ignore frames for unknown devices (this is normal in CAN
|
||||||
|
// networks)
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace openarm::canbus
|
||||||
140
src/openarm/canbus/can_socket.cpp
Normal file
140
src/openarm/canbus/can_socket.cpp
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <openarm/canbus/can_socket.hpp>
|
||||||
|
|
||||||
|
namespace openarm::canbus {
|
||||||
|
|
||||||
|
CANSocket::CANSocket(const std::string& interface, bool enable_fd)
|
||||||
|
: socket_fd_(-1), interface_(interface), fd_enabled_(enable_fd), initialized_(false) {
|
||||||
|
if (!initialize_socket(interface)) {
|
||||||
|
throw CANSocketException("Failed to initialize socket for interface: " + interface);
|
||||||
|
}
|
||||||
|
initialized_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CANSocket::~CANSocket() { cleanup(); }
|
||||||
|
|
||||||
|
bool CANSocket::initialize_socket(const std::string& interface) {
|
||||||
|
// Create socket
|
||||||
|
socket_fd_ = socket(PF_CAN, SOCK_RAW, CAN_RAW);
|
||||||
|
if (socket_fd_ < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ifreq ifr;
|
||||||
|
struct sockaddr_can addr;
|
||||||
|
|
||||||
|
strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ - 1);
|
||||||
|
ifr.ifr_name[IFNAMSIZ - 1] = '\0';
|
||||||
|
|
||||||
|
if (ioctl(socket_fd_, SIOCGIFINDEX, &ifr) < 0) {
|
||||||
|
close(socket_fd_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.can_family = AF_CAN;
|
||||||
|
addr.can_ifindex = ifr.ifr_ifindex;
|
||||||
|
|
||||||
|
if (fd_enabled_) {
|
||||||
|
int enable_canfd = 1;
|
||||||
|
if (setsockopt(socket_fd_, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable_canfd,
|
||||||
|
sizeof(enable_canfd)) < 0) {
|
||||||
|
close(socket_fd_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bind(socket_fd_, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) < 0) {
|
||||||
|
close(socket_fd_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct timeval timeout;
|
||||||
|
timeout.tv_sec = 0;
|
||||||
|
timeout.tv_usec = 100;
|
||||||
|
if (setsockopt(socket_fd_, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
|
||||||
|
close(socket_fd_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CANSocket::cleanup() {
|
||||||
|
if (socket_fd_ > 0) {
|
||||||
|
close(socket_fd_);
|
||||||
|
socket_fd_ = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t CANSocket::read_raw_frame(void* buffer, size_t buffer_size) {
|
||||||
|
if (!initialized_) return -1;
|
||||||
|
return read(socket_fd_, buffer, buffer_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t CANSocket::write_raw_frame(const void* buffer, size_t frame_size) {
|
||||||
|
if (!initialized_) return -1;
|
||||||
|
return write(socket_fd_, buffer, frame_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CANSocket::write_can_frame(const can_frame& frame) {
|
||||||
|
return write(socket_fd_, &frame, sizeof(frame)) == sizeof(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CANSocket::write_canfd_frame(const canfd_frame& frame) {
|
||||||
|
return write(socket_fd_, &frame, sizeof(frame)) == sizeof(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CANSocket::read_can_frame(can_frame& frame) {
|
||||||
|
if (!initialized_) return false;
|
||||||
|
ssize_t bytes_read = read(socket_fd_, &frame, sizeof(frame));
|
||||||
|
return bytes_read == sizeof(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CANSocket::read_canfd_frame(canfd_frame& frame) {
|
||||||
|
if (!initialized_) return false;
|
||||||
|
ssize_t bytes_read = read(socket_fd_, &frame, sizeof(frame));
|
||||||
|
return bytes_read == sizeof(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CANSocket::is_data_available(int timeout_us) {
|
||||||
|
if (!initialized_) return false;
|
||||||
|
|
||||||
|
fd_set read_fds;
|
||||||
|
struct timeval timeout;
|
||||||
|
|
||||||
|
FD_ZERO(&read_fds);
|
||||||
|
FD_SET(socket_fd_, &read_fds);
|
||||||
|
|
||||||
|
timeout.tv_sec = timeout_us / 1000000;
|
||||||
|
timeout.tv_usec = (timeout_us % 1000000);
|
||||||
|
|
||||||
|
int result = select(socket_fd_ + 1, &read_fds, nullptr, nullptr, &timeout);
|
||||||
|
|
||||||
|
return (result > 0 && FD_ISSET(socket_fd_, &read_fds));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace openarm::canbus
|
||||||
70
src/openarm/damiao_motor/dm_motor.cpp
Normal file
70
src/openarm/damiao_motor/dm_motor.cpp
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include <openarm/damiao_motor/dm_motor.hpp>
|
||||||
|
#include <openarm/damiao_motor/dm_motor_constants.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace openarm::damiao_motor {
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
Motor::Motor(MotorType motor_type, uint32_t send_can_id, uint32_t recv_can_id)
|
||||||
|
: send_can_id_(send_can_id),
|
||||||
|
recv_can_id_(recv_can_id),
|
||||||
|
motor_type_(motor_type),
|
||||||
|
enabled_(false),
|
||||||
|
state_q_(0.0),
|
||||||
|
state_dq_(0.0),
|
||||||
|
state_tau_(0.0),
|
||||||
|
state_tmos_(0),
|
||||||
|
state_trotor_(0) {}
|
||||||
|
|
||||||
|
// Enable methods
|
||||||
|
void Motor::set_enabled(bool enable) { this->enabled_ = enable; }
|
||||||
|
|
||||||
|
// Parameter methods
|
||||||
|
// TODO: storing temp params in motor object might not be a good idea
|
||||||
|
// also -1 is not a good default value, consider using a different value
|
||||||
|
double Motor::get_param(int RID) const {
|
||||||
|
auto it = temp_param_dict_.find(RID);
|
||||||
|
return (it != temp_param_dict_.end()) ? it->second : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Motor::set_temp_param(int RID, int val) { temp_param_dict_[RID] = val; }
|
||||||
|
|
||||||
|
// State update methods
|
||||||
|
void Motor::update_state(double q, double dq, double tau, int tmos, int trotor) {
|
||||||
|
state_q_ = q;
|
||||||
|
state_dq_ = dq;
|
||||||
|
state_tau_ = tau;
|
||||||
|
state_tmos_ = tmos;
|
||||||
|
state_trotor_ = trotor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Motor::set_state_tmos(int tmos) { state_tmos_ = tmos; }
|
||||||
|
|
||||||
|
void Motor::set_state_trotor(int trotor) { state_trotor_ = trotor; }
|
||||||
|
|
||||||
|
// Static methods
|
||||||
|
LimitParam Motor::get_limit_param(MotorType motor_type) {
|
||||||
|
size_t index = static_cast<size_t>(motor_type);
|
||||||
|
if (index >= MOTOR_LIMIT_PARAMS.size()) {
|
||||||
|
throw std::invalid_argument("Invalid motor type: " +
|
||||||
|
std::to_string(static_cast<int>(motor_type)));
|
||||||
|
}
|
||||||
|
return MOTOR_LIMIT_PARAMS[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace openarm::damiao_motor
|
||||||
177
src/openarm/damiao_motor/dm_motor_control.cpp
Normal file
177
src/openarm/damiao_motor/dm_motor_control.cpp
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <openarm/damiao_motor/dm_motor.hpp>
|
||||||
|
#include <openarm/damiao_motor/dm_motor_constants.hpp>
|
||||||
|
#include <openarm/damiao_motor/dm_motor_control.hpp>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace openarm::damiao_motor {
|
||||||
|
|
||||||
|
// Command creation methods (return data array, can_id handled externally)
|
||||||
|
CANPacket CanPacketEncoder::create_enable_command(const Motor& motor) {
|
||||||
|
return {motor.get_send_can_id(), pack_command_data(0xFC)};
|
||||||
|
}
|
||||||
|
|
||||||
|
CANPacket CanPacketEncoder::create_disable_command(const Motor& motor) {
|
||||||
|
return {motor.get_send_can_id(), pack_command_data(0xFD)};
|
||||||
|
}
|
||||||
|
|
||||||
|
CANPacket CanPacketEncoder::create_set_zero_command(const Motor& motor) {
|
||||||
|
return {motor.get_send_can_id(), pack_command_data(0xFE)};
|
||||||
|
}
|
||||||
|
|
||||||
|
CANPacket CanPacketEncoder::create_mit_control_command(const Motor& motor,
|
||||||
|
const MITParam& mit_param) {
|
||||||
|
return {motor.get_send_can_id(), pack_mit_control_data(motor.get_motor_type(), mit_param)};
|
||||||
|
}
|
||||||
|
|
||||||
|
CANPacket CanPacketEncoder::create_query_param_command(const Motor& motor, int RID) {
|
||||||
|
return {0x7FF, pack_query_param_data(motor.get_send_can_id(), RID)};
|
||||||
|
}
|
||||||
|
|
||||||
|
CANPacket CanPacketEncoder::create_refresh_command(const Motor& motor) {
|
||||||
|
uint8_t send_can_id = motor.get_send_can_id();
|
||||||
|
std::vector<uint8_t> data = {static_cast<uint8_t>(send_can_id & 0xFF),
|
||||||
|
static_cast<uint8_t>((send_can_id >> 8) & 0xFF),
|
||||||
|
0xCC,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00};
|
||||||
|
return {0x7FF, data};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data interpretation methods (use recv_can_id for received data)
|
||||||
|
StateResult CanPacketDecoder::parse_motor_state_data(const Motor& motor,
|
||||||
|
const std::vector<uint8_t>& data) {
|
||||||
|
if (data.size() < 8) {
|
||||||
|
std::cerr << "Warning: Skipping motor state data less than 8 bytes" << std::endl;
|
||||||
|
return {0, 0, 0, 0, 0, false};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse state data
|
||||||
|
uint16_t q_uint = (static_cast<uint16_t>(data[1]) << 8) | data[2];
|
||||||
|
uint16_t dq_uint =
|
||||||
|
(static_cast<uint16_t>(data[3]) << 4) | (static_cast<uint16_t>(data[4]) >> 4);
|
||||||
|
uint16_t tau_uint = (static_cast<uint16_t>(data[4] & 0xf) << 8) | data[5];
|
||||||
|
int t_mos = static_cast<int>(data[6]);
|
||||||
|
int t_rotor = static_cast<int>(data[7]);
|
||||||
|
|
||||||
|
// Convert to physical values
|
||||||
|
LimitParam limits = MOTOR_LIMIT_PARAMS[static_cast<int>(motor.get_motor_type())];
|
||||||
|
double recv_q = CanPacketDecoder::uint_to_double(q_uint, -limits.pMax, limits.pMax, 16);
|
||||||
|
double recv_dq = CanPacketDecoder::uint_to_double(dq_uint, -limits.vMax, limits.vMax, 12);
|
||||||
|
double recv_tau = CanPacketDecoder::uint_to_double(tau_uint, -limits.tMax, limits.tMax, 12);
|
||||||
|
|
||||||
|
return {recv_q, recv_dq, recv_tau, t_mos, t_rotor, true};
|
||||||
|
}
|
||||||
|
|
||||||
|
ParamResult CanPacketDecoder::parse_motor_param_data(const std::vector<uint8_t>& data) {
|
||||||
|
if (data.size() < 8) return {0, NAN, false};
|
||||||
|
|
||||||
|
if ((data[2] == 0x33 || data[2] == 0x55)) {
|
||||||
|
uint8_t RID = data[3];
|
||||||
|
double num;
|
||||||
|
if (CanPacketDecoder::is_in_ranges(RID)) {
|
||||||
|
num = CanPacketDecoder::uint8s_to_uint32(data[4], data[5], data[6], data[7]);
|
||||||
|
} else {
|
||||||
|
std::array<uint8_t, 4> float_bytes = {data[4], data[5], data[6], data[7]};
|
||||||
|
num = CanPacketDecoder::uint8s_to_float(float_bytes);
|
||||||
|
}
|
||||||
|
return {RID, num, true};
|
||||||
|
} else {
|
||||||
|
std::cerr << "WARNING: INVALID PARAM DATA" << std::endl;
|
||||||
|
return {0, NAN, false};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data packing utility methods
|
||||||
|
std::vector<uint8_t> CanPacketEncoder::pack_mit_control_data(MotorType motor_type,
|
||||||
|
const MITParam& mit_param) {
|
||||||
|
uint16_t kp_uint = double_to_uint(mit_param.kp, 0, 500, 12);
|
||||||
|
uint16_t kd_uint = double_to_uint(mit_param.kd, 0, 5, 12);
|
||||||
|
|
||||||
|
// Get motor limits based on type
|
||||||
|
LimitParam limits = MOTOR_LIMIT_PARAMS[static_cast<int>(motor_type)];
|
||||||
|
uint16_t q_uint = double_to_uint(mit_param.q, -(double)limits.pMax, (double)limits.pMax, 16);
|
||||||
|
uint16_t dq_uint = double_to_uint(mit_param.dq, -(double)limits.vMax, (double)limits.vMax, 12);
|
||||||
|
uint16_t tau_uint =
|
||||||
|
double_to_uint(mit_param.tau, -(double)limits.tMax, (double)limits.tMax, 12);
|
||||||
|
|
||||||
|
return {static_cast<uint8_t>((q_uint >> 8) & 0xFF),
|
||||||
|
static_cast<uint8_t>(q_uint & 0xFF),
|
||||||
|
static_cast<uint8_t>(dq_uint >> 4),
|
||||||
|
static_cast<uint8_t>(((dq_uint & 0xF) << 4) | ((kp_uint >> 8) & 0xF)),
|
||||||
|
static_cast<uint8_t>(kp_uint & 0xFF),
|
||||||
|
static_cast<uint8_t>(kd_uint >> 4),
|
||||||
|
static_cast<uint8_t>(((kd_uint & 0xF) << 4) | ((tau_uint >> 8) & 0xF)),
|
||||||
|
static_cast<uint8_t>(tau_uint & 0xFF)};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> CanPacketEncoder::pack_query_param_data(uint32_t send_can_id, int RID) {
|
||||||
|
return {static_cast<uint8_t>(send_can_id & 0xFF),
|
||||||
|
static_cast<uint8_t>((send_can_id >> 8) & 0xFF),
|
||||||
|
0x33,
|
||||||
|
static_cast<uint8_t>(RID),
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> CanPacketEncoder::pack_command_data(uint8_t cmd) {
|
||||||
|
return {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, cmd};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility function implementations
|
||||||
|
double CanPacketEncoder::limit_min_max(double x, double min, double max) {
|
||||||
|
return std::max(min, std::min(x, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t CanPacketEncoder::double_to_uint(double x, double x_min, double x_max, int bits) {
|
||||||
|
x = limit_min_max(x, x_min, x_max);
|
||||||
|
double span = x_max - x_min;
|
||||||
|
double data_norm = (x - x_min) / span;
|
||||||
|
return static_cast<uint16_t>(data_norm * ((1 << bits) - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
double CanPacketDecoder::uint_to_double(uint16_t x, double min, double max, int bits) {
|
||||||
|
double span = max - min;
|
||||||
|
double data_norm = static_cast<double>(x) / ((1 << bits) - 1);
|
||||||
|
return data_norm * span + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
float CanPacketDecoder::uint8s_to_float(const std::array<uint8_t, 4>& bytes) {
|
||||||
|
float value;
|
||||||
|
std::memcpy(&value, bytes.data(), sizeof(float));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t CanPacketDecoder::uint8s_to_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3,
|
||||||
|
uint8_t byte4) {
|
||||||
|
uint32_t value;
|
||||||
|
uint8_t bytes[4] = {byte1, byte2, byte3, byte4};
|
||||||
|
std::memcpy(&value, bytes, sizeof(uint32_t));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanPacketDecoder::is_in_ranges(int number) {
|
||||||
|
return (7 <= number && number <= 10) || (13 <= number && number <= 16) ||
|
||||||
|
(35 <= number && number <= 36);
|
||||||
|
}
|
||||||
|
} // namespace openarm::damiao_motor
|
||||||
116
src/openarm/damiao_motor/dm_motor_device.cpp
Normal file
116
src/openarm/damiao_motor/dm_motor_device.cpp
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
#include <openarm/damiao_motor/dm_motor.hpp>
|
||||||
|
#include <openarm/damiao_motor/dm_motor_constants.hpp>
|
||||||
|
#include <openarm/damiao_motor/dm_motor_control.hpp>
|
||||||
|
#include <openarm/damiao_motor/dm_motor_device.hpp>
|
||||||
|
|
||||||
|
namespace openarm::damiao_motor {
|
||||||
|
|
||||||
|
DMCANDevice::DMCANDevice(Motor& motor, canid_t recv_can_mask, bool use_fd)
|
||||||
|
: canbus::CANDevice(motor.get_send_can_id(), motor.get_recv_can_id(), recv_can_mask, use_fd),
|
||||||
|
motor_(motor),
|
||||||
|
callback_mode_(CallbackMode::STATE),
|
||||||
|
use_fd_(use_fd) {}
|
||||||
|
|
||||||
|
std::vector<uint8_t> DMCANDevice::get_data_from_frame(const can_frame& frame) {
|
||||||
|
return std::vector<uint8_t>(frame.data, frame.data + frame.can_dlc);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> DMCANDevice::get_data_from_frame(const canfd_frame& frame) {
|
||||||
|
return std::vector<uint8_t>(frame.data, frame.data + frame.len);
|
||||||
|
}
|
||||||
|
void DMCANDevice::callback(const can_frame& frame) {
|
||||||
|
if (use_fd_) {
|
||||||
|
std::cerr << "WARNING: WRONG CALLBACK FUNCTION" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> data = get_data_from_frame(frame);
|
||||||
|
|
||||||
|
switch (callback_mode_) {
|
||||||
|
case STATE:
|
||||||
|
if (frame.can_dlc >= 8) {
|
||||||
|
// Convert frame data to vector and let Motor handle parsing
|
||||||
|
StateResult result = CanPacketDecoder::parse_motor_state_data(motor_, data);
|
||||||
|
if (frame.can_id == motor_.get_recv_can_id() && result.valid) {
|
||||||
|
motor_.update_state(result.position, result.velocity, result.torque,
|
||||||
|
result.t_mos, result.t_rotor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PARAM: {
|
||||||
|
ParamResult result = CanPacketDecoder::parse_motor_param_data(data);
|
||||||
|
if (result.valid) {
|
||||||
|
motor_.set_temp_param(result.rid, result.value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IGNORE:
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DMCANDevice::callback(const canfd_frame& frame) {
|
||||||
|
if (not use_fd_) {
|
||||||
|
std::cerr << "WARNING: CANFD MODE NOT ENABLED" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame.can_id != motor_.get_recv_can_id()) {
|
||||||
|
std::cerr << "WARNING: CANFD FRAME ID DOES NOT MATCH MOTOR ID" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> data = get_data_from_frame(frame);
|
||||||
|
if (callback_mode_ == STATE) {
|
||||||
|
StateResult result = CanPacketDecoder::parse_motor_state_data(motor_, data);
|
||||||
|
if (result.valid) {
|
||||||
|
motor_.update_state(result.position, result.velocity, result.torque, result.t_mos,
|
||||||
|
result.t_rotor);
|
||||||
|
}
|
||||||
|
} else if (callback_mode_ == PARAM) {
|
||||||
|
ParamResult result = CanPacketDecoder::parse_motor_param_data(data);
|
||||||
|
if (result.valid) {
|
||||||
|
motor_.set_temp_param(result.rid, result.value);
|
||||||
|
}
|
||||||
|
} else if (callback_mode_ == IGNORE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
can_frame DMCANDevice::create_can_frame(canid_t send_can_id, std::vector<uint8_t> data) {
|
||||||
|
can_frame frame;
|
||||||
|
std::memset(&frame, 0, sizeof(frame));
|
||||||
|
frame.can_id = send_can_id;
|
||||||
|
frame.can_dlc = data.size();
|
||||||
|
std::copy(data.begin(), data.end(), frame.data);
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
canfd_frame DMCANDevice::create_canfd_frame(canid_t send_can_id, std::vector<uint8_t> data) {
|
||||||
|
canfd_frame frame;
|
||||||
|
frame.can_id = send_can_id;
|
||||||
|
frame.len = data.size();
|
||||||
|
frame.flags = CANFD_BRS;
|
||||||
|
std::copy(data.begin(), data.end(), frame.data);
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace openarm::damiao_motor
|
||||||
128
src/openarm/damiao_motor/dm_motor_device_collection.cpp
Normal file
128
src/openarm/damiao_motor/dm_motor_device_collection.cpp
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// Copyright 2025 Enactic, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#include <linux/can.h>
|
||||||
|
#include <linux/can/raw.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <openarm/damiao_motor/dm_motor_device_collection.hpp>
|
||||||
|
|
||||||
|
namespace openarm::damiao_motor {
|
||||||
|
|
||||||
|
DMDeviceCollection::DMDeviceCollection(canbus::CANSocket& can_socket)
|
||||||
|
: can_socket_(can_socket),
|
||||||
|
can_packet_encoder_(std::make_unique<CanPacketEncoder>()),
|
||||||
|
can_packet_decoder_(std::make_unique<CanPacketDecoder>()),
|
||||||
|
device_collection_(std::make_unique<canbus::CANDeviceCollection>(can_socket_)) {}
|
||||||
|
|
||||||
|
void DMDeviceCollection::enable_all() {
|
||||||
|
for (auto dm_device : get_dm_devices()) {
|
||||||
|
auto& motor = dm_device->get_motor();
|
||||||
|
CANPacket enable_packet = CanPacketEncoder::create_enable_command(motor);
|
||||||
|
send_command_to_device(dm_device, enable_packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DMDeviceCollection::disable_all() {
|
||||||
|
for (auto dm_device : get_dm_devices()) {
|
||||||
|
CANPacket disable_packet = CanPacketEncoder::create_disable_command(dm_device->get_motor());
|
||||||
|
send_command_to_device(dm_device, disable_packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DMDeviceCollection::set_zero_all() {
|
||||||
|
for (auto dm_device : get_dm_devices()) {
|
||||||
|
CANPacket zero_packet = CanPacketEncoder::create_set_zero_command(dm_device->get_motor());
|
||||||
|
send_command_to_device(dm_device, zero_packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DMDeviceCollection::refresh_one(int i) {
|
||||||
|
auto dm_device = get_dm_devices()[i];
|
||||||
|
auto& motor = dm_device->get_motor();
|
||||||
|
CANPacket refresh_packet = CanPacketEncoder::create_refresh_command(motor);
|
||||||
|
send_command_to_device(dm_device, refresh_packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DMDeviceCollection::refresh_all() {
|
||||||
|
for (auto dm_device : get_dm_devices()) {
|
||||||
|
auto& motor = dm_device->get_motor();
|
||||||
|
CANPacket refresh_packet = CanPacketEncoder::create_refresh_command(motor);
|
||||||
|
send_command_to_device(dm_device, refresh_packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DMDeviceCollection::set_callback_mode_all(CallbackMode callback_mode) {
|
||||||
|
for (auto dm_device : get_dm_devices()) {
|
||||||
|
dm_device->set_callback_mode(callback_mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DMDeviceCollection::query_param_one(int i, int RID) {
|
||||||
|
CANPacket param_query =
|
||||||
|
CanPacketEncoder::create_query_param_command(get_dm_devices()[i]->get_motor(), RID);
|
||||||
|
send_command_to_device(get_dm_devices()[i], param_query);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DMDeviceCollection::query_param_all(int RID) {
|
||||||
|
for (auto dm_device : get_dm_devices()) {
|
||||||
|
CANPacket param_query =
|
||||||
|
CanPacketEncoder::create_query_param_command(dm_device->get_motor(), RID);
|
||||||
|
send_command_to_device(dm_device, param_query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DMDeviceCollection::send_command_to_device(std::shared_ptr<DMCANDevice> dm_device,
|
||||||
|
const CANPacket& packet) {
|
||||||
|
if (can_socket_.is_canfd_enabled()) {
|
||||||
|
canfd_frame frame = dm_device->create_canfd_frame(packet.send_can_id, packet.data);
|
||||||
|
can_socket_.write_canfd_frame(frame);
|
||||||
|
} else {
|
||||||
|
can_frame frame = dm_device->create_can_frame(packet.send_can_id, packet.data);
|
||||||
|
can_socket_.write_can_frame(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DMDeviceCollection::mit_control_one(int i, const MITParam& mit_param) {
|
||||||
|
CANPacket mit_cmd =
|
||||||
|
CanPacketEncoder::create_mit_control_command(get_dm_devices()[i]->get_motor(), mit_param);
|
||||||
|
send_command_to_device(get_dm_devices()[i], mit_cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DMDeviceCollection::mit_control_all(const std::vector<MITParam>& mit_params) {
|
||||||
|
for (size_t i = 0; i < mit_params.size(); i++) {
|
||||||
|
mit_control_one(i, mit_params[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Motor> DMDeviceCollection::get_motors() const {
|
||||||
|
std::vector<Motor> motors;
|
||||||
|
for (auto dm_device : get_dm_devices()) {
|
||||||
|
motors.push_back(dm_device->get_motor());
|
||||||
|
}
|
||||||
|
return motors;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<DMCANDevice>> DMDeviceCollection::get_dm_devices() const {
|
||||||
|
std::vector<std::shared_ptr<DMCANDevice>> dm_devices;
|
||||||
|
for (const auto& [id, device] : device_collection_->get_devices()) {
|
||||||
|
auto dm_device = std::dynamic_pointer_cast<DMCANDevice>(device);
|
||||||
|
if (dm_device) {
|
||||||
|
dm_devices.push_back(dm_device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dm_devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace openarm::damiao_motor
|
||||||
Loading…
Reference in New Issue
Block a user