using Flyshot.Core.Domain;
using Flyshot.Core.Planning;
using Microsoft.Extensions.Logging;
namespace Flyshot.Core.Triggering;
///
/// 根据规划轨迹和飞拍配置生成触发时间轴,把示教点上的 shot_flags / offset_values / addr
/// 映射成带理论时间和离散化时间的 ShotEvent,以及可直接注入伺服流的 TrajectoryDoEvent。
///
public sealed class ShotTimelineBuilder
{
private readonly WaypointTimestampResolver _resolver;
private readonly ILogger? _logger;
///
/// 初始化 ShotTimelineBuilder,依赖一个时间戳解析器来对齐补中点后的轨迹与原始示教点。
///
/// 时间戳解析器。
/// 日志记录器;允许 null。
public ShotTimelineBuilder(WaypointTimestampResolver resolver, ILogger? logger = null)
{
_resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
_logger = logger;
}
///
/// 为给定轨迹构建完整的触发时间轴。
///
/// 规划后的轨迹(含补中点信息和机器人配置)。
/// IO 保持周期数(对应原系统的 io_keep_cycles)。
/// 稠密采样周期,用于离散化 sample_index 和 sample_time。
/// 是否生成可注入伺服流的 DO 事件。
/// 包含 ShotEvent 和 TrajectoryDoEvent 的触发时间轴。
public ShotTimeline Build(PlannedTrajectory trajectory, int holdCycles, TimeSpan samplePeriod, bool useDo = true)
{
ArgumentNullException.ThrowIfNull(trajectory);
if (holdCycles < 0)
{
throw new ArgumentOutOfRangeException(nameof(holdCycles), "IO 保持周期数不能为负数。");
}
if (samplePeriod <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(samplePeriod), "采样周期必须大于零。");
}
var timestamps = _resolver.Resolve(trajectory);
var program = trajectory.OriginalProgram;
var robot = trajectory.Robot;
double triggerPeriodSeconds = robot.TriggerPeriod.TotalSeconds;
double samplePeriodSeconds = samplePeriod.TotalSeconds;
var shotEvents = new List();
var triggerTimeline = new List();
for (int i = 0; i < program.Waypoints.Count; i++)
{
if (!program.ShotFlags[i])
{
continue;
}
double triggerTime = timestamps[i] + program.OffsetValues[i] * triggerPeriodSeconds;
int sampleIndex = (int)Math.Round(triggerTime / samplePeriodSeconds);
double sampleTime = sampleIndex * samplePeriodSeconds;
var addressGroup = program.AddressGroups[i];
shotEvents.Add(new ShotEvent(
waypointIndex: i,
triggerTime: triggerTime,
sampleIndex: sampleIndex,
sampleTime: sampleTime,
addressGroup: addressGroup));
if (useDo)
{
// use_do=false 时保留 ShotEvent 诊断信息,但不向运行时下发 IO 脉冲。
triggerTimeline.Add(new TrajectoryDoEvent(
waypointIndex: i,
triggerTime: triggerTime,
offsetCycles: program.OffsetValues[i],
holdCycles: holdCycles,
addressGroup: addressGroup));
}
}
_logger?.LogInformation(
"ShotTimeline 构建完成: shotFlags总数={ShotFlagCount}, 触发事件数={TriggerCount}, useDo={UseDo}, holdCycles={HoldCycles}",
program.ShotFlags.Count(static f => f),
triggerTimeline.Count,
useDo,
holdCycles);
return new ShotTimeline(shotEvents, triggerTimeline);
}
}