Fixing + new features on motors scan and ping

#1
by CarolinePascal HF Staff - opened
reachy_mini_testbench/main.py CHANGED
@@ -42,7 +42,7 @@ psutil.cpu_percent(interval=None)
42
  from rustypot import Xl330PyController
43
  from reachy_mini.daemon.utils import find_serial_port
44
  from reachy_mini.utils.hardware_config.parser import parse_yaml_config
45
- from reachy_mini.tools.setup_motor import setup_motor, check_configuration
46
  from importlib.resources import files
47
  import reachy_mini as reachy_mini_package
48
 
@@ -947,7 +947,7 @@ async def scan_motors(baudrate: int = 1000000):
947
  raise HTTPException(503, "No serial port detected")
948
 
949
  try:
950
- controller = Xl330PyController(port, baudrate, 0.01)
951
  found_motors = []
952
 
953
  config = get_hardware_config()
@@ -986,7 +986,7 @@ async def scan_baudrates():
986
  raise HTTPException(503, "No serial port detected")
987
 
988
  # Common baudrates used by Dynamixel motors
989
- baudrates = [9600, 57600, 115200, 1000000]
990
 
991
  config = get_hardware_config()
992
  id_to_name = {m.id: name for name, m in config.motors.items()}
@@ -996,7 +996,7 @@ async def scan_baudrates():
996
  for baudrate in baudrates:
997
  controller = None
998
  try:
999
- controller = Xl330PyController(port, baudrate, 0.01)
1000
  found_motors = []
1001
 
1002
  # Scan all possible IDs (0-254)
@@ -1031,10 +1031,11 @@ async def scan_baudrates():
1031
  pass
1032
  # Small delay to ensure port is fully released
1033
  import time
1034
- time.sleep(0.1)
1035
 
1036
  total_motors_found = sum(r["motors_found"] for r in results)
1037
-
 
1038
  return {
1039
  "port": port,
1040
  "variant": variant,
@@ -1053,7 +1054,7 @@ async def lookup_motor(req: MotorLookupRequest):
1053
  raise HTTPException(503, "No serial port detected")
1054
 
1055
  try:
1056
- controller = Xl330PyController(port, req.baudrate, 0.01)
1057
  found = controller.ping(req.motor_id)
1058
 
1059
  config = get_hardware_config()
@@ -1078,7 +1079,7 @@ async def check_all_motors(baudrate: int = 1000000):
1078
  raise HTTPException(503, "No serial port detected")
1079
 
1080
  try:
1081
- controller = Xl330PyController(port, baudrate, 0.01)
1082
  config = get_hardware_config()
1083
 
1084
  results = []
@@ -1183,7 +1184,7 @@ async def control_motor_led(req: MotorLedRequest):
1183
  raise HTTPException(503, "No serial port detected")
1184
 
1185
  try:
1186
- controller = Xl330PyController(port, 1000000, 0.01)
1187
  controller.write_led(req.motor_id, 1 if req.state else 0)
1188
  return {
1189
  "motor_id": req.motor_id,
 
42
  from rustypot import Xl330PyController
43
  from reachy_mini.daemon.utils import find_serial_port
44
  from reachy_mini.utils.hardware_config.parser import parse_yaml_config
45
+ from reachy_mini.tools.setup_motor import setup_motor, check_configuration, SERIAL_TIMEOUT, XL_BAUDRATE_CONV_TABLE, COMMANDS_BITS_LENGTH
46
  from importlib.resources import files
47
  import reachy_mini as reachy_mini_package
48
 
 
947
  raise HTTPException(503, "No serial port detected")
948
 
949
  try:
950
+ controller = Xl330PyController(port, baudrate, SERIAL_TIMEOUT + float(COMMANDS_BITS_LENGTH["Ping"])/baudrate)
951
  found_motors = []
952
 
953
  config = get_hardware_config()
 
986
  raise HTTPException(503, "No serial port detected")
987
 
988
  # Common baudrates used by Dynamixel motors
989
+ baudrates = list(XL_BAUDRATE_CONV_TABLE.keys())
990
 
991
  config = get_hardware_config()
992
  id_to_name = {m.id: name for name, m in config.motors.items()}
 
996
  for baudrate in baudrates:
997
  controller = None
998
  try:
999
+ controller = Xl330PyController(port, baudrate, SERIAL_TIMEOUT + float(COMMANDS_BITS_LENGTH["Ping"])/baudrate)
1000
  found_motors = []
1001
 
1002
  # Scan all possible IDs (0-254)
 
1031
  pass
1032
  # Small delay to ensure port is fully released
1033
  import time
1034
+ time.sleep(SERIAL_TIMEOUT)
1035
 
1036
  total_motors_found = sum(r["motors_found"] for r in results)
1037
+ logger.info(f"Total motors found: {total_motors_found}")
1038
+ logger.info(f"Results: {results}")
1039
  return {
1040
  "port": port,
1041
  "variant": variant,
 
1054
  raise HTTPException(503, "No serial port detected")
1055
 
1056
  try:
1057
+ controller = Xl330PyController(port, req.baudrate, SERIAL_TIMEOUT + float(COMMANDS_BITS_LENGTH["Ping"])/req.baudrate)
1058
  found = controller.ping(req.motor_id)
1059
 
1060
  config = get_hardware_config()
 
1079
  raise HTTPException(503, "No serial port detected")
1080
 
1081
  try:
1082
+ controller = Xl330PyController(port, baudrate, SERIAL_TIMEOUT + float(COMMANDS_BITS_LENGTH["Ping"])/baudrate)
1083
  config = get_hardware_config()
1084
 
1085
  results = []
 
1184
  raise HTTPException(503, "No serial port detected")
1185
 
1186
  try:
1187
+ controller = Xl330PyController(port, 1000000, SERIAL_TIMEOUT + float(COMMANDS_BITS_LENGTH["Write"])/1000000)
1188
  controller.write_led(req.motor_id, 1 if req.state else 0)
1189
  return {
1190
  "motor_id": req.motor_id,
reachy_mini_testbench/static/index.html CHANGED
@@ -246,9 +246,6 @@
246
 
247
  <div class="control-group">
248
  <h3>Motor Reflash</h3>
249
- <p class="panel-description warning-text">
250
- <strong>⚠️ Warning:</strong> Connect ONLY ONE motor before reflashing to avoid conflicts!
251
- </p>
252
  <div class="reflash-controls">
253
  <div class="reflash-input-row">
254
  <label>From ID:
 
246
 
247
  <div class="control-group">
248
  <h3>Motor Reflash</h3>
 
 
 
249
  <div class="reflash-controls">
250
  <div class="reflash-input-row">
251
  <label>From ID:
reachy_mini_testbench/static/main.js CHANGED
@@ -618,6 +618,27 @@ async function lookupMotor() {
618
  resultSpan.textContent = `Found: ${data.name}`;
619
  resultSpan.className = 'lookup-result found';
620
  showToast(`Motor ${motorId} (${data.name}) responding`, 'success');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
  } else {
622
  resultSpan.textContent = 'Not found';
623
  resultSpan.className = 'lookup-result not-found';
@@ -740,7 +761,7 @@ async function reflashMotor() {
740
  }
741
 
742
  // Confirm before reflashing
743
- if (!confirm(`⚠️ CONFIRM REFLASH\n\nThis will reconfigure the motor:\n\nFrom: ID ${fromId} @ ${fromBaudrate}\nTo: ${targetPreset}\n\nMake sure ONLY ONE motor is connected!\n\nProceed?`)) {
744
  return;
745
  }
746
 
@@ -771,6 +792,69 @@ async function reflashMotor() {
771
  </div>
772
  `;
773
  showToast('Motor successfully reflashed!', 'success');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
774
  } else if (data.success && !data.verified) {
775
  resultDiv.className = 'reflash-result warning';
776
  resultDiv.innerHTML = `
 
618
  resultSpan.textContent = `Found: ${data.name}`;
619
  resultSpan.className = 'lookup-result found';
620
  showToast(`Motor ${motorId} (${data.name}) responding`, 'success');
621
+
622
+ // Blink LED multiple times for 2 seconds
623
+ try {
624
+ const blinkCount = 10; // 10 blinks over 2 seconds
625
+ for (let i = 0; i < blinkCount; i++) {
626
+ // Turn LED on
627
+ await apiCall('/api/motors/led', {
628
+ method: 'POST',
629
+ body: JSON.stringify({ motor_id: motorId, state: true })
630
+ });
631
+ await new Promise(resolve => setTimeout(resolve, 100));
632
+ // Turn LED off
633
+ await apiCall('/api/motors/led', {
634
+ method: 'POST',
635
+ body: JSON.stringify({ motor_id: motorId, state: false })
636
+ });
637
+ await new Promise(resolve => setTimeout(resolve, 100));
638
+ }
639
+ } catch (error) {
640
+ console.error('LED control failed:', error);
641
+ }
642
  } else {
643
  resultSpan.textContent = 'Not found';
644
  resultSpan.className = 'lookup-result not-found';
 
761
  }
762
 
763
  // Confirm before reflashing
764
+ if (!confirm(`⚠️ CONFIRM REFLASH\n\nThis will reconfigure the motor:\n\nFrom: ID ${fromId} @ ${fromBaudrate}\nTo: ${targetPreset}\n\nProceed?`)) {
765
  return;
766
  }
767
 
 
792
  </div>
793
  `;
794
  showToast('Motor successfully reflashed!', 'success');
795
+
796
+ // AUTO-REFRESH: Update baudrate scanner display
797
+ const baudrateResults = document.getElementById('baudrate-results');
798
+ if (baudrateResults && !baudrateResults.classList.contains('hidden')) {
799
+ const listDiv = document.getElementById('baudrate-list');
800
+ if (!listDiv) return;
801
+
802
+ const baudrateItems = listDiv.querySelectorAll('.baudrate-item');
803
+
804
+ // Remove motor from old location and add to new location
805
+ baudrateItems.forEach(item => {
806
+ const baudrateValue = item.querySelector('.baudrate-value');
807
+ const baudrate = parseInt(baudrateValue.textContent.trim());
808
+
809
+ if (baudrate === data.from.baudrate) {
810
+ // Remove motor from old baudrate
811
+ const motorsDiv = item.querySelector('.baudrate-motors');
812
+ const motorItems = motorsDiv.querySelectorAll('.baudrate-motor-item');
813
+ motorItems.forEach(motorItem => {
814
+ const motorId = motorItem.querySelector('.baudrate-motor-id');
815
+ if (motorId && motorId.textContent.includes(`ID ${data.from.id}`)) {
816
+ motorItem.remove();
817
+ }
818
+ });
819
+
820
+ // Update count and status
821
+ const remaining = motorsDiv.querySelectorAll('.baudrate-motor-item').length;
822
+ if (remaining === 0) {
823
+ item.className = 'baudrate-item no-motors';
824
+ motorsDiv.innerHTML = '<span class="no-motors-text">No motors found</span>';
825
+ item.querySelector('.baudrate-count').textContent = '0 motor(s)';
826
+ } else {
827
+ item.querySelector('.baudrate-count').textContent = `${remaining} motor(s)`;
828
+ }
829
+ }
830
+
831
+ if (baudrate === data.to.baudrate) {
832
+ // Add motor to new baudrate
833
+ const motorsDiv = item.querySelector('.baudrate-motors');
834
+ const existingMotor = Array.from(motorsDiv.querySelectorAll('.baudrate-motor-id'))
835
+ .find(el => el.textContent.includes(`ID ${data.to.id}`));
836
+
837
+ if (!existingMotor) {
838
+ const noMotorsText = motorsDiv.querySelector('.no-motors-text');
839
+ if (noMotorsText) {
840
+ motorsDiv.innerHTML = '';
841
+ }
842
+
843
+ const motorItem = document.createElement('div');
844
+ motorItem.className = 'baudrate-motor-item';
845
+ motorItem.innerHTML = `
846
+ <span class="baudrate-motor-id" title="${data.to.name}">ID ${data.to.id}</span>
847
+ <button class="btn-mini btn-warning" onclick="prefillReflash(${data.to.id}, ${data.to.baudrate})">Reflash</button>
848
+ `;
849
+ motorsDiv.appendChild(motorItem);
850
+
851
+ item.className = 'baudrate-item has-motors';
852
+ const count = motorsDiv.querySelectorAll('.baudrate-motor-item').length;
853
+ item.querySelector('.baudrate-count').textContent = `${count} motor(s)`;
854
+ }
855
+ }
856
+ });
857
+ }
858
  } else if (data.success && !data.verified) {
859
  resultDiv.className = 'reflash-result warning';
860
  resultDiv.innerHTML = `