Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
763896e
Merge all changes from initial testing issues.
SimeonPr Feb 11, 2026
7b49639
rise_motion: cia402.hpp: Fix signedness of CiA402_Outputs.
SimeonPr Feb 17, 2026
238ae55
rise_motion: Add CiA402::to_switch_on_disabled.
SimeonPr Feb 17, 2026
fa8d393
rise_motion: Enable packedMode.
SimeonPr Feb 17, 2026
5ddbcd8
rise_motion: ec_manager: Remove obsolete functions.
SimeonPr Feb 17, 2026
22f1fbe
rise_motion: ec_manager.cpp: Add motor shutdown on cyclic loop exit.
SimeonPr Feb 17, 2026
5320121
Delete ec_structs.hpp
SimeonPr Feb 17, 2026
992f0c3
ec_manager.cpp: Set ModeOfOperation to CyclicSyncPositionMode
SimeonPr Feb 17, 2026
48c422d
ec_manager.cpp: Fix Transition to OPERATION_ENABLED (CiA402).
SimeonPr Feb 17, 2026
3e6f7dd
ec_manager.cpp: Fix indexing and input/output semantic.
SimeonPr Feb 17, 2026
7b2ee72
ec_manager.cpp: Fix indexing and input/output semantic. (again)
SimeonPr Feb 17, 2026
cce4076
ec_manager.cpp: Fix headers.
SimeonPr Feb 17, 2026
5ce8982
ec_manager.cpp: Fix dummy node name check.
SimeonPr Feb 17, 2026
e77aed0
rise_motion: main.cpp: Set interface to enp1s0.
SimeonPr Feb 17, 2026
03ec358
testing_node.cpp: Make node less noisy.
SimeonPr Feb 17, 2026
332a362
rise_motion: cia402.cpp: Reset Faults (for testing).
SimeonPr Feb 18, 2026
7bb70e6
rise_motion: ec_manager: Add transition_ec function.
SimeonPr Feb 18, 2026
9a31406
rise_motion: ec_manager.cpp: memset ctx and IOMap to 0.
SimeonPr Feb 18, 2026
fce0c68
rise_motion: ec_manager.cpp: Add explicit ethercat transitions.
SimeonPr Feb 18, 2026
bf1fcd1
rise_motion: ec_manager.cpp: Cleanup.
SimeonPr Feb 18, 2026
56f9bda
rise_motion: ec_manager.cpp: Quality of Life Improvements
SimeonPr Feb 23, 2026
2522f79
ec_manager: Add run method.
SimeonPr Feb 23, 2026
d1ad34f
ec_manager: Fine tuning.
SimeonPr Feb 23, 2026
adf3105
cia402.cpp: Complete switch_on_disabled logic.
SimeonPr Feb 23, 2026
f7c241e
ethercat_node.cpp: Remove unnecessary extensions.
SimeonPr Feb 23, 2026
249cd2f
Add build script.
SimeonPr Feb 23, 2026
019c91c
build.sh: Update permissions.
SimeonPr Feb 23, 2026
1a88f00
Implement better ethercat startup logic.
SimeonPr Mar 4, 2026
23dadbd
testing_node: Don't start publishing before valid pos.
SimeonPr Mar 4, 2026
408bf88
ec_manager.cpp: Move configuration after operational EC.
SimeonPr Mar 4, 2026
1fdcbfe
ec_manager.cpp: Disable auto fault recovery.
SimeonPr Mar 4, 2026
ab6ba25
ec_manager.cpp: Monitor CiA402 State during cyclic loop.
SimeonPr Mar 4, 2026
a128608
ec_manager.cpp: Set TargetPosition even if no new data available.
SimeonPr Mar 4, 2026
67ace1b
Add SDOs
SimeonPr Mar 5, 2026
df521b2
rise_motion: cia402: Add transition_to.
SimeonPr Mar 5, 2026
51e596f
rise_motion: cia402: Remove auto fault reset.
SimeonPr Mar 5, 2026
d71992b
ec_manager: Move cycle_time + logger into initializer.
SimeonPr Mar 5, 2026
89273d5
ec_manager.hpp: Add missing declarations.
SimeonPr Mar 5, 2026
6196e50
ec_manager.cpp: Add transition_motors_to.
SimeonPr Mar 5, 2026
5507795
ec_manager.cpp: Use goto instead of exit for graceful shutdown.
SimeonPr Mar 5, 2026
6448039
ec_manager.cpp: Update next before entering loop.
SimeonPr Mar 5, 2026
7c85fe1
main.cpp: Call ec_manager with cycle_time.
SimeonPr Mar 5, 2026
94e83b9
ec_manager: Add is_running.
SimeonPr Mar 5, 2026
e263c22
ec_manager.hpp: Fix headers.
SimeonPr Mar 5, 2026
ca6e8b9
ethercat_node.cpp: Only publish if ethercat is enabled.
SimeonPr Mar 5, 2026
66ad520
Formatting.
SimeonPr Mar 5, 2026
82bcc67
ec_manager: Add shutdown method and motors property.
SimeonPr Mar 6, 2026
2ea9f6f
Feat: Implemented Mock ECManager
florian-pages Mar 25, 2026
0b6fa43
Improve docs
florian-pages Mar 25, 2026
3fd9759
Fix docs
florian-pages Mar 25, 2026
ea29f92
Add .gitignore
florian-pages Mar 27, 2026
7678cd1
dont know what im doing
florian-pages Mar 27, 2026
1ab4135
Edit documentation
florian-pages Mar 27, 2026
8d1db3f
feat: implemented basic admittance
florian-pages Mar 27, 2026
877f966
Feat: Implemented forwarding all motor data
florian-pages Mar 27, 2026
6aeeb2f
Edit documentation
florian-pages Mar 27, 2026
e00238d
added debug message for admittance ctrl
florian-pages Mar 27, 2026
40deca4
Improvements to admittance node
florian-pages Mar 27, 2026
4cb6466
Basic admittance working
florian-pages Mar 29, 2026
a2f4683
Reduce control loop to only run when new data is available
florian-pages Mar 29, 2026
ba5bad2
fix: safety guards and timestamps for admittance node
florian-pages Mar 29, 2026
31742c8
fix: update timestamp retrieval for motor velocity and debug messages
florian-pages Apr 8, 2026
0f70f2a
feat: velocity mode switching via ROS2 service
florian-pages Apr 8, 2026
316e3a8
feat: add mode validation, only accept postition and velocities
florian-pages Apr 8, 2026
1c79d09
Feat: Add fake sensor input for admittance node
florian-pages Apr 8, 2026
67c9ede
Change override for sensor data from force to adc ticks to test the w…
florian-pages Apr 8, 2026
a536f4b
Feat: Admttanz node setzt operation mode beim start auf velocity
florian-pages Apr 8, 2026
65fe72e
Add python script to simulate adc reading
florian-pages Apr 8, 2026
ab13363
Merge branch 'wip/implement-admittance-node' into topic-#51-admittanz…
florian-pages Apr 9, 2026
7ddfe4d
Remove admittance node to keep rise-motion clean
florian-pages Apr 9, 2026
a432341
remove sinewave in mock manager
florian-pages Apr 9, 2026
31e075b
remove docs from tracking
florian-pages Apr 15, 2026
c53b5d2
Feat: increase to six motors
florian-pages Apr 29, 2026
02e3471
mock: add num_motors CLI argument
florian-pages May 13, 2026
817ded9
add plotjuggler to launch file
florian-pages May 13, 2026
a4abf7f
Add adc_voltage_raw
florian-pages May 20, 2026
19b51a9
Feat: Add torque offset dataflow
florian-pages May 21, 2026
e6ae68c
AdmittanceDebug: add pos_rad field (absolute encoder position)
florian-pages May 21, 2026
920ca38
Merge remote-tracking branch 'origin/feature/torque-offset' into topi…
florian-pages May 21, 2026
e086d76
Set EtherCAT interface to enp5s0 (new tower PC)
florian-pages Jun 4, 2026
3514013
ec_manager: tolerate WKC jitter, recover from QUICK_STOP
florian-pages Jun 4, 2026
678a4b8
AdmittanceDebug: add force_filtered field for EMA filter output
florian-pages Jun 4, 2026
0ede03e
ethercat: add CyclicSyncTorque mode (mode 10) support
florian-pages Jun 5, 2026
2063f33
AdmittanceDebug: add accel, velocity_fb_joint,
florian-pages Jun 28, 2026
20de17c
AdmittanceDebug: add pos_rel_rad/pos_rel_deg tare fields
florian-pages Jul 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
**/build/
**/log/
**/install/
rosbag2_*/
**/*~
.vscode
.vscode
.claude
CLAUDE.md
plans/
SOMANET Circulo Safe Motion.pdf
docs/
3 changes: 3 additions & 0 deletions rise_motion_dev_ws/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
colcon build --symlink-install --cmake-args -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
sudo setcap cap_net_admin,cap_net_raw+eip build/rise_motion/rise_motion_main
source install/setup.bash
62 changes: 59 additions & 3 deletions rise_motion_dev_ws/src/rise_motion/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,66 @@ endif()

# find dependencies
find_package(ament_cmake REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rise_motion_messages REQUIRED)
find_package(std_msgs REQUIRED)

include_directories(include)

add_subdirectory(SOEM)

add_executable(
rise_motion_main
src/main.cpp
src/ec_manager.cpp
src/ethercat_node.cpp
src/cia402.cpp
)
ament_target_dependencies(
rise_motion_main
rclcpp
rise_motion_messages
)
target_link_libraries(
rise_motion_main
soem
)

#target_link_options(rise_motion_main PRIVATE -Wl,-rpath,/hello/home/rise/Projects/rise-motion/rise_motion_dev_ws/install/rise_motion_messages/lib)
install(TARGETS
rise_motion_main
DESTINATION lib/${PROJECT_NAME}
)

add_executable(rise_motion_mock
src/main_mock.cpp
src/ec_manager_mock.cpp
src/ethercat_node.cpp
)
ament_target_dependencies(
rise_motion_mock
rclcpp
rise_motion_messages
)
install(TARGETS
rise_motion_mock
DESTINATION lib/${PROJECT_NAME}
)


add_executable(testing_node
src/testing_node.cpp
)

target_link_libraries(testing_node PUBLIC
${rise_motion_messages_TARGETS}
rclcpp::rclcpp
)

install(TARGETS
testing_node
DESTINATION lib/${PROJECT_NAME}
)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
Expand Down
123 changes: 123 additions & 0 deletions rise_motion_dev_ws/src/rise_motion/include/rise_motion/apsa.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#pragma once

#include <atomic>
#include <cstring>
#include <memory>


template <typename T>
class APSA {
public:
APSA()
: c_mem(std::make_unique<T>()), // Allocate communication buffer
a_mem(std::make_unique<T>()), // Allocate atomic/shared buffer
p_mem(std::make_unique<T>()), // Allocate performance buffer
c_p(nullptr), // Communication pointer starts null
a_p(nullptr), // Atomic pointer starts null
p_p(nullptr) // Performance pointer starts null
{
// No initialization needed - pointers start null as per APSA spec
}

bool comm_write(const T& data) {
// Copy new data into our communication buffer
*c_mem = data;

// Point c_p to the buffer we just wrote
c_p.store(c_mem.get(), std::memory_order_release);

T* desired = c_p.load(std::memory_order_acquire);

// Perform the atomic swap
T* old_a_p = a_p.exchange(desired, std::memory_order_acq_rel);

// Update c_p to point to what a_p was pointing to
c_p.store(old_a_p, std::memory_order_release);

return true;
}


bool perf_read(T& data) {
// Check if new data is available in atomic pointer
// memory_order_acquire ensures we see the write from comm_write
T* a_ptr = a_p.load(std::memory_order_acquire);

if (a_ptr != nullptr) {
// New data is available! Swap p_p with a_p to claim it
T* old_p_p = p_p.exchange(a_p.load(std::memory_order_acquire),
std::memory_order_acq_rel);

// Update a_p atomically
a_p.store(old_p_p, std::memory_order_release);

// Copy the data from our performance buffer
// p_p now points to the buffer with fresh data
T* p_ptr = p_p.load(std::memory_order_acquire);
if (p_ptr != nullptr) {
data = *p_ptr;

// This tells the communication thread we're done with this data
p_p.store(nullptr, std::memory_order_release);

return true; // New data was read
}
}

return false; // No new data available
}


bool perf_write(const T& data) {
// Copy data into our performance buffer
*p_mem = data;

// Point p_p to the buffer we just wrote
p_p.store(p_mem.get(), std::memory_order_release);

// Atomically swap p_p with a_p
T* old_a_p = a_p.exchange(p_p.load(std::memory_order_acquire),
std::memory_order_acq_rel);

// Update p_p to point to what a_p was pointing to
p_p.store(old_a_p, std::memory_order_release);

return true;
}


bool comm_read(T& data) {
// Check if new data is available in atomic pointer
T* a_ptr = a_p.load(std::memory_order_acquire);

if (a_ptr != nullptr) {
// New data is available! Swap c_p with a_p to claim it
T* old_c_p = c_p.exchange(a_p.load(std::memory_order_acquire),
std::memory_order_acq_rel);

// Update a_p atomically
a_p.store(old_c_p, std::memory_order_release);

// Copy the data from our communication buffer
T* c_ptr = c_p.load(std::memory_order_acquire);
if (c_ptr != nullptr) {
data = *c_ptr;

c_p.store(nullptr, std::memory_order_release);

return true; // New data was read
}
}

return false; // No new data available
}

private:
std::unique_ptr<T> c_mem; // Communication thread's buffer (ROS side)
std::unique_ptr<T> a_mem; // Atomic/shared buffer (exchange point)
std::unique_ptr<T> p_mem; // Performance thread's buffer (EtherCAT side)

std::atomic<T*> c_p; // Communication pointer (ROS side access)
std::atomic<T*> a_p; // Atomic pointer (shared exchange point)
std::atomic<T*> p_p; // Performance pointer (EtherCAT side access)
};
151 changes: 151 additions & 0 deletions rise_motion_dev_ws/src/rise_motion/include/rise_motion/cia402.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#pragma once
#include <cstdint>
#include <optional>
#include <string>

#include <osal_defs.h>

OSAL_PACKED_BEGIN
typedef struct OSAL_PACKED {
uint16_t Statusword;
int8_t OpModeDisplay;
int32_t PositionValue;
int32_t VelocityValue;
int16_t TorqueValue;
uint16_t AnalogInput1;
uint16_t AnalogInput2;
uint16_t AnalogInput3;
uint16_t AnalogInput4;
uint32_t TuningStatus;
uint32_t DigitalInputs;
uint32_t UserMISO;
uint32_t Timestamp;
int32_t PositionDemandInternalValue;
int32_t VelocityDemandValue;
int16_t TorqueDemand;
} CiA402_Inputs;
OSAL_PACKED_END

OSAL_PACKED_BEGIN
typedef struct OSAL_PACKED {
uint16_t Controlword;
int8_t OpMode;
int16_t TargetTorque;
int32_t TargetPosition;
int32_t TargetVelocity;
int16_t TorqueOffset;
uint32_t TuningCommand;
uint32_t PhysicalOutputs;
uint32_t BitMask;
uint32_t UserMOSI;
int32_t VelocityOffset;
} CiA402_Outputs;
OSAL_PACKED_END

class CiA402Motor {
public:
enum class State {
NOT_READY_TO_SWITCH_ON,
SWITCH_ON_DISABLED,
READY_TO_SWITCH_ON,
SWITCHED_ON,
OPERATION_ENABLED,
QUICK_STOP_ACTIVE,
FAULT_REACTION_ACTIVE,
FAULT,
};

enum class Operation {
SHUTDOWN,
SWITCH_ON,
DISABLE_VOLTAGE,
QUICK_STOP,
DISABLE_OPERATION,
ENABLE_OPERATION,
FAULT_RESET
};

// https://doc.synapticon.com/circulo/sw5.4/objects_html/6xxx/6060.html
enum class ModeOfOperation : int8_t {
ImpedanceMode = -6,
JointTorqueMode = -5,
SystemIdentificationMode = -4,
OpenLoopFieldMode = -3,
DiagnosticsMode = -2,
CoggingCompensationRecordingMode = -1,
ProfilePositionMode = 1,
ProfileVelocityMode = 3,
TorqueProfileMode = 4,
HomingMode = 6,
CyclicSyncPositionMode = 8,
CyclicSyncVelocityMode = 9,
CyclicSyncTorqueMode = 10
};

CiA402Motor(CiA402_Inputs *inputs, CiA402_Outputs *outputs);

/**
* @brief Gets the motor's current state based on its status word.
*
* Compares the status word against predefined patterns. Returns the matching
* state, or `std::nullopt` if no match is found.
*
* @return The motor's state if a match is found; otherwise, `std::nullopt`.
*
* @warning Returning `std::nullopt` indicates an unrecognized status word.
* Callers must handle this case to avoid undefined behavior.
*
* @see CiA402Motor::State, state_patterns
*/
std::optional<State> get_state() const;
std::string state_as_string() const;
bool is_state(State s) const;
bool is_operation_enabled() const;
bool is_fault() const;

void to_operation_enabled();
void to_switch_on_disabled();
void transition_to(State s);
void reset_fault();
void set_control_word(Operation op);
void set_mode_of_operation(ModeOfOperation m);

CiA402_Inputs *inputs;
CiA402_Outputs *outputs;

private:
struct StatePattern {
uint16_t mask;
uint16_t value;
State state;
};

static constexpr StatePattern state_patterns[] = {
// https://doc.synapticon.com/circulo/system_integration/status_and_controlword.html
// mask value state
{0b1001111, 0b0000000, State::NOT_READY_TO_SWITCH_ON},
{0b1001111, 0b1000000, State::SWITCH_ON_DISABLED},
{0b1101111, 0b0100001, State::READY_TO_SWITCH_ON},
{0b1101111, 0b0100011, State::SWITCHED_ON},
{0b1101111, 0b0100111, State::OPERATION_ENABLED},
{0b1101111, 0b0000111, State::QUICK_STOP_ACTIVE},
{0b1001111, 0b0001111, State::FAULT_REACTION_ACTIVE},
{0b1001111, 0b0001000, State::FAULT}};

struct ControlPattern {
uint16_t mask;
uint16_t value;
Operation op;
};

static constexpr ControlPattern control_patterns[] = {
// https://doc.synapticon.com/circulo/system_integration/status_and_controlword.html
// mask value operation
{0b10000111, 0b00000110, Operation::SHUTDOWN},
{0b10001111, 0b00000111, Operation::SWITCH_ON},
{0b10000010, 0b00000000, Operation::DISABLE_VOLTAGE},
{0b10000110, 0b00000010, Operation::QUICK_STOP},
{0b10001111, 0b00000111, Operation::DISABLE_OPERATION},
{0b10001111, 0b00001111, Operation::ENABLE_OPERATION},
{0b10000000, 0b10000000, Operation::FAULT_RESET}};
};
Loading
Loading