MovementDefinition详解
概述 / Overview
`MovementDefinition` 是 Clumsy 中描述 一个运动行为的抽象基类。每个 Movement 是一段 可迭代的控制循环,由 `DriveTask` 宿主按 DriveTaskInterval(默认 20 ms)周期推进。它是 Clumsy 自动驾驶的 基本运动语汇。
`MovementDefinition` is the Clumsy abstract base that describes a single motion behaviour. Each Movement is an iterable control loop, ticked by `DriveTask` at `DriveTaskInterval` (default 20 ms). It's Clumsy's basic motion vocabulary.
定义:`D:\src\Clumsy\ClumsyCore\Pilot\MovementDefinition.cs:41-48`。 Definition: `D:\src\Clumsy\ClumsyCore\Pilot\MovementDefinition.cs:41-48`.
契约 / Contract
public abstract class MovementDefinition
{
public abstract IEnumerable<bool> Get();
}
public abstract class MovementDefinition<T> : MovementDefinition
{
public new abstract IEnumerable<T> Get();
}
`Get()` 返回一个 可迭代;框架每次调 `MoveNext()` 推一格。
`Get()` returns an enumerable; the framework calls `MoveNext()` once per tick.
- 每次 `yield return true` = "继续 tick" / "keep ticking"
- 迭代器耗尽(不再 `yield`) = "Movement 完成" / "Movement done"
生命周期 / Lifecycle
你的代码 / Your code:
DriveTask.WaitDriveTask(new MyMovement{...}.Get());
│
▼
DriveTask 内部 / Inside DriveTask (D:\src\Clumsy\ClumsyCore\DriveTask.cs:104-144):
new Thread + AboveNormal priority
│
▼ 循环 / loop:
enumerator.MoveNext()
Thread.Sleep(Configuration.conf.DriveTaskInterval) // 20 ms
...
│
▼ 完成 / done (MoveNext returns false):
stopped_reason = 0
Monitor.PulseAll(wait)
│
▼ 异常 / on exception:
stopped_reason = -1
capture & rethrow on Wait()
调用方 `WaitDriveTask` 在 `Monitor.Wait(wait)` 上阻塞直到完成。 Caller `WaitDriveTask` blocks on `Monitor.Wait(wait)` until completion.
最小 Movement / Minimal Movement
using ClumsyCore.Pilot;
using ClumsyCore.DrivePlans;
using System.Collections.Generic;
public class MyStraight : MovementDefinition
{
public float dx, dy; // displacement
public float basespeed = 600; // mm/s
public override IEnumerable<bool> Get()
{
var sx = currentX; // pose at start
var sy = currentY;
var driver = new SteeringLineFollowing
{
srcX = sx, srcY = sy,
dstX = sx + dx, dstY = sy + dy,
basespeed = basespeed
};
foreach (var t in driver.Follow()) yield return t;
}
}
[MovementTest] 属性 / Attribute
目标 / Target: 类 / class
效果 / Effect: 在 Clumsy 测试 UI 注册一个 测试按钮;按下立即在车上跑这个 Movement(车应静止 / 在仿真)。 Registers a test button in the Clumsy UI; pressing it runs the Movement on a stationary (or simulated) vehicle.
[MovementTest(name = "测试我的直线 / Test my line")]
public class MyStraight : MovementDefinition { ... }
可用原语 / Primitive library
Movement 通常 组合下层原语而不是从零写控制律。下面是仓库里现成的: Movements usually compose lower-level primitives rather than implement control law from scratch.
ClumsyCore/Legacy/(几何跟踪基础)
| Primitive | 用途 / Use |
|---|---|
| `SteeringLineFollowing` | 阿克曼正向纯跟踪 |
| `SteeringLineFollowingReverse` | 阿克曼反向跟踪 |
| `OmniWheelTracking` | 全向轮速跟踪 |
| `LeftRightWheelTracking` | 差速轮速跟踪 |
| `OmniBezierTracking` / `LeftRightBezierTracking` | Bezier 曲线跟踪 |
| `TrajectoryReplanningObstacleAvoidance` | 动态避障 |
ClumsyDance/Movements/(行为聚合)
| Movement | 用途 / Use |
|---|---|
| `SlotFollowing` | 槽位跟踪(叉车叉孔 / KIVA 腿对) |
| `ReflexDock` | 反射式对接 |
| `KivaFollowShelfOfManyLegs` | 多腿料架导航 |
| `TopHatFollowing` | 礼帽形特征跟踪 |
| `BezierMPC` | Bezier 参考的 MPC |
| `LRWheelOdometryCalculator` | 编码器积分助手 |
ClumsyDance/Detectors/(感知)
| Detector | 用途 |
|---|---|
| `LidarDetectTray` | 通用托盘检测(2 腿 / 3 腿可调) |
| `Lidar2dDetect2LegTray`, `Lidar2dDetect3LegTray`, `Lidar3dDetectTray` | 特化变体 |
| `Cam3dDetectTray` | 3D 相机检测 |
MovementDefinition<T> 泛型变体 / Generic variant
当 Movement 需要 返回一个对象(如已配置好的子 Movement): When a Movement needs to return a typed handle (e.g. a pre-configured sub-Movement):
public class BuildFollower : MovementDefinition<SteeringLineFollowing>
{
public float srcX, srcY, dstX, dstY;
public override IEnumerable<SteeringLineFollowing> Get()
{
yield return new SteeringLineFollowing
{
srcX = srcX, srcY = srcY,
dstX = dstX, dstY = dstY,
basespeed = 800
};
}
}
异常 + 中止 / Exceptions & abort
- `Get()` 内抛异常 → `DriveTask.Wait()` 返回时 重新抛出。
- `PilotBase.SecurityCheck()` 在没收到控制信号超时(默认 300 ms)时调 `DriveStop()`;当前 Movement 的 enumerator 不会再被推进,但 资源清理责任在 Movement 作者(在 yield 前后包 try/finally)。
- 调用者按 `WaitDriveTask(...)` 同步等;要异步用 `DriveTask.WaitDriveTask` 包在另一线程或 `SimpleAgvInterface.Queue`。
Exceptions from `Get()` re-throw when `WaitDriveTask` returns. The watchdog (`SecurityCheck`, default 300 ms timeout) calls `DriveStop()`; the enumerator stops being advanced but Movement-owned cleanup is on the author (wrap in try/finally).
与 Pilot 的关系 / Relation to Pilot
- `PilotBase` 是 Movement 的 宿主进程上下文(提供 pose、IDriveWriter 等)。
- `PilotDefinition<T>` 加 typed config。
- 95% 的车型定制不用碰 PilotBase —— 写 Movement 就够。
`PilotBase` is Movement's host context (pose, drive writers); `PilotDefinition<T>` adds typed config. 95% of customisation only needs Movements.
常见错误 / Gotchas
- 在 `Get()` 用 `Thread.Sleep` → 阻塞 DriveTask 线程,定时器漂移。正确方式:`yield return` 多次。
Don't `Thread.Sleep` inside `Get()`; just `yield return` multiple times.
- Movement 跨实例共享状态 → Movement 应是 可重入的;不要用 static 字段保存进度。
Movements should be reentrant; don't use static fields for progress.
- 忘了在不需要继续时 `yield break` → 看起来在原地踏步。
- 构造时读 pose → 应在 `Get()` 第一帧读,避免构造与执行间车移动了。
Read pose inside `Get()`, not in the ctor — otherwise the car may have moved.