fix ROS profiles and simulator reliability
This commit is contained in:
+6
-1
@@ -105,7 +105,12 @@ WebSocket и компоновки интерфейса.
|
||||
`/RMC2/camera_bottom/aruco_id` (и старый `/RMC2/aruco_id` для Webots), а также
|
||||
`/RMC2/lift_status`. По документации реального RMC2 статус лифта имеет QoS
|
||||
`Transient Local` и значения `lowered`, `moving`, `raised`; команды — `0.0` вниз
|
||||
и `0.05` м вверх. Мост настроен именно так.
|
||||
и `0.05` м вверх. В Webots верхнее положение — `0.1` м, поэтому это значение
|
||||
используется по умолчанию. Для физического RMC2 запускайте мост так:
|
||||
|
||||
```bash
|
||||
MVCH_LIFT_UP_HEIGHT=0.05 bun run start
|
||||
```
|
||||
|
||||
Симулятор из открытого пакета не публикует напряжение аккумуляторов. Мост уже
|
||||
подписан на `/RMC*/battery_state` (`sensor_msgs/BatteryState`) и
|
||||
|
||||
@@ -33,7 +33,7 @@ export function isCommand(value: unknown): value is Command {
|
||||
case 'set_goal':
|
||||
return isPose(command.pose)
|
||||
case 'lift':
|
||||
return command.robot === 'RMC2' && (command.height === 0 || command.height === 0.05)
|
||||
return command.robot === 'RMC2' && (command.height === 0 || command.height === 0.05 || command.height === 0.1)
|
||||
case 'gripper':
|
||||
return command.robot === 'RMC1' && (command.state === 'open' || command.state === 'closed')
|
||||
default:
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Small JSON <-> ROS 2 bridge for the module G web interface.
|
||||
|
||||
stdin: one JSON command per line
|
||||
stdout: one JSON state per line (only machine-readable data)
|
||||
stderr: diagnostics
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
@@ -35,6 +28,9 @@ except ImportError:
|
||||
|
||||
ROBOTS = ("RMC1", "RMC2")
|
||||
MANUAL_TIMEOUT = 0.35
|
||||
LIFT_UP_HEIGHT = float(os.environ.get("MVCH_LIFT_UP_HEIGHT", "0.1"))
|
||||
if LIFT_UP_HEIGHT not in (0.05, 0.1):
|
||||
raise SystemExit("MVCH_LIFT_UP_HEIGHT должен быть 0.05 или 0.1")
|
||||
|
||||
|
||||
def empty_robot():
|
||||
@@ -163,7 +159,11 @@ class FmsBridge(Node):
|
||||
self.create_subscription(String, "/RMC2/aruco_id", self.on_aruco, sensor_qos)
|
||||
lift_qos = QoSProfile(
|
||||
reliability=ReliabilityPolicy.RELIABLE,
|
||||
durability=DurabilityPolicy.TRANSIENT_LOCAL,
|
||||
durability=(
|
||||
DurabilityPolicy.TRANSIENT_LOCAL
|
||||
if LIFT_UP_HEIGHT == 0.05
|
||||
else DurabilityPolicy.VOLATILE
|
||||
),
|
||||
history=HistoryPolicy.KEEP_LAST,
|
||||
depth=1,
|
||||
)
|
||||
@@ -349,7 +349,7 @@ class FmsBridge(Node):
|
||||
self.state["robots"][robot]["goal"] = None
|
||||
self.state["robots"][robot]["plan"] = []
|
||||
elif kind == "lift" and robot == "RMC2" and not emergency:
|
||||
height = 0.05 if float(command.get("height", 0)) >= 0.025 else 0.0
|
||||
height = LIFT_UP_HEIGHT if float(command.get("height", 0)) > 0 else 0.0
|
||||
self.lift_publisher.publish(Float64(data=height))
|
||||
with self.lock:
|
||||
self.state["robots"][robot]["liftStatus"] = "command: up" if height else "command: down"
|
||||
|
||||
@@ -5,7 +5,7 @@ import { applyMockCommand, initialState, tickMock } from './state'
|
||||
describe('command validation', () => {
|
||||
test('accepts valid commands', () => {
|
||||
expect(isCommand({ type: 'manual', robot: 'RMC1', x: 0.4, y: 0.2, yaw: 0 })).toBe(true)
|
||||
expect(isCommand({ type: 'lift', robot: 'RMC2', height: 0.05 })).toBe(true)
|
||||
expect(isCommand({ type: 'lift', robot: 'RMC2', height: 0.1 })).toBe(true)
|
||||
expect(isCommand({ type: 'gripper', robot: 'RMC1', state: 'closed' })).toBe(true)
|
||||
})
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('mock state', () => {
|
||||
tickMock(state, 1)
|
||||
expect(state.robots.RMC2.pose.x).toBeCloseTo(0.5)
|
||||
|
||||
applyMockCommand(state, { type: 'lift', robot: 'RMC2', height: 0.05 })
|
||||
applyMockCommand(state, { type: 'lift', robot: 'RMC2', height: 0.1 })
|
||||
applyMockCommand(state, { type: 'emergency', robot: 'RMC2', active: true })
|
||||
expect(state.robots.RMC2.velocity.x).toBe(0)
|
||||
expect(state.robots.RMC2.liftStatus).toBe('up')
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ export default function App() {
|
||||
{selected === 'RMC1' ? (
|
||||
<div className="grid grid-cols-2 gap-2"><button disabled={rover.emergency} onClick={() => send({ type: 'gripper', robot: 'RMC1', state: 'open' })} className="rounded-lg bg-cyan-800 px-3 py-2 disabled:opacity-40">Открыть схват</button><button disabled={rover.emergency} onClick={() => send({ type: 'gripper', robot: 'RMC1', state: 'closed' })} className="rounded-lg bg-cyan-800 px-3 py-2 disabled:opacity-40">Закрыть схват</button></div>
|
||||
) : (
|
||||
<div className="grid grid-cols-2 gap-2"><button disabled={rover.emergency} onClick={() => send({ type: 'lift', robot: 'RMC2', height: 0.05 })} className="rounded-lg bg-amber-800 px-3 py-2 disabled:opacity-40">Поднять лифт</button><button disabled={rover.emergency} onClick={() => send({ type: 'lift', robot: 'RMC2', height: 0 })} className="rounded-lg bg-amber-800 px-3 py-2 disabled:opacity-40">Опустить лифт</button></div>
|
||||
<div className="grid grid-cols-2 gap-2"><button disabled={rover.emergency} onClick={() => send({ type: 'lift', robot: 'RMC2', height: 0.1 })} className="rounded-lg bg-amber-800 px-3 py-2 disabled:opacity-40">Поднять лифт</button><button disabled={rover.emergency} onClick={() => send({ type: 'lift', robot: 'RMC2', height: 0 })} className="rounded-lg bg-amber-800 px-3 py-2 disabled:opacity-40">Опустить лифт</button></div>
|
||||
)}
|
||||
</Card>
|
||||
</aside>
|
||||
|
||||
@@ -45,5 +45,5 @@ export type Command =
|
||||
| { type: 'set_pose'; robot: RobotId; pose: Pose }
|
||||
| { type: 'set_goal'; robot: RobotId; pose: Pose }
|
||||
| { type: 'cancel_goal'; robot: RobotId }
|
||||
| { type: 'lift'; robot: 'RMC2'; height: 0 | 0.05 }
|
||||
| { type: 'lift'; robot: 'RMC2'; height: 0 | 0.05 | 0.1 }
|
||||
| { type: 'gripper'; robot: 'RMC1'; state: 'open' | 'closed' }
|
||||
|
||||
Reference in New Issue
Block a user