init
This commit is contained in:
commit
f2204f3cb4
16 changed files with 591 additions and 0 deletions
22
LICENSE
Normal file
22
LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
|||
MIT License
|
||||
|
||||
Original Copyright (c) 2023 Philipp Wu
|
||||
Modified Copyright (c) 2025 Franka Robotics GmbH
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
7
config/gello_config.yaml
Normal file
7
config/gello_config.yaml
Normal file
|
@ -0,0 +1,7 @@
|
|||
usb-FTDI_USB__-__Serial_Converter_FTA7NPA1-if00-port0:
|
||||
side: "sample-config"
|
||||
num_joints: 7
|
||||
joint_signs: [1, 1, 1, 1, 1, -1, 1]
|
||||
gripper: true
|
||||
best_offsets: [6.283, 4.712, 3.142, 3.142, 3.142, 3.142, 4.712]
|
||||
gripper_range_rad: [2.77856, 3.50931]
|
0
franka_gello_state_publisher/__init__.py
Normal file
0
franka_gello_state_publisher/__init__.py
Normal file
Binary file not shown.
BIN
franka_gello_state_publisher/__pycache__/driver.cpython-312.pyc
Normal file
BIN
franka_gello_state_publisher/__pycache__/driver.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
274
franka_gello_state_publisher/driver.py
Normal file
274
franka_gello_state_publisher/driver.py
Normal file
|
@ -0,0 +1,274 @@
|
|||
import time
|
||||
from threading import Event, Lock, Thread
|
||||
from typing import Protocol, Sequence
|
||||
|
||||
import numpy as np
|
||||
from dynamixel_sdk.group_sync_read import GroupSyncRead
|
||||
from dynamixel_sdk.group_sync_write import GroupSyncWrite
|
||||
from dynamixel_sdk.packet_handler import PacketHandler
|
||||
from dynamixel_sdk.port_handler import PortHandler
|
||||
from dynamixel_sdk.robotis_def import (
|
||||
COMM_SUCCESS,
|
||||
DXL_HIBYTE,
|
||||
DXL_HIWORD,
|
||||
DXL_LOBYTE,
|
||||
DXL_LOWORD,
|
||||
)
|
||||
|
||||
# Constants
|
||||
ADDR_TORQUE_ENABLE = 64
|
||||
ADDR_GOAL_POSITION = 116
|
||||
LEN_GOAL_POSITION = 4
|
||||
ADDR_PRESENT_POSITION = 132
|
||||
LEN_PRESENT_POSITION = 4
|
||||
TORQUE_ENABLE = 1
|
||||
TORQUE_DISABLE = 0
|
||||
|
||||
|
||||
class DynamixelDriverProtocol(Protocol):
|
||||
def set_joints(self, joint_angles: Sequence[float]):
|
||||
"""Set the joint angles for the Dynamixel servos.
|
||||
|
||||
Args:
|
||||
joint_angles (Sequence[float]): A list of joint angles.
|
||||
"""
|
||||
...
|
||||
|
||||
def torque_enabled(self) -> bool:
|
||||
"""Check if torque is enabled for the Dynamixel servos.
|
||||
|
||||
Returns:
|
||||
bool: True if torque is enabled, False if it is disabled.
|
||||
"""
|
||||
...
|
||||
|
||||
def set_torque_mode(self, enable: bool):
|
||||
"""Set the torque mode for the Dynamixel servos.
|
||||
|
||||
Args:
|
||||
enable (bool): True to enable torque, False to disable.
|
||||
"""
|
||||
...
|
||||
|
||||
def get_joints(self) -> np.ndarray:
|
||||
"""Get the current joint angles in radians.
|
||||
|
||||
Returns:
|
||||
np.ndarray: An array of joint angles.
|
||||
"""
|
||||
...
|
||||
|
||||
def close(self):
|
||||
"""Close the driver."""
|
||||
|
||||
|
||||
class FakeDynamixelDriver(DynamixelDriverProtocol):
|
||||
def __init__(self, ids: Sequence[int]):
|
||||
self._ids = ids
|
||||
self._joint_angles = np.zeros(len(ids), dtype=int)
|
||||
self._torque_enabled = False
|
||||
|
||||
def set_joints(self, joint_angles: Sequence[float]):
|
||||
if len(joint_angles) != len(self._ids):
|
||||
raise ValueError(
|
||||
"The length of joint_angles must match the number of servos"
|
||||
)
|
||||
if not self._torque_enabled:
|
||||
raise RuntimeError("Torque must be enabled to set joint angles")
|
||||
self._joint_angles = np.array(joint_angles)
|
||||
|
||||
def torque_enabled(self) -> bool:
|
||||
return self._torque_enabled
|
||||
|
||||
def set_torque_mode(self, enable: bool):
|
||||
self._torque_enabled = enable
|
||||
|
||||
def get_joints(self) -> np.ndarray:
|
||||
return self._joint_angles.copy()
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
class DynamixelDriver(DynamixelDriverProtocol):
|
||||
def __init__(
|
||||
self, ids: Sequence[int], port: str = "/dev/ttyUSB0", baudrate: int = 2000000
|
||||
):
|
||||
"""Initialize the DynamixelDriver class.
|
||||
|
||||
Args:
|
||||
ids (Sequence[int]): A list of IDs for the Dynamixel servos.
|
||||
port (str): The USB port to connect to the arm.
|
||||
baudrate (int): The baudrate for communication.
|
||||
"""
|
||||
self._ids = ids
|
||||
self._joint_angles = None
|
||||
self._lock = Lock()
|
||||
|
||||
# Initialize the port handler, packet handler, and group sync read/write
|
||||
self._portHandler = PortHandler(port)
|
||||
self._packetHandler = PacketHandler(2.0)
|
||||
self._groupSyncRead = GroupSyncRead(
|
||||
self._portHandler,
|
||||
self._packetHandler,
|
||||
ADDR_PRESENT_POSITION,
|
||||
LEN_PRESENT_POSITION,
|
||||
)
|
||||
self._groupSyncWrite = GroupSyncWrite(
|
||||
self._portHandler,
|
||||
self._packetHandler,
|
||||
ADDR_GOAL_POSITION,
|
||||
LEN_GOAL_POSITION,
|
||||
)
|
||||
|
||||
# Open the port and set the baudrate
|
||||
if not self._portHandler.openPort():
|
||||
raise RuntimeError("Failed to open the port")
|
||||
|
||||
if not self._portHandler.setBaudRate(baudrate):
|
||||
raise RuntimeError(f"Failed to change the baudrate, {baudrate}")
|
||||
|
||||
# Add parameters for each Dynamixel servo to the group sync read
|
||||
for dxl_id in self._ids:
|
||||
if not self._groupSyncRead.addParam(dxl_id):
|
||||
raise RuntimeError(
|
||||
f"Failed to add parameter for Dynamixel with ID {dxl_id}"
|
||||
)
|
||||
|
||||
# Disable torque for each Dynamixel servo
|
||||
self._torque_enabled = False
|
||||
try:
|
||||
self.set_torque_mode(self._torque_enabled)
|
||||
except Exception as e:
|
||||
print(f"port: {port}, {e}")
|
||||
|
||||
self._stop_thread = Event()
|
||||
self._start_reading_thread()
|
||||
|
||||
def set_joints(self, joint_angles: Sequence[float]):
|
||||
if len(joint_angles) != len(self._ids):
|
||||
raise ValueError(
|
||||
"The length of joint_angles must match the number of servos"
|
||||
)
|
||||
if not self._torque_enabled:
|
||||
raise RuntimeError("Torque must be enabled to set joint angles")
|
||||
|
||||
for dxl_id, angle in zip(self._ids, joint_angles):
|
||||
# Convert the angle to the appropriate value for the servo
|
||||
position_value = int(angle * 2048 / np.pi)
|
||||
|
||||
# Allocate goal position value into byte array
|
||||
param_goal_position = [
|
||||
DXL_LOBYTE(DXL_LOWORD(position_value)),
|
||||
DXL_HIBYTE(DXL_LOWORD(position_value)),
|
||||
DXL_LOBYTE(DXL_HIWORD(position_value)),
|
||||
DXL_HIBYTE(DXL_HIWORD(position_value)),
|
||||
]
|
||||
|
||||
# Add goal position value to the Syncwrite parameter storage
|
||||
dxl_addparam_result = self._groupSyncWrite.addParam(
|
||||
dxl_id, param_goal_position
|
||||
)
|
||||
if not dxl_addparam_result:
|
||||
raise RuntimeError(
|
||||
f"Failed to set joint angle for Dynamixel with ID {dxl_id}"
|
||||
)
|
||||
|
||||
# Syncwrite goal position
|
||||
dxl_comm_result = self._groupSyncWrite.txPacket()
|
||||
if dxl_comm_result != COMM_SUCCESS:
|
||||
raise RuntimeError("Failed to syncwrite goal position")
|
||||
|
||||
# Clear syncwrite parameter storage
|
||||
self._groupSyncWrite.clearParam()
|
||||
|
||||
def torque_enabled(self) -> bool:
|
||||
return self._torque_enabled
|
||||
|
||||
def set_torque_mode(self, enable: bool):
|
||||
torque_value = TORQUE_ENABLE if enable else TORQUE_DISABLE
|
||||
with self._lock:
|
||||
for dxl_id in self._ids:
|
||||
dxl_comm_result, dxl_error = self._packetHandler.write1ByteTxRx(
|
||||
self._portHandler, dxl_id, ADDR_TORQUE_ENABLE, torque_value
|
||||
)
|
||||
if dxl_comm_result != COMM_SUCCESS or dxl_error != 0:
|
||||
print(dxl_comm_result)
|
||||
print(dxl_error)
|
||||
raise RuntimeError(
|
||||
f"Failed to set torque mode for Dynamixel with ID {dxl_id}"
|
||||
)
|
||||
|
||||
self._torque_enabled = enable
|
||||
|
||||
def _start_reading_thread(self):
|
||||
self._reading_thread = Thread(target=self._read_joint_angles)
|
||||
self._reading_thread.daemon = True
|
||||
self._reading_thread.start()
|
||||
|
||||
def _read_joint_angles(self):
|
||||
# Continuously read joint angles and update the joint_angles array
|
||||
while not self._stop_thread.is_set():
|
||||
time.sleep(0.001)
|
||||
with self._lock:
|
||||
_joint_angles = np.zeros(len(self._ids), dtype=int)
|
||||
dxl_comm_result = self._groupSyncRead.txRxPacket()
|
||||
if dxl_comm_result != COMM_SUCCESS:
|
||||
print(f"warning, comm failed: {dxl_comm_result}")
|
||||
continue
|
||||
for i, dxl_id in enumerate(self._ids):
|
||||
if self._groupSyncRead.isAvailable(
|
||||
dxl_id, ADDR_PRESENT_POSITION, LEN_PRESENT_POSITION
|
||||
):
|
||||
angle = self._groupSyncRead.getData(
|
||||
dxl_id, ADDR_PRESENT_POSITION, LEN_PRESENT_POSITION
|
||||
)
|
||||
angle = np.int32(np.uint32(angle))
|
||||
_joint_angles[i] = angle
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"Failed to get joint angles for Dynamixel with ID {dxl_id}"
|
||||
)
|
||||
self._joint_angles = _joint_angles
|
||||
# self._groupSyncRead.clearParam() # TODO what does this do? should i add it
|
||||
|
||||
def get_joints(self) -> np.ndarray:
|
||||
# Return a copy of the joint_angles array to avoid race conditions
|
||||
while self._joint_angles is None:
|
||||
time.sleep(0.1)
|
||||
# with self._lock:
|
||||
_j = self._joint_angles.copy()
|
||||
return _j / 2048.0 * np.pi
|
||||
|
||||
def close(self):
|
||||
self._stop_thread.set()
|
||||
self._reading_thread.join()
|
||||
self._portHandler.closePort()
|
||||
|
||||
|
||||
def main():
|
||||
# Set the port, baudrate, and servo IDs
|
||||
ids = [1]
|
||||
|
||||
# Create a DynamixelDriver instance
|
||||
try:
|
||||
driver = DynamixelDriver(ids)
|
||||
except FileNotFoundError:
|
||||
driver = DynamixelDriver(ids, port="/dev/cu.usbserial-FT7WBMUB")
|
||||
|
||||
# Test setting torque mode
|
||||
driver.set_torque_mode(True)
|
||||
driver.set_torque_mode(False)
|
||||
|
||||
# Test reading the joint angles
|
||||
try:
|
||||
while True:
|
||||
joint_angles = driver.get_joints()
|
||||
print(f"Joint angles for IDs {ids}: {joint_angles}")
|
||||
# print(f"Joint angles for IDs {ids[1]}: {joint_angles[1]}")
|
||||
except KeyboardInterrupt:
|
||||
driver.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main() # Test the driver
|
129
franka_gello_state_publisher/gello_publisher.py
Normal file
129
franka_gello_state_publisher/gello_publisher.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
import os
|
||||
import glob
|
||||
from typing import Tuple
|
||||
import rclpy
|
||||
from rclpy.node import Node
|
||||
import numpy as np
|
||||
|
||||
from .driver import DynamixelDriver
|
||||
from sensor_msgs.msg import JointState
|
||||
from std_msgs.msg import Float32
|
||||
import yaml
|
||||
from ament_index_python.packages import get_package_share_directory
|
||||
|
||||
|
||||
class GelloPublisher(Node):
|
||||
def __init__(self):
|
||||
super().__init__("gello_publisher")
|
||||
|
||||
default_com_port = self.determine_default_com_port()
|
||||
self.declare_parameter("com_port", default_com_port)
|
||||
self.com_port = self.get_parameter("com_port").get_parameter_value().string_value
|
||||
self.port = self.com_port.split("/")[-1]
|
||||
"""The port that GELLO is connected to."""
|
||||
|
||||
config_path = os.path.join(
|
||||
get_package_share_directory("franka_gello_state_publisher"),
|
||||
"config",
|
||||
"gello_config.yaml",
|
||||
)
|
||||
self.get_values_from_config(config_path)
|
||||
|
||||
self.robot_joint_publisher = self.create_publisher(JointState, "/gello/joint_states", 10)
|
||||
self.gripper_joint_publisher = self.create_publisher(
|
||||
Float32, "/gripper_client/target_gripper_width_percent", 10
|
||||
)
|
||||
|
||||
self.timer = self.create_timer(1 / 25, self.publish_joint_jog)
|
||||
|
||||
self.joint_names = [
|
||||
"fr3_joint1",
|
||||
"fr3_joint2",
|
||||
"fr3_joint3",
|
||||
"fr3_joint4",
|
||||
"fr3_joint5",
|
||||
"fr3_joint6",
|
||||
"fr3_joint7",
|
||||
]
|
||||
|
||||
def determine_default_com_port(self) -> str:
|
||||
matches = glob.glob("/dev/serial/by-id/usb-FTDI_USB__-__Serial_Converter*")
|
||||
if matches:
|
||||
self.get_logger().info(f"Auto-detected com_ports: {matches}")
|
||||
return matches[0]
|
||||
else:
|
||||
self.get_logger().warn("No com_ports detected. Please specify the com_port manually.")
|
||||
return "INVALID_COM_PORT"
|
||||
|
||||
def get_values_from_config(self, config_file: str):
|
||||
with open(config_file, "r") as file:
|
||||
config = yaml.safe_load(file)
|
||||
|
||||
self.num_robot_joints: int = config[self.port]["num_joints"]
|
||||
"""The number of joints in the robot."""
|
||||
|
||||
self.joint_signs: Tuple[float, ...] = config[self.port]["joint_signs"]
|
||||
"""Depending on how the motor is mounted on the Gello, its rotation direction can be reversed."""
|
||||
|
||||
self.gripper: bool = config[self.port]["gripper"]
|
||||
"""Whether or not the gripper is attached."""
|
||||
|
||||
joint_ids = list(range(1, self.num_joints))
|
||||
self.driver = DynamixelDriver(joint_ids, port=self.com_port, baudrate=2000000)
|
||||
"""The driver for the Dynamixel motors."""
|
||||
|
||||
self.best_offsets = np.array(config[self.port]["best_offsets"])
|
||||
"""The best offsets for the joints."""
|
||||
|
||||
self.gripper_range_rad: Tuple[float, float] = config[self.port]["gripper_range_rad"]
|
||||
"""The range of the gripper in radians."""
|
||||
|
||||
self.__post_init__()
|
||||
|
||||
def __post_init__(self):
|
||||
assert len(self.joint_signs) == self.num_robot_joints
|
||||
for idx, j in enumerate(self.joint_signs):
|
||||
assert j == -1 or j == 1, f"Joint idx: {idx} should be -1 or 1, but got {j}."
|
||||
|
||||
@property
|
||||
def num_joints(self) -> int:
|
||||
extra_joints = 1 if self.gripper else 0
|
||||
return self.num_robot_joints + extra_joints
|
||||
|
||||
def publish_joint_jog(self):
|
||||
current_joints = self.driver.get_joints()
|
||||
current_robot_joints = current_joints[: self.num_robot_joints]
|
||||
current_joints_corrected = (current_robot_joints - self.best_offsets) * self.joint_signs
|
||||
|
||||
robot_joint_states = JointState()
|
||||
robot_joint_states.header.stamp = self.get_clock().now().to_msg()
|
||||
robot_joint_states.name = self.joint_names
|
||||
robot_joint_states.header.frame_id = "fr3_link0"
|
||||
robot_joint_states.position = [float(joint) for joint in current_joints_corrected]
|
||||
|
||||
gripper_joint_states = Float32()
|
||||
if self.gripper:
|
||||
gripper_position = current_joints[-1]
|
||||
gripper_joint_states.data = self.gripper_readout_to_percent(gripper_position)
|
||||
else:
|
||||
gripper_joint_states.position = 0.0
|
||||
self.robot_joint_publisher.publish(robot_joint_states)
|
||||
self.gripper_joint_publisher.publish(gripper_joint_states)
|
||||
|
||||
def gripper_readout_to_percent(self, gripper_position: float) -> float:
|
||||
gripper_percent = (gripper_position - self.gripper_range_rad[0]) / (
|
||||
self.gripper_range_rad[1] - self.gripper_range_rad[0]
|
||||
)
|
||||
return max(0.0, min(1.0, gripper_percent))
|
||||
|
||||
|
||||
def main(args=None):
|
||||
rclpy.init(args=args)
|
||||
gello_publisher = GelloPublisher()
|
||||
rclpy.spin(gello_publisher)
|
||||
gello_publisher.destroy_node()
|
||||
rclpy.shutdown()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
22
launch/main.launch.py
Executable file
22
launch/main.launch.py
Executable file
|
@ -0,0 +1,22 @@
|
|||
from launch import LaunchDescription
|
||||
from launch.actions import DeclareLaunchArgument
|
||||
from launch_ros.actions import Node
|
||||
from launch.substitutions import LaunchConfiguration
|
||||
|
||||
|
||||
def generate_launch_description():
|
||||
args = []
|
||||
args.append(
|
||||
DeclareLaunchArgument(name="com_port", default_value=None, description="Default COM port")
|
||||
)
|
||||
nodes = [
|
||||
Node(
|
||||
package="franka_gello_state_publisher",
|
||||
executable="gello_publisher",
|
||||
name="gello_publisher",
|
||||
output="screen",
|
||||
parameters=[{"com_port": LaunchConfiguration("com_port")}],
|
||||
),
|
||||
]
|
||||
|
||||
return LaunchDescription(args + nodes)
|
24
package.xml
Normal file
24
package.xml
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||
<package format="3">
|
||||
<name>franka_gello_state_publisher</name>
|
||||
<version>0.1.0</version>
|
||||
<description>Publisher of joint states of gello</description>
|
||||
<maintainer email="support@franka.de">Franka Robotics GmbH</maintainer>
|
||||
<license>MIT</license>
|
||||
|
||||
<depend>rclpy</depend>
|
||||
<depend>std_msgs</depend>
|
||||
<depend>sensor_msgs</depend>
|
||||
<depend>control_msgs</depend>
|
||||
<depend>geometry_msgs</depend>
|
||||
|
||||
<test_depend>ament_copyright</test_depend>
|
||||
<test_depend>ament_flake8</test_depend>
|
||||
<test_depend>ament_pep257</test_depend>
|
||||
<test_depend>python3-pytest</test_depend>
|
||||
|
||||
<export>
|
||||
<build_type>ament_python</build_type>
|
||||
</export>
|
||||
</package>
|
0
resource/franka_gello_state_publisher
Normal file
0
resource/franka_gello_state_publisher
Normal file
4
setup.cfg
Normal file
4
setup.cfg
Normal file
|
@ -0,0 +1,4 @@
|
|||
[develop]
|
||||
script_dir=$base/lib/franka_gello_state_publisher
|
||||
[install]
|
||||
install_scripts=$base/lib/franka_gello_state_publisher
|
26
setup.py
Normal file
26
setup.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
from setuptools import find_packages, setup
|
||||
import glob
|
||||
|
||||
package_name = "franka_gello_state_publisher"
|
||||
|
||||
setup(
|
||||
name=package_name,
|
||||
version="0.1.0",
|
||||
packages=find_packages(exclude=["test"]),
|
||||
data_files=[
|
||||
("share/ament_index/resource_index/packages", ["resource/" + package_name]),
|
||||
("share/" + package_name, ["package.xml"]),
|
||||
("share/" + package_name + "/config", glob.glob("config/*.yaml")),
|
||||
("share/" + package_name + "/launch", ["launch/main.launch.py"]),
|
||||
],
|
||||
install_requires=["setuptools", "dynamixel_sdk"],
|
||||
zip_safe=True,
|
||||
maintainer="Franka Robotics GmbH",
|
||||
maintainer_email="support@franka.de",
|
||||
description="Publishes the state of the GELLO teleoperation device.",
|
||||
license="MIT",
|
||||
tests_require=["pytest"],
|
||||
entry_points={
|
||||
"console_scripts": ["gello_publisher = franka_gello_state_publisher.gello_publisher:main"],
|
||||
},
|
||||
)
|
25
test/test_copyright.py
Normal file
25
test/test_copyright.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Copyright 2015 Open Source Robotics Foundation, 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.
|
||||
|
||||
from ament_copyright.main import main
|
||||
import pytest
|
||||
|
||||
|
||||
# Remove the `skip` decorator once the source file(s) have a copyright header
|
||||
@pytest.mark.skip(reason="No copyright header has been placed in the generated source file.")
|
||||
@pytest.mark.copyright
|
||||
@pytest.mark.linter
|
||||
def test_copyright():
|
||||
rc = main(argv=[".", "test"])
|
||||
assert rc == 0, "Found errors"
|
33
test/test_flake8.py
Normal file
33
test/test_flake8.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Copyright 2017 Open Source Robotics Foundation, 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.
|
||||
|
||||
from ament_flake8.main import main_with_errors
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@pytest.mark.flake8
|
||||
@pytest.mark.linter
|
||||
def test_flake8():
|
||||
config_path = Path(__file__).resolve().parents[3] / ".flake8"
|
||||
excluded_file = Path(__file__).resolve().parents[1] / "franka_gello_state_publisher/driver.py"
|
||||
print(excluded_file)
|
||||
rc, errors = main_with_errors(
|
||||
argv=["--config", str(config_path), "--exclude", str(excluded_file)]
|
||||
)
|
||||
assert rc == 0, "Found %d code style errors / warnings:\n" % len(errors) + "\n".join(errors)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_flake8()
|
25
test/test_pep257.py
Normal file
25
test/test_pep257.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Copyright 2015 Open Source Robotics Foundation, 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.
|
||||
|
||||
from ament_pep257.main import main
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@pytest.mark.linter
|
||||
@pytest.mark.pep257
|
||||
def test_pep257():
|
||||
excluded_file = Path(__file__).resolve().parents[1] / "franka_gello_state_publisher/driver.py"
|
||||
rc = main(argv=[".", "test", "--exclude", str(excluded_file)])
|
||||
assert rc == 0, "Found code style errors / warnings"
|
Loading…
Add table
Add a link
Reference in a new issue