Skip to content

Commit 170a7cb

Browse files
committed
Reapply "review: revert heart node, drop moteus_ros2 + cal log, restore reviewed arm headers (PR #187 feedback)"
This reverts commit d5c5b0b.
1 parent 461e525 commit 170a7cb

7 files changed

Lines changed: 357 additions & 277 deletions

File tree

.gitmodules

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
[submodule "src/external_pkgs/moteus"]
88
path = src/external_pkgs/moteus
99
url = https://github.com/mjbots/moteus.git
10-
[submodule "src/external_pkgs/moteus_ros2"]
11-
path = src/external_pkgs/moteus_ros2
12-
url = https://github.com/mjbots/moteus_ros2.git
1310
[submodule "src/external_pkgs/ffmpeg_image_transport"]
1411
path = src/external_pkgs/ffmpeg_image_transport
1512
url = https://github.com/ros-misc-utilities/ffmpeg_image_transport.git

moteus-cal-AD8ANVQwUBggOTNO-20260804T014146.313271.log

Lines changed: 0 additions & 35 deletions
This file was deleted.

src/external_pkgs/moteus_ros2

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/rover_arm_common/include/rover_arm_common/motor_addressing.h

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,18 @@ struct JointMap {
7575
double direction; // +1 or -1: sign between output revolutions and URDF angle
7676
};
7777

78-
//!? this is wrong?? where is A4?
79-
// Should only be needed for IK/rviz. Leave be for now.
78+
// urdf joint names only needed for IK/rviz. The URDF still describes the old
79+
// 6-joint arm (a4_rotation..a6_rotation) — a5_differential/a6_differential/
80+
// ee_prismatic don't exist there yet.
8081
static const JointMap ARM_JOINTS[NUM_MOTORS] = {
8182
// id hardware label urdf joint name boot angle (rad) direction
82-
{ 1, "Base", "shoulder_joint", -1.57, -1.0 },
83-
{ 2, "Shoulder", "link_1_joint", -1.57, -1.0 },
84-
{ 3, "Elbow", "link1_link2", 0.9, -1.0 },
85-
{ 4, "BALLS", "MEOWMEOW", 0.9, -1.0 }, // IDK
86-
{ 5, "Wrist Pitch", "a4_rotation", 0.0, -1.0 },
87-
{ 6, "Wrist Roll", "a5_rotation", 1.2, -1.0 },
88-
{ 7, "End Effector", "a6_rotation", 0.0, -1.0 },
83+
{ 1, "A1", "shoulder_joint", -1.57, -1.0 },
84+
{ 2, "A2", "link_1_joint", -1.57, -1.0 },
85+
{ 3, "A3", "link1_link2", 0.9, -1.0 },
86+
{ 4, "A4", "a4_rotation", 0.9, -1.0 },
87+
{ 5, "A5", "a5_differential", 0.0, -1.0 },
88+
{ 6, "A6", "a6_differential", 1.2, -1.0 },
89+
{ 7, "EE", "ee_prismatic", 0.0, -1.0 },
8990
};
9091

9192

src/rover_arm_common/include/rover_arm_common/motor_config.h

Lines changed: 90 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "motor_addressing.h" // NUM_MOTORS
44

55
#include <cmath>
6+
#include <cstdint>
67
#include <limits>
78
#include <string>
89
#include <vector>
@@ -63,6 +64,14 @@ struct MotorConfig {
6364
// Stored here so the HMI can display it in the Motor Config panel.
6465
float gear_reduction = 1.0f;
6566

67+
// Datasheet identity (Maxon). poles/kv feed calibration flags — these are
68+
// PER MOTOR: calibrating with the wrong pole count mis-commutates the
69+
// motor (low torque at max current, siren whine, angle-dependent gains).
70+
int poles = 16; // pole count (pole pairs x 2)
71+
float kv = 265.0f;
72+
float nominal_current_A = 1.0f; // continuous thermal rating
73+
const char* part_no = "?";
74+
6675
// -------------------------------------------------------------------------
6776
// Helpers used by configureMotor()
6877
// -------------------------------------------------------------------------
@@ -99,14 +108,18 @@ struct MotorConfig {
99108
inline std::vector<MotorConfig> get_arm_configuration() {
100109
std::vector<MotorConfig> motors(NUM_MOTORS);
101110

111+
// --- Synced from controller flash 2026-08-02 (conf get readback) so the
112+
// repo matches the bench tuning. A7/EE was not replying; it keeps
113+
// struct defaults until it can be read.
114+
102115
// --- PID gains (hand-tuned per axis) ---
103116
// Higher kp = stiffer. Add kd to dampen oscillation.
104-
motors[0].kp = 180.0f; motors[0].kd = 40.0f; // Base
105-
motors[1].kp = 2100.0f; motors[1].kd = 100.0f; // Shoulder
106-
motors[2].kp = 4000.0f; motors[2].kd = 750.0f; // Elbow
107-
motors[3].kp = 50.0f; motors[3].kd = 0.0f; // Wrist Pitch
108-
motors[4].kp = 600.0f; motors[4].kd = 100.0f; // Wrist Roll
109-
motors[5].kp = 600.0f; motors[5].kd = 100.0f; // End Effector
117+
motors[0].kp = 4000.0f; motors[0].kd = 600.0f; // A1
118+
motors[1].kp = 30000.0f; motors[1].kd = 6000.0f; // A2
119+
motors[2].kp = 40000.0f; motors[2].kd = 2000.0f; // A3
120+
motors[3].kp = 550.0f; motors[3].kd = 10.0f; // A4
121+
motors[4].kp = 17000.0f; motors[4].kd = 3500.0f; // A5
122+
motors[5].kp = 17000.0f; motors[5].kd = 3500.0f; // A6
110123

111124
// --- Gear reductions (for display in HMI only — firmware holds the real value) ---
112125
motors[0].gear_reduction = 1.0f / 190.0f;
@@ -115,23 +128,56 @@ inline std::vector<MotorConfig> get_arm_configuration() {
115128
motors[3].gear_reduction = 1.0f / 190.0f;
116129
motors[4].gear_reduction = 1.0f / 66.0f;
117130
motors[5].gear_reduction = 1.0f / 66.0f;
131+
motors[6].gear_reduction = 1.0f / 190.0f; // EE
132+
133+
// --- Datasheet identity (bench notes / Maxon, 2026-08) ---
134+
// A2/A3 are 22-pole kv=134 — calibration flags MUST come from here, not
135+
// the old hardcoded 16/265 (that mis-commutates them).
136+
const char* parts[NUM_MOTORS] = {"339281","607942","515458","339281","651607","651607","339281"};
137+
const int poles[NUM_MOTORS] = { 16, 22, 22, 16, 16, 16, 16 };
138+
const float kvs [NUM_MOTORS] = { 265, 134, 134, 187, 265, 265, 265 };
139+
const float noms [NUM_MOTORS] = { 1.01f, 9.5f, 6.44f, 1.01f, 2.52f, 2.52f, 1.01f };
140+
for (int i = 0; i < NUM_MOTORS; i++) {
141+
motors[i].part_no = parts[i];
142+
motors[i].poles = poles[i];
143+
motors[i].kv = kvs[i];
144+
motors[i].nominal_current_A = noms[i];
145+
}
118146

119147
// --- Current limits (A) ---
120-
motors[0].max_current_A = 1.0f;
121-
motors[1].max_current_A = 8.0f;
122-
motors[2].max_current_A = 5.5f;
123-
motors[3].max_current_A = 0.5f;
124-
motors[4].max_current_A = 2.5f;
125-
motors[5].max_current_A = 2.5f;
126-
127-
// --- Software position limits (output-shaft revolutions) ---
128-
motors[0].position_min = -0.3f; motors[0].position_max = 0.3f;
129-
motors[1].position_min = -0.47f; motors[1].position_max = -0.05f;
130-
motors[2].position_min = 0.01f; motors[2].position_max = 0.4f;
131-
// motors[3]: uses default (-1.0, 1.0)
132-
//TODO this is positions for motors, not motors
133-
motors[4].position_min = -999.01f; motors[4].position_max = 999.4f; // continuous
134-
motors[5].position_min = -999.01f; motors[5].position_max = 999.4f; // continuous
148+
motors[0].max_current_A = 2.0f; // back to 2.0 (2026-08-03); was 3.0 after the 08-02 stall
149+
motors[1].max_current_A = 14.0f;
150+
motors[2].max_current_A = 14.0f;
151+
motors[3].max_current_A = 1.01f; // 200142 nominal 2.14 A; 0.3 starved it (bench 2026-08-03)
152+
motors[4].max_current_A = 2.0f;
153+
motors[5].max_current_A = 2.0f;
154+
155+
// --- Voltage / power limits ---
156+
const float fnan = std::numeric_limits<float>::quiet_NaN();
157+
motors[0].max_voltage = 30.0f; motors[0].max_power_W = 200.0f;
158+
motors[1].max_voltage = 30.0f; motors[1].max_power_W = fnan;
159+
motors[2].max_voltage = 32.0f; motors[2].max_power_W = 250.0f;
160+
motors[3].max_voltage = 24.0f; motors[3].max_power_W = 30.0f;
161+
motors[4].max_voltage = 30.0f; motors[4].max_power_W = fnan;
162+
motors[5].max_voltage = 30.0f; motors[5].max_power_W = fnan;
163+
164+
// --- Motion profile ---
165+
motors[0].max_acceleration = 0.5f; motors[0].max_velocity = 0.01f;
166+
motors[1].max_acceleration = 0.5f; motors[1].max_velocity = 0.03f;
167+
motors[2].max_acceleration = 0.5f; motors[2].max_velocity = 0.05f;
168+
motors[3].max_acceleration = 0.5f; motors[3].max_velocity = 0.10f;
169+
motors[4].max_acceleration = 1.0f; motors[4].max_velocity = 0.05f;
170+
motors[5].max_acceleration = 1.0f; motors[5].max_velocity = 0.05f;
171+
172+
// --- Position limits (output-shaft revolutions) ---
173+
// Flash has servopos unbounded (nan) everywhere except A4 — adopted
174+
// as-is; homing/soft-stop bounds live in AxisConfig, not here.
175+
for (int i : {0, 1, 2, 4, 5}) {
176+
motors[i].position_min = fnan;
177+
motors[i].position_max = fnan;
178+
}
179+
motors[3].position_min = -0.5f; motors[3].position_max = 0.5f;
180+
motors[3].def_timeout = 0.1f;
135181

136182
return motors;
137183
}
@@ -176,7 +222,7 @@ namespace AxisConfig {
176222
/*AXIS 1 */ 0.1,
177223
/*AXIS 2 */ 0.1,
178224
/*AXIS 3 */ 0.1,
179-
/*AXIS 4 */ 0.1, // DISABLED
225+
/*AXIS 4 */ 0.1,
180226
/*AXIS 5 */ 0.1,
181227
/*AXIS 6 */ 0.1,
182228
/*AXIS EE */ 0.5 };
@@ -190,14 +236,33 @@ namespace AxisConfig {
190236
/*AXIS 6 */ 0.5,
191237
/*AXIS EE */ 0.5 };
192238

193-
float idle_position[NUM_AXES] = { // Rev/s Direction Independent.
239+
float idle_position[NUM_AXES] = { // Rev/s Direction Independent.
194240
/*AXIS 1 */ 0.1,
195241
/*AXIS 2 */ 0.1,
196242
/*AXIS 3 */ 0.1,
197243
/*AXIS 4 */ 0.1,
198244
/*AXIS 5 */ -0.1,
199245
/*AXIS 6 */ 0.1,
200246
/*AXIS EE */ 0.1 };
201-
202247

203-
};
248+
// Limit switches — AUX2 digital input, see setup_limit_switches.py.
249+
// mask: aux2 GPIO status bitfield, bit0 = aux2.pins.0 (ABS connector pin 2).
250+
// inverted=false: NC switch to GND (hard 2k pullup) -> pin HIGH = pressed.
251+
bool has_limit_switch[NUM_AXES] = {
252+
/*AXIS 1 */ true,
253+
/*AXIS 2 */ true,
254+
/*AXIS 3 */ true,
255+
/*AXIS 4 */ true,
256+
/*AXIS 5 */ false,
257+
/*AXIS 6 */ false,
258+
/*AXIS EE */ false };
259+
260+
// Per-axis, measured with DEBUG_LIMIT_SWITCH_RAW_REPLY (2026-08-02).
261+
// All four: pressed = HIGH; only the pin differs (A1/A3 pin 0, A2/A4 pin 1).
262+
// A1's first capture was misread as inverted — the arm was resting on its
263+
// switch, so the observed transitions were releases, not presses.
264+
// A3's pin has no pull; if it flakes: conf set aux2.pins.0.pull 2 on motor 3.
265+
uint8_t limit_switch_mask[NUM_AXES] = {1, 2, 1, 2, 0, 0, 0};
266+
bool limit_switch_inverted[NUM_AXES] = {false, false, false, false, false, false, false};
267+
268+
};
Lines changed: 99 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,105 @@
1-
//* Per-computer supervisor: starts/stops the subsystems listed in heart.yaml and
2-
//* reports their OBSERVED state (reaped via waitpid, never a stored belief).
1+
//* Meant to be like, the heart of the rover, each subsystem can be thought of as a muscle or organ. the heart can start/shutoff the flow of blood to each subsystem
2+
//* One of these will run on each computer, and run/manage all nodes on that device
3+
34
#include <rclcpp/rclcpp.hpp>
45
#include <rover_utils/include/roverCommon.h>
5-
#include <rover_msgs/msg/subsystem_command.hpp>
6-
#include <rover_msgs/msg/heart_status.hpp>
7-
#include <chrono>
8-
#include <sys/types.h>
9-
6+
#include <rover_msgs/msg/heart_request.hpp>
7+
using namespace ConsoleFormat;
108
class HeartNode : public rclcpp::Node
119
{
1210
public:
13-
HeartNode();
14-
15-
private:
16-
using State = rover_msgs::msg::SubsystemState;
17-
struct SubSystem {
18-
std::string name;
19-
std::string exec_command;
20-
pid_t pid = -1; // == pgid/sid (child calls setsid)
21-
uint8_t state = State::STOPPED;
22-
int exit_code = 0;
23-
bool pending_restart = false;
24-
int stop_stage = 0; // 1=SIGINT sent, 2=SIGTERM, 3=SIGKILL
25-
std::chrono::steady_clock::time_point started_at, escalate_at;
26-
};
27-
std::map<std::string, SubSystem> subsystems; // ordered -> stable msg order
28-
std::string my_host_id;
29-
30-
void startSubsystem(SubSystem& s);
31-
void stopSubsystem(SubSystem& s);
32-
void superviseTick(); // 250 ms: reap children, escalate stops
33-
void publishStatus();
34-
void commandCallback(rover_msgs::msg::SubsystemCommand::SharedPtr cmd);
35-
void signalGroup(pid_t pgid, int sig);
36-
37-
rclcpp::TimerBase::SharedPtr heartbeat_timer, supervise_timer;
38-
rclcpp::Subscription<rover_msgs::msg::SubsystemCommand>::SharedPtr command_sub;
39-
rclcpp::Publisher<rover_msgs::msg::HeartStatus>::SharedPtr status_pub;
11+
HeartNode() : Node("broken_heart", rclcpp::NodeOptions().allow_undeclared_parameters(true).automatically_declare_parameters_from_overrides(true))
12+
{
13+
std::string my_name = this->get_name();
14+
15+
if(my_name == "broken_heart"){
16+
RCLCPP_ERROR(this->get_logger(), "Sorry, you need to launch me with a different name (use a launch file with name= ). I'm going to get confused otherwise.");
17+
rclcpp::shutdown();
18+
}
19+
rclcpp::Parameter topic_param;
20+
rclcpp::Parameter feedback_topic_param;
21+
rclcpp::Parameter heartbeat_rate_param;
22+
this->get_parameter("request_topic", topic_param);
23+
this->get_parameter("heart_feedback_topic", feedback_topic_param);
24+
this->get_parameter("heartbeat_rate", heartbeat_rate_param);
25+
26+
// std::string request_topic = my_name + "/request";
27+
my_host_id = my_name;
28+
my_host_id.erase(0, 6);
29+
std::string request_topic = topic_param.value_to_string();
30+
std::string feedback_topic = feedback_topic_param.value_to_string();
31+
this->get_parameters("subsystems", params);
32+
33+
34+
RCLCPP_INFO(this->get_logger(), "I am %s, on topic %s", this->get_name(), request_topic.c_str());
35+
36+
//* Construct Subsystems from Params
37+
{ //just making a little scope for i, sure theres a better way
38+
int i = 0;
39+
for (const auto& [key, param] : params) {
40+
RCLCPP_INFO(this->get_logger(), "Got Subystem %s%s%s = %s%s%s",
41+
green(), key.c_str(), reset(), bright_blue(), param.value_to_string().c_str(), reset());
42+
SubSystem new_subsys;
43+
new_subsys.name = key;
44+
new_subsys.exec_command = param.value_to_string();
45+
new_subsys.index = i;
46+
subsystems_vector.push_back(new_subsys);
47+
subsystems[key] = new_subsys;
48+
i++;
49+
}
50+
if(i == 0){
51+
RCLCPP_WARN(this->get_logger(), "Warning, no subsystems defined. You sure you loaded parameters right? I'm useless without params, exiting.");
52+
rclcpp::shutdown();
53+
}
54+
}
55+
heart_request_sub = this->create_subscription<rover_msgs::msg::HeartRequest>(
56+
request_topic, 10, std::bind(&HeartNode::heartRequestCallback, this, std::placeholders::_1));
57+
heart_feedback_pub = this->create_publisher<rover_msgs::msg::HeartRequest>(
58+
feedback_topic, 20);
59+
60+
double heartbeat_rate = heartbeat_rate_param.as_double();
61+
double heartbeat_period = 1.0/heartbeat_rate;
62+
63+
heartbeat_timer = this->create_wall_timer(
64+
std::chrono::duration<double>(heartbeat_period), std::bind(&HeartNode::heartbeat, this));
65+
66+
67+
// runSubSystem(subsystems_vector[0]);
68+
}
69+
private:
70+
struct SubSystem{
71+
std::string name;
72+
std::string exec_command;
73+
int index = -1;
74+
pid_t pid;
75+
pid_t gpid;
76+
pid_t sid;
77+
bool online = false;
78+
};
79+
std::map<std::string, rclcpp::Parameter> params;
80+
std::unordered_map<std::string, SubSystem> subsystems;
81+
82+
// std::unordered_map<std::string, std::SubSystem>;
83+
84+
std::string my_host_id;
85+
86+
std::vector<SubSystem> subsystems_vector;
87+
88+
void runSubSystem(SubSystem& subsys);
89+
void killSubSystem(SubSystem& subsys);
90+
void heartbeat();
91+
92+
// void runChildNode(std::string pkg, std::string node_or_launch_file, std::string subsytem_name, int type = NODE, bool kill_orphan = true);
93+
void killProcessGroup(pid_t pgid); //! Should prob have a error return
94+
95+
rclcpp::TimerBase::SharedPtr heartbeat_timer;
96+
97+
rclcpp::Subscription<rover_msgs::msg::HeartRequest>::SharedPtr heart_request_sub;
98+
void heartRequestCallback(const rover_msgs::msg::HeartRequest::SharedPtr request);
99+
100+
rclcpp::Publisher<rover_msgs::msg::HeartRequest>::SharedPtr heart_feedback_pub;
40101
};
102+
103+
104+
105+

0 commit comments

Comments
 (0)