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,
|
| 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 =
|
| 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,
|
| 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(
|
| 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,
|
| 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,
|
| 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,
|
| 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\
|
| 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 = `
|